简体   繁体   中英

How to create account/signer using Phantom Wallet provider?

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?

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');

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

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM