EVM NFT Minting Guide for MCP
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 usingpaymentToken: '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= $70.5= $0.500.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
paymentTokenin 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: trueonly 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").