mint-nft
Mint a new NFT on EVM (ERC-721) or Solana (SPL Token with 0 decimals and Metaplex metadata).
Dependencies
"use client";
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { useState } from "react";
// ★ Minimal ERC-721 ABI — safeMint requires the contract to be pre-deployed
// In production, deploy an OpenZeppelin ERC721 contract and use its address
const erc721MintAbi = [
{
name: "safeMint",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "address" },
{ name: "tokenId", type: "uint256" },
{ name: "uri", type: "string" },
],
outputs: [],
},
{
name: "totalSupply",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "uint256" }],
},
] as const;
export function MintNFT() {
const { address } = useAccount();
const [contractAddress, setContractAddress] = useState("");
const [recipient, setRecipient] = useState("");
const [tokenId, setTokenId] = useState("");
const [tokenUri, setTokenUri] = useState("");
const { writeContract, data: hash, isPending, error } = useWriteContract();
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash });
const handleMint = () => {
writeContract({
address: contractAddress as `0x${string}`,
abi: erc721MintAbi,
functionName: "safeMint",
args: [
(recipient || address) as `0x${string}`,
BigInt(tokenId),
tokenUri,
],
});
};
return (
<div>
<h2>Mint NFT (ERC-721)</h2>
<input
value={contractAddress}
onChange={(e) => setContractAddress(e.target.value)}
placeholder="NFT contract address (0x...)"
/>
<input
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
placeholder={`Recipient (default: ${address ?? "your wallet"})`}
/>
<input
value={tokenId}
onChange={(e) => setTokenId(e.target.value)}
placeholder="Token ID (e.g., 1)"
/>
<input
value={tokenUri}
onChange={(e) => setTokenUri(e.target.value)}
placeholder="Token URI (ipfs://... or https://...)"
/>
<button onClick={handleMint} disabled={isPending || !contractAddress || !tokenId}>
{isPending ? "Minting..." : "Mint NFT"}
</button>
{isConfirming && <p>Waiting for confirmation...</p>}
{isSuccess && <p>NFT minted! Tx: {hash}</p>}
{error && <p>Error: {error.message}</p>}
</div>
);
}Mint NFT — Learn
What is an NFT?
An NFT (Non-Fungible Token) is a token where each unit is unique and not interchangeable. A fungible token (like ETH or USDC) is interchangeable — one ETH equals any other ETH. An NFT is different: token #1 is not the same as token #2, even in the same collection.
The "non-fungible" property is enforced by the token standard:
- EVM: ERC-721 gives each token a unique
tokenIdand tracks ownership withownerOf(tokenId) - Solana: A mint account with 0 decimals and a max supply of 1 — physically impossible to have more than one
How ERC-721 works
// Core ERC-721 storage
mapping(uint256 => address) private _owners; // tokenId → owner
mapping(address => uint256) private _balances; // owner → count
mapping(uint256 => address) private _tokenApprovals; // tokenId → approved spender
function safeMint(address to, uint256 tokenId, string memory uri) external onlyOwner {
_mint(to, tokenId);
_setTokenURI(tokenId, uri); // stores the metadata URI
}
function ownerOf(uint256 tokenId) public view returns (address) {
return _owners[tokenId]; // O(1) lookup
}
The ERC-721 standard (EIP-721) defines these required functions:
balanceOf(address)— how many NFTs does this address own?ownerOf(tokenId)— who owns this specific token?transferFrom(from, to, tokenId)— transfer ownershipapprove(to, tokenId)— approve someone to transfer a specific tokentokenURI(tokenId)— get the metadata URI for a token
How Solana NFTs work
On Solana, an NFT is defined by three constraints on an SPL mint account:
- 0 decimals — can't have 0.5 of the token
- Max supply of 1 — mint authority is used exactly once, then (optionally) revoked
- Metaplex metadata — a separate Program Derived Address account stores name, symbol, URI
NFT identity: mint account (PublicKey)
↓
Metaplex Metadata PDA: name, symbol, uri, creators, royalties
↓
Token account (ATA): holds the 1 token, owned by the NFT holder's wallet
The Metaplex Token Metadata Program is the de facto standard for Solana NFT metadata.
Metadata standards
Both EVM and Solana store NFT metadata off-chain (usually IPFS or Arweave) and reference it with a URI.
ERC-721 Metadata JSON
{
"name": "My NFT #1",
"description": "A description of this NFT",
"image": "ipfs://QmXxx.../image.png",
"attributes": [
{ "trait_type": "Background", "value": "Blue" },
{ "trait_type": "Eyes", "value": "Laser" }
]
}
Metaplex JSON Standard
{
"name": "My NFT #1",
"symbol": "MNFT",
"description": "A description of this NFT",
"seller_fee_basis_points": 500,
"image": "https://arweave.net/xxx/image.png",
"attributes": [
{ "trait_type": "Background", "value": "Blue" },
{ "trait_type": "Eyes", "value": "Laser" }
],
"properties": {
"files": [
{ "uri": "https://arweave.net/xxx/image.png", "type": "image/png" }
],
"creators": [{ "address": "YourWallet...", "share": 100 }]
}
}
IPFS vs Arweave
| IPFS | Arweave | |
|---|---|---|
| Cost | Free to pin (via Pinata/NFT.storage); pay for persistence | One-time fee (~$0.01/MB) |
| Permanence | Data can disappear if unpinned | Permanent by design |
| URI format | ipfs://QmHash... | ar://TxId... or https://arweave.net/TxId |
| Speed | Variable gateway speed | Fast via arweave.net |
For production NFT collections, Arweave is preferred because the data is permanent. IPFS data can disappear if nobody pins it.
The minting flow
EVM full flow
- Deploy an ERC-721 contract (once per collection)
- Call
safeMint(to, tokenId, tokenURI)for each NFT - The contract stores
_owners[tokenId] = to - Wallets and marketplaces read
tokenURI(tokenId)to display the NFT
Solana full flow
- Generate a new keypair for each NFT mint
- Create the mint account (SystemProgram.createAccount)
- Initialize as SPL mint with 0 decimals (createInitializeMintInstruction)
- Create the recipient's Associated Token Account
- Mint exactly 1 token (createMintToInstruction)
- (Optional but recommended) Attach Metaplex metadata via
createCreateMetadataAccountV3Instruction - (Optional) Revoke mint authority — proves no more can ever be minted
Revoking mint authority
After minting your collection, you can make it immutable by revoking the mint authority:
// Solana — after minting all tokens
await setAuthority(
connection,
payer,
mintPubkey,
currentAuthority,
AuthorityType.MintTokens,
null, // ★ null = revoke
);
On EVM, you renounce ownership of the contract (or remove the minter role), preventing any future mints.