Metaplex NFTs
The previous page showed two ways to price a token — a shared orderbook or an AMM pool — but both assumed the thing being traded was fungible: one USDC is exactly as good as any other USDC. This page asks the opposite question. What if you want a token where every unit is unique and un-swappable — a piece of art, a game item, a ticket, a domain name? That is a non-fungible token, an NFT.
The tempting guess, coming from Ethereum, is that an NFT needs its own kind of contract. It does not. This page’s whole point is that an NFT on Solana is not new machinery at all: it is the SPL Token program you already met, used in a very particular way, with one extra account bolted on for a name and a picture. We keep pulling the same lever — one shared program, many external accounts — one notch further.
An NFT is a token with supply 1
Section titled “An NFT is a token with supply 1”Start from what the SPL Token program already gives you: a mint account describes a token, and token accounts hold balances of it. A mint has two fields that, together, decide whether the token is fungible:
decimals— how finely one unit divides. USDC hasdecimals = 6, so the smallest amount is 0.000001 USDC.supply— how many base units exist.
To make a token non-fungible, you pick the only combination that makes “a fraction of one” and “more than one” both impossible:
FUNGIBLE TOKEN (e.g. USDC) NON-FUNGIBLE TOKEN (an NFT) ────────────────────────── ─────────────────────────── mint { mint { decimals: 6, ── divisible decimals: 0, ── indivisible supply: 10^15 ── billions supply: 1, ── exactly one } } + mint authority burned (no one can ever mint a 2nd)Set decimals = 0 and there are no fractions — you cannot hold half. Set supply = 1 and exactly one indivisible unit exists in the entire universe. Then burn the mint authority (set it to None) so no one can ever mint a second one. That is it. There is now exactly one token, it lives in exactly one token account at a time, and whoever’s wallet owns that token account “owns the NFT.” The transfer that moves it is the same SPL Token transfer instruction that moves USDC — the runtime cannot even tell the difference.
This is the first-principles definition worth memorizing: an NFT is an SPL mint with decimals = 0, supply = 1, and a burned mint authority. Everything else is decoration.
The missing piece: where do the name and picture live?
Section titled “The missing piece: where do the name and picture live?”A bare mint with supply 1 proves scarcity, but it is anonymous. The mint is a 32-byte address and two integers; it has nowhere to put “this is Bored Ape #4271” or a link to the image. The SPL Token program deliberately does not know about names or art — it is a ledger, not an art gallery, and jamming media fields into it would bloat every fungible mint too.
So the ecosystem does what it always does: it adds a second shared program that attaches the human-facing data as another account. That program is Metaplex Token Metadata.
Metaplex as the standard layer
Section titled “Metaplex as the standard layer”Metaplex Token Metadata is a single, deployed-once program (like SPL Token, one program for the whole chain). Given a mint, it creates one companion account — the metadata account — holding the fields a marketplace or wallet needs:
- name and symbol — e.g. “Mad Lad #1234”, “MAD”.
- uri — a link to an off-chain JSON file describing the asset (crucially, not the image itself; a pointer).
- creators — a list of addresses with a share and a “verified” flag.
- seller_fee_basis_points — the intended royalty (more on that below).
- collection — an optional pointer to a parent “collection” mint.
The metadata account is not stored at a random address. It lives at a Program-Derived Address that anyone can recompute from the mint:
metadata PDA = find_program_address( seeds = [ b"metadata", TOKEN_METADATA_PROGRAM_ID, mint_pubkey ], program = TOKEN_METADATA_PROGRAM_ID, )Because the address is derived from the mint, no lookup table is needed: given any NFT’s mint, a wallet computes exactly where its metadata must be and reads it. This is the same PDA trick the Associated Token Account uses to find your balance — one deterministic address per (mint, purpose), so the whole graph is navigable from a single key.
ONE NFT = THREE ACCOUNTS, TWO SHARED PROGRAMS ─────────────────────────────────────────────
SPL Token program Metaplex Token Metadata program (shared, deployed once) (shared, deployed once) │ │ │ owns/operates │ owns/operates ┌─────┴───────┐ ┌─────────────────┐ ┌───┴──────────────┐ │ mint │ │ token account │ │ metadata (PDA) │ │ decimals:0 │ │ owner: alice │ │ name, symbol │ │ supply: 1 │ │ amount: 1 │ │ uri ───────────►│──┐ └─────────────┘ └─────────────────┘ │ creators[] │ │ │ royalty bps │ │ └──────────────────┘ │ ▼ off-chain JSON + image (Arweave/IPFS)The URI pattern: on-chain pointer, off-chain asset
Section titled “The URI pattern: on-chain pointer, off-chain asset”Look again at that uri field. It does not contain the image, and it does not even contain the full description — it contains a URL. That URL points to a small JSON document (the “off-chain metadata”) that itself contains the name again, attributes/traits, and a further image URL pointing at the actual PNG or GIF.
ON CHAIN (metadata account) OFF CHAIN (fetched over HTTP) ─────────────────────────── ───────────────────────────── uri: "https://arweave.net/AbC..." ──► { "name": "Mad Lad #1234", "image": "https://arweave.net/XyZ...", "attributes": [ {"trait_type":"Hat","value":"Crown"} ] } │ └──► the actual image bytesWhy this indirection instead of storing the image on chain? Cost and physics. On-chain storage is paid for by rent — you pre-fund an account with enough lamports to cover the bytes it occupies, forever. A single 2 MB image would cost orders of magnitude more than the ~few hundred bytes of a metadata account, and multiplied across a 10,000-item collection it becomes absurd. The ledger is a consensus-replicated database on every validator; you do not want a global supercomputer storing your JPEGs. So the on-chain part stays tiny (a pointer plus a name), and the heavy bytes live wherever the URL points.
That choice has a trust-and-permanence cost, and you must name it:
- If the
uripoints at a plain web server (https://mysite.com/1234.json), the NFT is only as permanent as that server’s hosting bill. Let the domain lapse and the “asset” is a dead link — the on-chain token still exists, but it points at nothing. - Arweave aims to fix permanence by charging a one-time fee for (targeted) permanent storage — pay once, stored “forever” by an endowment model. As of this writing (2024) it is a common choice for Solana collections that care about permanence, though which storage backend a given collection uses varies.
- IPFS gives you content addressing: the URL is a hash of the content (
ipfs://Qm...), so the data cannot be silently swapped — but IPFS only keeps data as long as some node pins it, so permanence still depends on someone paying to pin.
The tension is unavoidable: putting the image on chain is prohibitively expensive; putting it off chain reintroduces the very off-chain trust assumption a blockchain was supposed to remove. NFTs live with a pointer on chain and hope the pointee stays alive. Naming that trade — what exactly does the chain guarantee, and what does it merely point at? — is the honest way to reason about any NFT.
Why reuse SPL Token instead of a bespoke standard?
Section titled “Why reuse SPL Token instead of a bespoke standard?”Ethereum answered “how do I represent a unique token?” with ERC-721: a new interface, and in practice a new contract deployed per collection. Every 721 collection is its own program at its own address, with its own ownerOf and tokenURI functions and its own storage of who owns token #N.
Solana answered the same question by not inventing a token standard at all:
ERC-721 (Ethereum) Metaplex NFT (Solana) ────────────────── ───────────────────── deploy a new 721 contract per reuse the ONE SPL Token program collection (new bytecode) (a mint with supply 1) ownership stored inside that ownership is a normal SPL token contract's mapping account balance of 1 tokenURI() lives in that contract metadata is a Metaplex account (PDA) N collections = N contracts to N collections = 0 new programs; audit & trust just more mints + metadata accountsThis is the exact same argument the part overview made for fungible tokens, applied one notch tighter. Because an NFT is just an SPL mint, every wallet and marketplace already knows how to hold and transfer it on day zero — the transfer path is the shared SPL Token transfer, not per-collection code. New collections add data (mints, metadata accounts), never new code. There is no per-collection contract to audit, and no risk that collection #501 has a subtly broken transferFrom. The trade, as always: you inherit the shared programs’ fixed behavior. You cannot bake arbitrary logic into “the token” the way a 721 contract can override its own transfer — you get SPL Token’s rules, plus whatever Metaplex or Token-2022 extensions standardize.
Under the hood — collections, creators, and royalties as metadata
Section titled “Under the hood — collections, creators, and royalties as metadata”Three things people think of as “NFT features” are, on Solana, just fields on the metadata account — conventions, not enforced primitives:
- Collections. A “collection” (the 10,000 apes as a set) is itself a mint + metadata account. Each item’s metadata carries a
collectionfield pointing at the collection mint, and averifiedflag. The flag is only trustworthy because setting it requires a signature from the collection’s authority — so a scammer can claim membership but cannot verify it. Verification is a signature check, not a vibe. - Creators. The
creatorsarray lists addresses, each with a percentage share and averifiedbool. A creator is “verified” only once that address has signed the metadata — again, a signature turns a claim into a fact. Marketplaces show the verified-creator badge; unverified creator strings are just text anyone can write. - Royalties.
seller_fee_basis_pointsrecords an intended resale royalty (e.g.500= 5%). Here is the honest part: on-chain royalty enforcement is contested and evolving. The field is a statement of intent; whether a marketplace actually pays it is a policy choice, not something the SPL Token transfer forces. As of 2024 the ecosystem has swung through honor-system royalties, marketplaces that stopped enforcing them, and stronger mechanisms — Metaplex’s programmable NFTs (pNFTs) and transfer-hook approaches under Token-2022 — that try to make a transfer fail unless the royalty is paid. Treat any specific claim about “royalties are enforced now” as a dated snapshot to re-verify; the mechanism (a field vs a transfer that can reject) is the durable idea.
The bill this runs up — and the next page
Section titled “The bill this runs up — and the next page”Everything above works, and it is genuinely elegant: no new code per collection, full composability, a clean pointer to off-chain art. But count the accounts. Every ordinary NFT needs its own mint account, its own metadata account, and a token account for each holder — and each of those accounts pays rent to occupy space on every validator forever.
ONE regular NFT ≈ mint + metadata + (a token account per holder) ────────────────────────────────────────────────────────────────── 1,000,000-item collection = 1,000,000 mints + 1,000,000 metadata accounts + ≥ 1,000,000 token accounts each rent-funded, each occupying ledger space forever ⇒ many millions of accounts, real SOL in rent, just to *exist*.At the scale of a game handing out a million in-item drops, or a loyalty program minting per-user badges, “a few accounts each” becomes millions of accounts and a rent bill that dwarfs the value of any single item. The shared-program trick removed the code explosion (no contract per collection), but it did nothing about the account explosion — and on a chain where state lives in rent-funded accounts on every validator, that is the binding constraint.
That is precisely the problem the next page attacks. If the expensive part is one account per item, what if you kept only a single Merkle root on chain and proved each item against it, moving the millions of leaves off-ledger? Continue with Compressed NFTs & State Compression.
The architect’s lens
Section titled “The architect’s lens”- Why does it exist? Because people want unique, ownable digital objects, and Solana wanted to provide them without inventing a whole new token standard or a contract-per-collection model. Metaplex Token Metadata exists to attach the human-facing data (name, art pointer, creators, royalty) that the deliberately-minimal SPL Token program refuses to carry.
- What problem does it solve? It turns “represent a unique asset” into “make an SPL mint with
supply = 1and hang a standard metadata account off it,” so every wallet and marketplace understands every NFT on day zero, with zero new code per collection. - What are the trade-offs? You inherit the shared programs’ fixed behavior (you cannot bake arbitrary logic into the token itself), the real asset lives off chain behind a URI (permanence and trust depend on Arweave/IPFS/hosting), and royalty enforcement is not free — a field is a promise, not a mechanism.
- When should I avoid it? When you need millions of items (per-item mint + metadata + token accounts make ordinary NFTs too expensive — reach for state compression), or when the “asset” is really fungible (then it is just an SPL token, no NFT machinery needed).
- What breaks if I remove it? Drop Metaplex and a supply-1 mint is an anonymous 32-byte address — provably scarce but nameless and pictureless, with no shared place for wallets and marketplaces to read a name, an image URL, a creator, or a collection. The scarcity survives; the identity and the ecosystem-wide interoperability do not.
Check your understanding
Section titled “Check your understanding”- Give the first-principles definition: what three properties of an SPL mint make it a non-fungible token, and why does each one matter?
- A supply-1 mint proves scarcity but is anonymous. What does the Metaplex metadata account add, and how does a wallet find that account given only the mint?
- Explain the URI pattern: what is stored on chain, what is stored off chain, and what trust/permanence cost does that split introduce? Contrast a plain web URL with Arweave and IPFS.
- Contrast Solana’s NFT approach with Ethereum’s ERC-721. What multiplies as you launch more collections on each chain, and what is the trade-off Solana accepts in exchange?
- Why does an ordinary NFT collection become expensive at million-item scale, and how does that cost motivate the next page — given that the shared-program model already removed the code explosion?
Show answers
decimals = 0(indivisible — no fractions, you can only hold 0 or 1),supply = 1(exactly one unit exists in the whole chain), and a burned mint authority (set toNone, so no one can ever mint a second). Together they guarantee a unique, transferable, indivisible object — and the transfer that moves it is the same SPL Token transfer that moves any fungible token.- Metaplex Token Metadata attaches a metadata account holding name, symbol, a
uripointing at off-chain JSON/image, creators, royalty basis points, and an optional collection pointer. The wallet finds it deterministically: the metadata account lives at a PDA derived from seeds["metadata", metadata_program_id, mint], so given any mint you can recompute exactly where its metadata must be — no lookup table needed. - On chain: a tiny metadata account with a
uripointer (plus name/symbol/creators/royalty). Off chain: the JSON document and the actual image bytes. The cost is trust and permanence — the chain only guarantees the pointer, not the pointee. A plain web URL dies when the hosting lapses; Arweave charges once for (aimed-to-be) permanent storage; IPFS is content-addressed (the URL is a hash, so data can’t be silently swapped) but only persists while some node pins it. - ERC-721 typically deploys a new contract per collection — so N collections = N programs to audit, with ownership and
tokenURIliving inside each. Solana reuses the one SPL Token program (a mint with supply 1) plus the shared Metaplex program — so N collections = zero new programs, only more mints and metadata accounts, and every collection is transferable/holdable on day zero. The trade-off: you inherit the shared programs’ fixed behavior and cannot bake arbitrary per-collection logic into the token itself. - Every ordinary NFT needs its own mint + own metadata account + a token account per holder, and each account pays rent to occupy space on every validator forever. A million-item collection means millions of rent-funded accounts — real SOL just to exist. The shared-program model removed the code explosion (no contract per collection) but not this account explosion, which motivates compressed NFTs & state compression: keep a single Merkle root on chain and prove each item against it, moving the millions of leaves off-ledger.