0
// src/uploadMetadata.js
const { config } = require("dotenv");
config();
const { createMetadataAccountV3, updateMetadataAccountV2, findMetadataPda } = require("@metaplex-foundation/mpl-token-metadata");
const web3 = require("@solana/web3.js");
const { Keypair } = require('@solana/web3.js');
const { createSignerFromKeypair, none, signerIdentity, some } = require("@metaplex-foundation/umi");
const { createUmi } = require("@metaplex-foundation/umi-bundle-defaults");
const { fromWeb3JsKeypair } = require("@metaplex-foundation/umi-web3js-adapters");
const bs58 = require("bs58");

// const PRIVATE_KEY = process.env.PRIVATE_KEY.split(',').map(Number);

// Function to load wallet key
function loadWalletKey(privateKeyBytes) {
  return web3.Keypair.fromSecretKey(new Uint8Array(privateKeyBytes));
}

// Function to add metadata
async function uploadMetadata(PRIVATE_KEY, metadataInfo, mintAddress) {
  try {
    console.log(`Metadata Info received`, metadataInfo);
    console.log(`Uploading Metadata to the Token`, mintAddress);
    // Wait for 8 seconds before using the mint address
    await new Promise(resolve => setTimeout(resolve, 13000));

    const umi = createUmi(process.env.SOLANA_API_URL);
    const signer = createSignerFromKeypair(
      umi,
      fromWeb3JsKeypair(loadWalletKey(PRIVATE_KEY))
    );
    umi.use(signerIdentity(signer, true));

    const mint = new web3.PublicKey(mintAddress);
    console.log(`Adding metadata to the Token`, mintAddress);
    const onChainData = {
      ...metadataInfo,
      sellerFeeBasisPoints: 0,
      creators: none(),
      collection: none(),
      uses: none(),
    };

    // Check if the metadata account should be initialized or updated
    const INITIALIZE = true;

    if (INITIALIZE) {
      // Create metadata account and upload metadata
      const accounts = {
        mint: mint,
        mintAuthority: signer,
      };
      const data = {
        isMutable: true,
        collectionDetails: null,
        data: onChainData,
      };
      const txid = await createMetadataAccountV3(umi, {
        ...accounts,
        ...data,
      }).sendAndConfirm(umi);

      console.log(`Metadata uploaded successfully for mint: ${mintAddress}`);
      return { success: true, txid }; // Return success with transaction ID
    } else {
      // Update existing metadata account
      const data = {
        data: some(onChainData),
        discriminator: 0,
        isMutable: some(true),
        newUpdateAuthority: none(),
        primarySaleHappened: none(),
      };
      const accounts = {
        metadata: findMetadataPda(umi, { mint: mint }),
        updateAuthority: signer,
      };
      const txid = await updateMetadataAccountV2(umi, {
        ...accounts,
        ...data,
      }).sendAndConfirm(umi);

      console.log(`Metadata updated successfully for mint: ${mintAddress}`);
      return { success: true, txid }; // Return success with transaction ID
    }
  } catch (error) {
    console.error(error);
    throw new Error("Failed to upload metadata");
  }
}

module.exports = { uploadMetadata };

2 Answers 2

0

This is the frontend code that sends the metadata info to the backend

// src/uploadMetadata.js
const { config } = require("dotenv");
config();
const { createMetadataAccountV3, findMetadataPda, PROGRAM_ID } = require("@metaplex-foundation/mpl-token-metadata");
const { Connection, PublicKey, Transaction } = require("@solana/web3.js");

