Belong.net Logo
Crypto

EVM NFT Minting Guide for MCP

End-to-end NFT minting on EVM chains using MCP tools.

Overview

This document outlines the process of minting NFTs on EVM-compatible blockchains (e.g., BNB, Ethereum, Polygon) using MCP (Model Context Protocol) tools. The workflow involves preparing transactions with MCP tools, signing and broadcasting them on the frontend, and finalizing operations on the backend. The minting process has been updated to use /deploy-collection and /complete-collection endpoints for deployment, with minting handled via /evm-mint (now active) and /mint-complete.

📋 Prerequisites: Ensure MCP integration is set up with your API key.

Supported Payment Tokens

Collections can accept payments in two types of tokens:

  • NATIVE - Chain's native token (automatically resolved based on chain ID):
    • Ethereum (1): ETH
    • Polygon (137): POL
    • Celo (42220): CELO
    • Sepolia (11155111): ETH
    • Base (8453): ETH
    • And other EVM chains
  • USDC - USD Coin stablecoin (chain-specific addresses are automatically resolved)
  • BUSD - Binance USD stablecoin (chain-specific addresses are automatically resolved)

💡 Note: When using paymentToken: 'NATIVE', users pay with the chain's native currency (e.g., ETH on Ethereum, POL on Polygon). When using paymentToken: 'USDC' or 'BUSD', users must approve the collection contract to spend the ERC20 token before minting.

Price Format

All prices (mintPrice, whitelistMintPrice) should be specified in human-readable format:

  • 1 = $1 (or 1 USDC/ETH/POL)
  • 7 = $7
  • 0.5 = $0.50
  • 0.001 = $0.001

The system automatically converts these values to the correct on-chain format based on the token's decimals (18 for native tokens, 6 for USDC).

Process Flow

flowchart TD;

A[Start: User Authentication] --> B[Deploy Collection]
B --> C[Complete Deployment]
C --> D{Payment Token}
D --> E[Native Token]
D --> F[Approve ERC20 Token]
E --> G[Mint NFTs]
F --> G
G --> H[Complete Mint]
H --> I[End: NFTs Minted]

style A fill:#e1f5fe
style I fill:#c8e6c9
style F fill:#fff3e0
style G fill:#f3e5f5

MCP Tools

1. Deploy Collection

Tool: deploy-collection

Description: Prepares a new NFT collection transaction for signing. Updated to use /crypto/evm/deploy-collection endpoint. No database collection is created until complete-collection confirms the deployment transaction.

Input data:

{
  name: string,                    // Collection name
  symbol: string,                  // Collection symbol (no spaces allowed)
  description?: string,            // Description
  feeNumerator: number,            // Royalty fraction (basis points, max 6)
  mintPrice: number,               // Mint price in human-readable format (e.g., 1 for $1, 7 for $7, 0.5 for $0.50)
  whitelistMintPrice: number,      // Whitelist mint price in human-readable format (e.g., 0.3 for $0.30)
  transferable: boolean,           // NFT transferability
  maxTotalSupply: number,          // Max NFT count
  paymentToken: 'NATIVE' | 'USDC' | 'BUSD', // Payment token ('NATIVE' for chain native token like ETH/POL, 'USDC' or 'BUSD' for stablecoin)
  creator: string,                 // Creator wallet address
  chainId: number,                 // EVM chain ID
  externalUrl: string | null,      // External link (required, can be null)
  attributes?: Array<{             // Optional attributes
    trait_type: string,
    display_type?: string,
    value: string
  }>,
  referralCode?: string            // Optional referral code
}

Output data:

{
  params: [{
    creator: string,               // Creator wallet address
    paymentToken: string,          // Token contract address (resolved from NATIVE/USDC/BUSD input)
    feeNumerator: number,
    transferable: boolean,
    maxTotalSupply: number,
    mintPrice: string,             // Price in wei/token units
    whitelistMintPrice: string,    // Price in wei/token units
    metadata: { name: string, symbol: string },
    contractURI: string
  }, "0x0...", {
    nonce: number,
    deadline: number,
    signature: string
  }],
  verifyingContract: string,       // Address of the verifying contract
  method: object,                  // ABI-encoded method details for the transaction
}

Note: The paymentToken in output is the resolved token contract address (e.g., 0xEee... for NATIVE, or USDC/BUSD contract address), automatically converted from the 'NATIVE' | 'USDC' | 'BUSD' input.

2. Complete Deployment

Tool: complete-collection

