1

I'm working on a React app integrated with Solana blockchain, and I'm facing a challenge with the token minting flow. The goal is for users to request minting of new tokens through a backend service, which then creates and signs a transaction. The user's wallet should submit this transaction to the blockchain and pay the associated fees.

Here's the flow I need:

  1. User requests token minting via UI.
  2. React app sends a request to the backend.
  3. Backend creates a signed transaction with minting instructions.
  4. Backend sends the signed transaction back to the frontend.
  5. User's wallet submits the transaction, paying the fees.

Issue:

The backend seems to create the transaction successfully, but when the frontend tries to submit it, I encounter an unexpected error.

Backend Code (Node.js):

router.post("/sol", authenticationMiddleware, corsMiddleware, async (req, res) => {
    try {
        const { decodedToken } = res.locals;

        if (!decodedToken) {
            return res.status(500).send("Token error");
        }

        const getUser = await db.collection("users").doc(decodedToken.uid).get();
        const user = getUser.data();
        const userTokens = Math.floor(user?.tokens); // Use numeric value for Solana transactions
        const userWallet = user?.solWallet;

        // Assuming you have a function to create a transaction for minting tokens
        // This function should prepare the transaction with the mint instruction but not sign it
        // The transaction is then serialized to a base64 string to be sent to the client
        const transactionBase64 = await prepareMintTransaction(userWallet, userTokens);
        return res.status(200).send({ transaction: transactionBase64 });

    } catch (error) {
        console.log("Error:", error);
        return res.status(500).send({ "error": "An error occurred while processing your request. Please try again." });
    }
});


async function prepareMintTransaction(userWallet, userTokens) {

    // Convert the secret key and mint public key string to their respective types
    const mintAuthoritySecretKey = Uint8Array.from(MINT_AUTHORITY_SECRET_KEY);
    const mintPublicKeyStr = MINT_PUBLIC_KEY_STR;
    const userPublicKeyStr = userWallet; // Assuming userWallet is a string
    const amount = userTokens; // Assuming userTokens is the amount to mint

    const connection = new web3.Connection(web3.clusterApiUrl("devnet"), "confirmed");
    const mintPublicKey = new web3.PublicKey(mintPublicKeyStr);
    const userPublicKey = new web3.PublicKey(userPublicKeyStr);
    const mintAuthorityKeypair = web3.Keypair.fromSecretKey(mintAuthoritySecretKey);

    // Fetch or create the associated token account for the recipient user
    const toTokenAccount = await splToken.getOrCreateAssociatedTokenAccount(
        connection,
        mintAuthorityKeypair,
        mintPublicKey,
        userPublicKey
    );

    // Make sure the amount is specified correctly, accounting for decimals.
    // If your token has 9 decimals and you want to mint 1 token, you need to pass 1000000000 as the amount.
    const decimals = 9; // This should match the decimals of your token.
    const mintAmount = BigInt(amount) * BigInt(10 ** decimals);

    // Create the 'mint to' instruction
    const mintToInstruction = splToken.createMintToInstruction(
        mintPublicKey,                // The public key of the mint
        toTokenAccount.address,       // The associated token account of the recipient
        mintAuthorityKeypair.publicKey, // The mint authority public key
        mintAmount,                   // The amount to mint, as a BigInt or number
    );

    // Create a new transaction and add the mint instruction
    let theTransaction = new web3.Transaction().add(mintToInstruction);

    // Fetch the recent blockhash to include in the transaction
    // theTransaction.recentBlockhash = (await connection.getRecentBlockhash()).blockhash;
    const { blockhash } = (await connection.getLatestBlockhash());
    theTransaction.recentBlockhash = blockhash;
    // Temporarily set the fee payer for the sake of transaction structure
    theTransaction.feePayer = mintAuthorityKeypair.publicKey;

    // const serializedTransaction = transaction.serializeMessage();
    const serializedTransaction = theTransaction.serialize({
        requireAllSignatures: false, // Indicates not all signatures are present yet
        verifySignatures: false, // No need to verify signatures at this point
    });

    // Encode the serialized transaction to base64
    const base64Transaction = serializedTransaction.toString("base64");

    return base64Transaction;
}

I deployed a program here: https://solscan.io/token/2RbY2xRtSwgvKdyqvvFpZPrDwknnZY63HqfSQGxr5x6y?cluster=devnet

In the above code, the MINT_PUBLIC_KEY_STR is defined like this:

const MINT_PUBLIC_KEY_STR = "2RbY2xRtSwgvKdyqvvFpZPrDwknnZY63HqfSQGxr5x6y";

The MINT_AUTHORITY_SECRET_KEY is the value taken from mint-authority.json from the project that deployed the program.

Frontend Code (React):


const fetchTransactionData = async () => {
    try {
      const token = await auth?.currentUser?.getIdToken(true);
      const api = "http://localhost:5001/my-app/us-central1/api/claim/sol";
      const response = await axios.post(
        api,
        {},
        {
          headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
          },
        }
      );
      return response.data.transaction; // Return the base64 encoded transaction
    } catch (error) {
      console.error(
        "Error fetching transaction data:",
        error.response?.data || error.message
      );
      throw error; // Re-throw the error to handle it in the calling function
    }
  };

  const handleSignAndSendTransaction = async (base64Transaction) => {
    if (!publicKey || !signTransaction) {
      console.log("Wallet not connected");
      return;
    }

    try {
      // Convert the base64 encoded transaction to a Transaction object
      const transactionBuffer = Buffer.from(base64Transaction, "base64");
      const transaction = Transaction.from(transactionBuffer);

      let signedTransaction;
      try {
        signedTransaction = await signTransaction(transaction);
      } catch (signingError) {
        // Handle errors that occur during the signing process
        console.error("Error during transaction signing:", signingError);
        throw signingError; // Re-throw the error to prevent further execution
      }

      // Send the signed transaction to the blockchain
      const signature = await connection.sendRawTransaction(
        signedTransaction.serialize()
      );
      console.log("Transaction signature:", signature);

      // Confirm the transaction
      const confirmation = await connection.confirmTransaction(
        signature,
        "confirmed"
      );
      console.log("Transaction confirmation:", confirmation);
    } catch (error) {
      console.error("Error signing or sending the transaction:", error);
    }
  };

  const handleClaimTokens = async () => {
    try {
      const base64Transaction = await fetchTransactionData(); // Fetch the transaction data
      if (base64Transaction) {
        await handleSignAndSendTransaction(base64Transaction); // Sign and send the transaction
      }
    } catch (error) {
      console.error("Error in handleClaimTokens:", error);
    }
  };

Screenshot of the error below

enter image description here

1 Answer 1

0

You're very close! You just need to sign the transaction with mintAuthorityKeypair before serializing sending it back to the frontend:

    theTransaction.partialSign(mintAuthorityKeypair);
    const serializedTransaction = theTransaction.serialize({
        requireAllSignatures: false, // Indicates not all signatures are present yet
        verifySignatures: false, // No need to verify signatures at this point
    });

1
  • Hey Jon! Thank you for your response. Unfortunately we decided to drop the mint to user wallet. Seems too dangerous and we're working on a basic transfer of tokens from one wallet to another, but that's not working either. I'll post another question.
    – AlienDecoy
    Commented Apr 7 at 3:33

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.