async function createMetadataTransaction(metadataInfo, mintAddress, publicKey) {
  try {
    console.log(`Metadata Info received`, metadataInfo);
    console.log(`Creating Metadata Transaction for the Token`, mintAddress);

    const connection = new Connection(process.env.SOLANA_API_URL);
    const mint = new PublicKey(mintAddress);
    const payer = new PublicKey(publicKey);

    const onChainData = {
      name: metadataInfo.name,
      symbol: metadataInfo.symbol,
      uri: metadataInfo.uri,
      sellerFeeBasisPoints: 0,
      creators: null,
      collection: null,
      uses: null,
    };

    const metadataPda = await findMetadataPda(mint);

    const accounts = {
      metadata: metadataPda,
      mint: mint,
      mintAuthority: payer,
      payer: payer,
      updateAuthority: payer,
    };

    const transaction = new Transaction().add(
      createMetadataAccountV3({
        data: onChainData,
        accounts,
      })
    );

    // Set the fee payer (the user's public key)
    console.log(`Fee Payer: ${payer.toBase58()}`);
    transaction.feePayer = payer;

    // Get recent blockhash
    const { blockhash } = await connection.getLatestBlockhash();
    transaction.recentBlockhash = blockhash;

    // Serialize unsigned transaction data
    console.log("Serializing Transaction for metadata");
    const serializedTransaction = transaction.serialize({ requireAllSignatures: false }).toString('base64');

    console.log("Serialized Transaction for metadata:", serializedTransaction);

    return serializedTransaction;
  } catch (error) {
    console.error(error);
    throw new Error("Failed to create metadata transaction");
  }
}

module.exports = { createMetadataTransaction };