Description: Finalizes collection deployment after broadcasting the transaction. Updated to use /crypto/evm/complete-collection endpoint. This is the step that persists the backend collection.

Input data:

{
  chainId: number,                 // EVM chain ID
  hash: string,                    // Deployment transaction hash
  hubId?: string,                  // Optional hub ID
  activityId?: string,             // Optional activity ID
  gasless?: boolean,               // Gasless mode
  whitelist_hubIDs?: string[],     // Whitelist hub IDs
  whitelist_userIDs?: string[]     // Whitelist user IDs
}

The backend reconstructs the collection from the deployment transaction calldata and receipt. It verifies that the transaction was sent to the configured NFT factory and that the deployed collection belongs to the authenticated creator wallet.

Sponsored minting note: gasless: true only enables sponsorship eligibility for the collection. Sponsor balances, preflight checks, and post-transaction usage recording are handled through the sponsorship API. See Sponsored Gas Ledger Guide.

Output data:

{
  id: string,                     // Backend collection ID for minting/media/checkout
  // Created or updated collection data (includes nftAddress, produce_data, etc.)
}

📸 Media Upload: After completing the collection deployment, you can add a cover image and NFT images to your collection. See Upload Collection Media for details.

3. Approve Payment Token (Required before Mint if using USDC or BUSD)

⚠️ Important: If paymentToken is set to "USDC" or "BUSD" (ERC20 tokens), you must approve the collection contract to spend tokens before minting. This step is not needed for "NATIVE" tokens (ETH, POL, etc.).

Implementation:

// Only needed if paymentToken is "USDC" or "BUSD"
const totalAmount = mintTokens.reduce(
  (sum, token) => sum + token.price * BigInt(token.quantity),
  BigInt(0),
);
await walletClient.writeContract({
  address: payingTokenAddress, // USDC or BUSD contract address
  abi: erc20Abi,
  functionName: "approve",
  args: [collectionAddress, totalAmount],
});

4. Mint NFT

Tool: evm-mint

Description: Prepares a crypto NFT minting transaction, returning data for signing. The endpoint does not accept paymentsMethod and does not return checkout provider data.

Input data:

{
  collectionId: string,            // Backend collection ID
  chainId: number,                 // EVM chain ID
  walletAddress: string,           // Minter's wallet address
  nfts: Array<{                    // NFTs to mint
    receiver: string,              // Receiver address for this NFT (where the NFT will be sent)
    name: string,                  // Name of the individual NFT
    description: string,           // Description of the NFT
    mintPrice: number,             // Price in human-readable format (e.g., 1 for $1, 0.5 for $0.50)
    image?: string,                // Image URI (optional)
    attributes?: Array<{ trait_type: string, display_type?: string, value: string }>,  // NFT attributes (optional)
    external_url?: string,         // External link for the NFT (optional)
    quantity: number,              // Quantity to mint (number of copies)
    animation_url?: string,        // Animation URI for video NFTs (optional)
    youtube_url?: string           // YouTube embed URL (optional)
  }>
}

Output data:

{
  chainId: number,
  verifyingContract: string,       // Collection contract address
  method: object,                  // ABI-encoded method details for the transaction
  params: [
    string,                        // Payment token address (0xEee... for NATIVE or USDC/BUSD contract address)
    Array<string>,                 // Receivers array - one address per NFT
    Array<{                        // DynamicPriceParameters array - one per NFT
      tokenId: bigint,            // Token ID (bigint format)
      price: bigint,              // Price in wei/token units (bigint format)
      tokenUri: string            // Metadata URI
    }>,
    Array<{                        // SignatureProtection array - one per NFT
      nonce: bigint,              // Nonce for replay protection
      deadline: bigint,           // Expiration timestamp
      signature: string           // ECDSA signature (hex format, 0x...)
    }>
  ],
  tokens: Array<{
    token_id: string,
    token_uri: string,
    minted_price: number,
    name: string,
    image?: string,
    attributes?: Array<{...}>,
    description?: string,
    external_url?: string,
    animation_url?: string,
    youtube_url?: string
  }>
}

Important: All arrays (receivers, dynamicPriceParameters, protections) must have the same length and be synchronized by index. Element at index i in each array corresponds to the same mint operation. The signature in protections[i] is generated for the parameters in dynamicPriceParameters[i] and receivers[i].

5. Complete Mint

Tool: mint-complete

Description: Completes the NFT minting process and saves token data. Requires authenticated user.

Input data:

