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:
- User requests token minting via UI.
- React app sends a request to the backend.
- Backend creates a signed transaction with minting instructions.
- Backend sends the signed transaction back to the frontend.
- 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