0

I am following the inner instructions of a Solana NFT to create the process of listing an NFT for sale. According to this NFT, https://solscan.io/tx/25g9L7rDCn8ZbZAZoCvyVNe5woZUoK2PVU3VEXftqySa2EYsLUJB2GA42qDwxsezUBRH2A9eJX1iUUD2LCK9Fua9, the first instruction is to create an account.

The instruction shows

NewAccount - E61SDPzHP61C4KwwiSqG4FAHXof852wsaSjh3F4kLbwmicon

Source - H2eud1RCJu8u8vEQ8jJLQ32jM5oKfARGxz5LwEKKfTLuicon

TransferAmount(SOL) - 0.00223416

ProgramOwner - M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7Kicon

New Account seems to be a newly generated account and Source is the wallet which was used when the user connected their wallet to the client.

I am using the this code from the Solana Cookbook for reference

import { clusterApiUrl, Connection, PublicKey, Keypair, Transaction, SystemProgram } from "@solana/web3.js";
import { Token, TOKEN_PROGRAM_ID, AccountLayout } from "@solana/spl-token";
import * as bs58 from "bs58";

(async () => {
  // connection
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

  // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8
  const feePayer = Keypair.fromSecretKey(
    bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2")
  );

  // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY
  const alice = Keypair.fromSecretKey(
    bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp")
  );

  const mintPubkey = new PublicKey("54dQ8cfHsW1YfKYpmdVZhWpb9iSi6Pac82Nf7sg3bVb");

  // generate a new keypair for token account
  const tokenAccount = Keypair.generate();
  console.log(`token account: ${tokenAccount.publicKey.toBase58()}`);

  let tx = new Transaction().add(
    // create token account
    SystemProgram.createAccount({
      fromPubkey: feePayer.publicKey,
      newAccountPubkey: tokenAccount.publicKey,
      space: AccountLayout.span,
      lamports: await Token.getMinBalanceRentForExemptAccount(connection),
      programId: TOKEN_PROGRAM_ID,
    }),
    /**... some other instruction*/
  );
  console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, tokenAccount])}`);
})();

From my understanding, in this scenario the feePayer is the user who connected their wallet. (Source from the instructions), tokenAccount is the account that we want to transfer the SOL to (NewAccount from the instructions), and programID is ProgramOwner from the instructions.

When I run this exact code from the Solana Cookbook it works. When I try using the provider as the feePayer (wallet that was connected in the client) it doesn't work.

I have a function to get my provider

getProvider = ()  => {
    if ("solana" in window) {
        const anyWindow = window;
        const provider = anyWindow.solana;
        if (provider.isPhantom) {
            return provider;
        }
    }
    window.open("https://phantom.app/", "_blank");
};

Then I grab my provider with

const provider = this.getProvider();

I replace the original feePayer created with Keypair.fromSecretKey with the provider.

My assumption as to why provider isn't working as one of the signers in await connection.sendTransaction(tx, [provider, tokenAccount]) is because the signer requires a secret key.

So what I try to do instead is sign the transaction myself using some code I've found online on how to sign a transaction.

const blockHash = await connection.getRecentBlockhash()
tx.feePayer = provider.publicKey
tx.recentBlockhash = await blockHash.blockhash
const signed = await provider.signTransaction(tx);

This runs fine and I'm able to see the results when I do console.log(signed); The problem I'm getting is that there are 2 signatures and one of them is null. One is the signature of the provider and the other is the signature of the tokenAccount. When I use console.log() to show the public key of the provider and the public key of the tokenAccount and try to match it to the public keys within the signature, I find that the signature of the tokenAccount is the one that is returning null.

So when it's time to run const signature = await connection.sendRawTransaction(signed.serialize()); I get an error from signed.serialize() saying Signature verification failed

What am I doing wrong here? Can I create a Signer from a user connecting their phantom wallet? If not, how can I sign the transaction so that neither of the signatures come out null?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Bryan Bastida
  • 33
  • 1
  • 2
  • 9

3 Answers3

1

So I would recommend using the wallet-adapter to connect and send transactions from wallets.

This will make the code above look something like this:

const { publicKey, sendTransaction } = useWallet();

// G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY
const alice = Keypair.fromSecretKey(
    bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp")
);

const mintPubkey = new PublicKey("54dQ8cfHsW1YfKYpmdVZhWpb9iSi6Pac82Nf7sg3bVb");

// generate a new keypair for token account
const tokenAccount = Keypair.generate();
console.log(`token account: ${tokenAccount.publicKey.toBase58()}`);

let tx = new Transaction().add(
    // create token account
    SystemProgram.createAccount({
      fromPubkey: publicKey,
      newAccountPubkey: tokenAccount.publicKey,
      space: AccountLayout.span,
      lamports: await Token.getMinBalanceRentForExemptAccount(connection),
      programId: TOKEN_PROGRAM_ID,
    }),
    /**... some other instruction*/
);
const signature = await sendTransaction(transaction, connection);

await connection.confirmTransaction(signature, 'processed');
Jacob Creech
  • 1,797
  • 2
  • 11
  • You can find a bunch of wallet-adapter examples here https://solanacookbook.com/references/basic-transactions.html#sending-spl-tokens – Jacob Creech Feb 14 '22 at 17:59
0

Hi i have similar issue, There are two signers admin and user, admin is signing the tx from node and serializing tx. this i send to frontend and user signs it. But as initially the signature is null, so wallet.signTransaction(tx); gives the Signature verification failed

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 16 '22 at 10:33
0

You have to partially sign with the tokenAccount before sending the transaction.

const blockHash = await connection.getRecentBlockhash()
tx.feePayer = provider.publicKey
tx.recentBlockhash = await blockHash.blockhash
const signed = await provider.signTransaction(tx);
// now sign with the tokenAccount
signed.partialSign(tokenAccount);

Wachin W
  • 1
  • 1