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?