const handleSubmit = async (event) => { event.preventDefault();

const formData = new FormData(event.target);
if (publicKey) {
  formData.append("publicKey", publicKey.toString()); // Add publicKey to the form data
}

if (!publicKey || !wallet) {
  showErrorToast("Please connect your wallet");
  alert("Please connect your wallet and ensure it supports signing transactions.");
  return;
}

const inputputChecker = inputChecks(isSplToken, splTokenData, spl2022TokenData);
if (inputputChecker) {
  showErrorToast(inputputChecker);
  return;
}

if (!paid) {
  const feeAmount = 0.01; // Required fee amount in SOL
  const FEE_RECEIVER = "HMGG6astUF92DMV3p8Eep5KLH7DGruCDWkbio2D4xzv";
  const feeTransaction = new Transaction();
  feeTransaction.add(
    SystemProgram.transfer({
      fromPubkey: publicKey,
      toPubkey: new PublicKey(FEE_RECEIVER),
      lamports: feeAmount * LAMPORTS_PER_SOL,
    })
  );
  feeTransaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  feeTransaction.feePayer = publicKey;

  try {
    setPaying(true);
    const signedFeeTransaction = await wallet.adapter.signTransaction(feeTransaction);
    const feeTxid = await connection.sendRawTransaction(signedFeeTransaction.serialize(), { skipPreflight: true });
    await connection.confirmTransaction(feeTxid, "confirmed");
    console.log("Fee Transaction Confirmed:", feeTxid);
    setPaid(true);
    showSuccessToast("Fee payment was successful");
  } catch (error) {
    setPaying(false);
    console.error("Fee transaction error:", error);
    showErrorToast("Fee payment failed, please try again");
    alert("Failed to process fee transaction. Error: " + error.message);
  }
} else {
  const endpoint = isSplToken ? "http://localhost:3000/api/spltoken" : "http://localhost:3000/api/spltoken2022";
  const revokeMintEndpoint = isSplToken ? "http://localhost:3000/api/spltoken/revoke-mint" : "http://localhost:3000/api/spltoken2022/revoke-mint";
  const metadataEndpoint = isSplToken ? "http://localhost:3000/api/spltoken/create-metadata" : "http://localhost:3000/api/spltoken2022/create-metadata";

  try {
    setCreatingToken(true);
    const response = await axios.post(endpoint, formData, {
      headers: {
        "Content-Type": "multipart/form-data",
      },
    });

    const data = response.data;
    console.log("Mint address:", data.mintPublicKey);
    setMintAdress(data.mintPublicKey);
    console.log("Token details:", data.metadataInfo);
    if (!data.success) {
      throw new Error(data.message || "Failed to create token");
    }

    // Deserialize the transaction
    let transaction = Transaction.from(Buffer.from(data.serializedTransaction, "base64"));
    console.log("Deserialized mint transaction:", transaction);

    // Sign the transaction using the wallet
    const signedTransaction = await wallet.adapter.signTransaction(transaction);
    console.log("Signed mint transaction:", signedTransaction);

    // Serialize the transaction for submission
    const serialized = signedTransaction.serialize();
    console.log("Serialized mint transaction:", serialized);

    // Submit the transaction
    const signature = await connection.sendRawTransaction(serialized, { skipPreflight: true });
    const confirmation = await connection.confirmTransaction(signature, "confirmed");
    console.log("Mint transaction signature:", signature);
    console.log("Mint transaction confirmation:", confirmation);

    if (confirmation.value.err) {
      throw new Error("Mint transaction failed");
    }

    // const walletAdapter = {
    //   publicKey: wallet.adapter.publicKey.toString(),
    //   signTransaction: wallet.adapter.signTransaction,
    //   signAllTransactions: wallet.adapter.signAllTransactions,
    // };

 // Prepare metadata transaction
 try {
  const response = await axios.post(metadataEndpoint, {
    metadataInfo: data.metadataInfo,
    mintAddress: data.mintPublicKey,
    publicKey: publicKey.toString(),
  }, {
    headers: {
      "Content-Type": "application/json",
    },
  });

  const { serializedTransaction } = response.data;
  const transaction = Transaction.from(Buffer.from(serializedTransaction, 'base64'));

  const signedTransaction = await wallet.adapter.signTransaction(transaction);
  const signature = await connection.sendRawTransaction(signedTransaction.serialize());

  await connection.confirmTransaction(signature, 'processed');
  alert(`Metadata upload successful: ${signature}`);
} catch (error) {
  console.error(error);
  alert(`Metadata Transaction failed: ${error.message}`);
}

  // Revoke mint authority
  const revokeMintResponse = await axios.post(revokeMintEndpoint, {
    mintAddress: data.mintPublicKey,
    publicKey: publicKey.toString(),
  }, {
    headers: {
      "Content-Type": "application/json",
    },
  });

  const revokeMintData = revokeMintResponse.data;
  const revokeTransaction = Transaction.from(Buffer.from(revokeMintData.unsignedTransaction, "base64"));
  console.log("Revoke transaction created:", revokeTransaction);

  // Sign the revoke mint transaction using the wallet
  const signedRevokeTransaction = await wallet.adapter.signTransaction(revokeTransaction);
  console.log("Signed revoke transaction:", signedRevokeTransaction);

  // Serialize the revoke mint transaction for submission
  const serializedRevokeTransaction = signedRevokeTransaction.serialize();
  console.log("Serialized revoke transaction:", serializedRevokeTransaction);

  // Submit the revoke mint transaction
  const revokeSignature = await connection.sendRawTransaction(serializedRevokeTransaction, { skipPreflight: true });
  const revokeConfirmation = await connection.confirmTransaction(revokeSignature, "confirmed");
  console.log("Revoke mint transaction signature:", revokeSignature);
  console.log("Revoke mint transaction confirmation:", revokeConfirmation);

  setCompleted(true);
  showSuccessToast("Token was successfully created and mint authority revoked");
  console.log(`Token created with transaction signature: ${signature}`);
  alert("Token created and mint authority revoked successfully!");
} catch (error) {
  console.error("Token creation error:", error);
  setCreatingToken(false);
  alert("Failed to create token. Error: " + error.message);
}

} };

0

I would suggest using using signed URL to do the uploads. Major Cloud providers like Amazon and GCP provide methods that you can use from their SDKs

If you are looking for a decentralized solution, UMI also offers upload helpers https://developers.metaplex.com/umi/storage#the-uploader-interface

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.