0

I am trying to add the capability for a user to be able to mint and NFT from a Candy Machine from the frontend of my website. I am running into this error when the user approves the mint transaction (the second one on the frontend: WalletSendTransactionError: Signature verification failed

I have tried using both Phantom and Solfare to make sure that it wasn't a wallet-specific error, but I am unable to get the transaction to work. What am I missing in the backend/frontend?

Front end code:

const { connection } = useConnection();
    const { publicKey, sendTransaction } = useWallet();

    async function mintSong(e: React.MouseEvent) {
        e.preventDefault();
        if (!publicKey) return;  // Avoid VS Code issues

        // Get these two from DB
        const candyMachineAddr = '926XwiL5x2GLEzUySvfRdkFyqLcUbmxcNB1XviEeBSvt';
        const tonicCut = 0.1;  // Number in %
        const artistPubKeyStr = "Et1ctkaVX9wADf6xqjBTxsUBMKzJcfUYMPo3riFod5zk";
        const songPrice = 0.9;  // 0.9 USDC

        // ADD CHECK FOR PRICE AND CALL THE WALLET TO CHECK IF THEY HAVE ENOUGH TO PAY

        console.log(`Fetching semi transaction for CM: ${candyMachineAddr}`);

        // Fetch transaction from backend by sending connected wallet as 'account'
        fetch('http://localhost:3333/api/mintsong', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ 
                accountPubKeyStr: publicKey.toString(),
                candyMachineAddress: candyMachineAddr,
                tonicCut: tonicCut,
                artistPubKeyStr: artistPubKeyStr,
                songPrice: songPrice,
            })
        }).then(response => response.json())
        .then(async response => {
          const mintTransaction = Transaction.from(Buffer.from(response.transactionMint, 'base64'));
          const paymentTransaction = Transaction.from(Buffer.from(response.transactionPayment, 'base64'));
            try {
                await sendTransaction(paymentTransaction, connection);
                console.log('Payment transaction complete')
                await sendTransaction(mintTransaction, connection);
                if (response.deleteCandyMachine) {
                    fetch('http://localhost:3333/api/deleteCandyMachine', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ 
                            candyMachineAddress: candyMachineAddr,
                        })
                    });
                }
                alert('Purchase complete!');
            } catch (error) {
                console.error(error);
                alert(`Error sending transaction: ${error}`);
            }
        })
        .catch(error => {
          console.error(error);
        });
    }

Here is my backend code that creates two transactions: one for payment and one for minting. These are split and not bundled into the same transaction because bundling them made the transaction too big.