{
  chainId: number,                 // EVM chain ID
  hash: string                     // Mint transaction hash
}

The backend reconstructs collectionId, walletAddress, and token data from the mint transaction calldata, receipt logs, and token metadata URI. All minted receivers must belong to the authenticated user on the same chain.

Output data:

Array<{
  id: string,
  name?: string,
  hash?: string,
  address: string,
  block_number?: string,
  token_id: string,
  token_uri?: string,
  image?: string,
  userId?: string,
  minted_price?: number,
  attributes?: Array<{...}>,
  cryptoAddress?: { address: string }
}>

Usage

Deploy Collection

const deployResult = await mcp.callTool("/crypto/evm/deploy-collection", {
  data: {
    name: "My Collection",
    symbol: "MC",
    description: "My awesome NFT collection",
    feeNumerator: 5,
    mintPrice: 1, // $1 in human-readable format
    whitelistMintPrice: 0.5, // $0.50 for whitelisted users
    transferable: true,
    maxTotalSupply: 1000,
    chainId: 1,
    creator: account.address,
    paymentToken: "USDC", // or "NATIVE" for ETH, or "BUSD" for BUSD
    externalUrl: "https://example.com",
  },
});
const { params, verifyingContract, method } = deployResult;
const hash = await walletClient.writeContract({
  address: verifyingContract,
  abi: [method],
  functionName: method.name,
  args: params,
});

Complete Deployment

const collection = await mcp.callTool("/crypto/evm/complete-collection", {
  data: {
    chainId: 1,
    hash,
  },
});
const collectionId = collection.id;

Create Referral Code

const { address, method } = await mcp.callTool(
  "/crypto/evm/create-referral-code",
  {
    data: { chainId: 1 },
  },
);
await walletClient.writeContract({
  address,
  abi: [method],
  functionName: method.name,
});

Get Referral Code by Creator

const { referralCode } = await mcp.callTool(
  "/crypto/evm/get-referral-code-by-creator",
  {
    data: {
      chainId: 1,
      walletAddress: "0x1234567890abcdef1234567890abcdef12345678",
    },
  },
);
// referralCode is a bytes32 hash, returns zeroHash if no valid code exists

Mint NFTs

const mintResult = await mcp.callTool("/crypto/evm/evm-mint", {
  data: {
    collectionId,
    chainId: 1,
    walletAddress: account.address,
    nfts: [
      {
        receiver: account.address, // Receiver address (where NFT will be sent)
        name: "NFT 1",
        description: "First NFT",
        mintPrice: 1,
        quantity: 1,
      }, // $1 per NFT
    ],
  },
});
const { verifyingContract, params } = mintResult;

// params structure: [paymentToken, receivers, dynamicPriceParameters, protections]
const [paymentTokenAddress, receivers, dynamicPriceParameters, protections] =
  params;

// Check if payment token is ERC20 (not native token)
const isNativeToken =
  paymentTokenAddress.toLowerCase() ===
  "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";

if (!isNativeToken) {
  // Approve ERC20 token (e.g., USDC, BUSD)
  // Calculate total amount needed for all NFTs
  const totalAmount = dynamicPriceParameters.reduce(
    (sum, item) => sum + BigInt(item.price),
    0n,
  );
  await walletClient.writeContract({
    address: paymentTokenAddress, // ERC20 token address
    abi: erc20Abi,
    functionName: "approve",
    args: [verifyingContract, totalAmount],
  });
}

// Call mintDynamicPrice function
const mintHash = await walletClient.writeContract({
  address: verifyingContract,
  abi: mintAbiV2, // Use mint-abi-v2.ts ABI
  functionName: "mintDynamicPrice",
  args: [paymentTokenAddress, receivers, dynamicPriceParameters, protections],
});

Note: The mintDynamicPrice function signature is:

mintDynamicPrice(
  address expectedPayingToken,
  address[] calldata receivers,
  DynamicPriceParameters[] calldata dynamicPriceParameters,
  SignatureProtection[] calldata protections
) external payable

Where DynamicPriceParameters is (uint256 tokenId, uint256 price, string tokenUri) and SignatureProtection is (uint256 nonce, uint256 deadline, bytes signature).

Complete Mint

await mcp.callTool("/crypto/evm/mint-complete", {
  data: {
    chainId: 1,
    hash: mintHash,
  },
});

Errors

MCP tools return errors in the standard MCP format, with details in the error field (e.g., "INVALID_CHAIN_ID", "INSUFFICIENT_BALANCE").

Copyright © 2026