const ENDPOINT = clusterApiUrl('devnet');
const authority = createKeypairFromFileSync('devnet_keypair.json');
// Mainet USDC
// const USDC_ADDRESS = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
// Devnet 'fake' USDC, you can get these tokens from https://spl-token-faucet.com/
const USDC_ADDRESS = new PublicKey("Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr");

  routes.post('/api/mintsong', async (req, res) => {
  const accountPubKeyStr = req.body.accountPubKeyStr;
  const userPubkey = new PublicKey(accountPubKeyStr);
  const artistPubKeyStr = req.body.artistPubKeyStr;
  const artistPubKey = new PublicKey(artistPubKeyStr);
  const candyMachineAddress = req.body.candyMachineAddress;
  const tonicCut = parseFloat(req.body.tonicCut);
  const songPrice = parseFloat(req.body.songPrice);

  console.log(`User pubkey: ${accountPubKeyStr}`);
  console.log(`Candy Machine Addr: ${candyMachineAddress}`);
  
  const connection = new Connection(ENDPOINT)

  // Get the shop keypair from the environment variable
  const shopKeypair = authority;

  // Create a guest identity for buyer, so they will be a required signer for the transaction
  const identitySigner = new GuestIdentityDriver(userPubkey);

  // Initialise Metaplex with our shop keypair
  const metaplex = Metaplex
    .make(connection)
    .use(guestIdentity(userPubkey));

  const candyMachine = await metaplex.candyMachines().findByAddress({ address: new PublicKey(candyMachineAddress) });
  console.log(`Found CM at ${candyMachine.address}`);
  console.log(`ShopKeypair pubkey ${shopKeypair.publicKey}`);

  let deleteCandyMachine = false;
  if (candyMachine.itemsRemaining.toNumber() <= 1) {
    deleteCandyMachine = true;
  }

  const transactionBuilder = await metaplex.candyMachines().builders().mint({
    candyMachine: candyMachine,
    collectionUpdateAuthority: shopKeypair.publicKey,
    guards: {
      thirdPartySigner: {
        signer: shopKeypair,
      }
    }
  });

  // console.log(`Transaction signers: ${transactionBuilder.}`);


  const usdcMint = await getMint(connection, USDC_ADDRESS);
  // Get the buyer's USDC ATA
  console.log(`FROM`);
  const fromUsdcAddress = await getOrCreateAssociatedTokenAccount(
    connection,
    shopKeypair,
    USDC_ADDRESS,
    userPubkey,
  );
  // Get the shop's USDC ATA
  console.log(`SHOP`);
  const toUsdcAddressShop = await getOrCreateAssociatedTokenAccount(
    connection,
    shopKeypair,
    USDC_ADDRESS,
    shopKeypair.publicKey,
  );
  // Get the artist's USDC ATA
  console.log(`ARITST`);
  const toUsdcAddressArtist = await getOrCreateAssociatedTokenAccount(
    connection,
    shopKeypair,
    USDC_ADDRESS,
    artistPubKey,
  );

  // Allow the network to catch up
  await sleep(2000); // Sleep for 2 seconds

  console.log(`Both ATAs for USDC created`);
  const decimals = usdcMint.decimals;
  const PRICE_USDC_BP = songPrice * 10 ** decimals;
  const artistCutBP = PRICE_USDC_BP * (1 - tonicCut);
  const tonicCutBP = PRICE_USDC_BP * tonicCut;

  console.log(`PRICE USDC BP: ${PRICE_USDC_BP}`);
  console.log(`Tonic Cut BP: ${tonicCutBP}`);
  console.log(`USDC Decimals: ${decimals}`);

  console.log('Shop instruction');
  const usdcTransferInstructionShop = createTransferCheckedInstruction(
    fromUsdcAddress.address, // from USDC address
    USDC_ADDRESS, // USDC mint address
    toUsdcAddressShop.address, // to USDC address
    userPubkey, // owner of the from USDC address (the buyer)
    tonicCutBP, // price in BASIS POINTS
    decimals
  );

  console.log('Artist instruction');
  const usdcTransferInstructionArtist = createTransferCheckedInstruction(
    fromUsdcAddress.address, // from USDC address
    USDC_ADDRESS, // USDC mint address
    toUsdcAddressArtist.address, // to USDC address
    userPubkey, // owner of the from USDC address (the buyer)
    artistCutBP, // price in BASIS POINTS
    decimals
  );

  console.log('New transaction builder');
  const paymentTransactionBuilder = new TransactionBuilder();

  console.log('Prepend artist');
  paymentTransactionBuilder.add({
    instruction: usdcTransferInstructionArtist,
    signers: [identitySigner]
  }, {
    instruction: usdcTransferInstructionShop,
    signers: [identitySigner]
  });

  console.log(`Prepended usdc transfer to the song creation`);

  // transactionBuilder.setFeePayer(payerKeypair)

  // Convert to transaction
  const latestBlockhash = await connection.getLatestBlockhash();
  const mintTransaction = await transactionBuilder.toTransaction(latestBlockhash);
  console.log(`Mint Transaction created`);
  const paymentTransaction = await paymentTransactionBuilder.toTransaction(latestBlockhash);
  console.log(`Payment Transaction created`);

  // Partially sign the transaction, as the shop and the mint
  // The account is also a required signer, but they'll sign it with their wallet after we return it

  mintTransaction.sign(shopKeypair);
  paymentTransaction.sign(shopKeypair);
  console.log(`Transaction signed by shop`);
  

  // Serialize the transaction and convert to base64 to return it
  const serializedTransactionMint = mintTransaction.serialize({
    requireAllSignatures: false // account is a missing signature
  });
  console.log(`Transaction serialized and partially signed mint`);

  const serializedTransactionPayment = paymentTransaction.serialize({
    requireAllSignatures: false // account is a missing signature
  });
  console.log(`Transaction serialized and partially signed payment`);

  const base64Mint = serializedTransactionMint.toString('base64');
  const base64Payment = serializedTransactionPayment.toString('base64');
  console.log(`Transaction converted to base64 string`);

  // Return the serialized transaction
  res.send({
    transactionMint: base64Mint,
    transactionPayment: base64Payment,
    deleteCandyMachine: deleteCandyMachine,
  });
});```


  [1]: https://i.stack.imgur.com/SGcw1.png
1
  • I am facing the same issue... Can anyone please help?? Sep 20, 2023 at 10:35

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.