“Mint an NFT” sounds like a single operation.
From a user's perspective, it often looks like this:
Connect Wallet
↓
Click Mint
↓
Confirm Transaction
↓
NFT Appears in Wallet
Underneath that button, several systems have to agree on the same state.
The image is not necessarily stored on the blockchain. The metadata may live on IPFS or another storage layer. The smart contract owns the token ID and ownership state. The wallet signs the transaction. An indexer eventually makes the new asset visible to the application.
A simplified architecture looks like this:
NFT Application
│
▼
Mint UI
│
▼
Wallet
│
signed tx
▼
Smart Contract
/ \
/ \
▼ ▼
Token State Metadata URI
│ │
▼ ▼
Blockchain IPFS / Storage
│
▼
Events
│
▼
Indexer
│
▼
Marketplace / UI
The interesting part is that the NFT itself is only one piece of the system.
Start With What the NFT Actually Represents
Before writing a contract, you need to define the asset.
An NFT could represent:
A unique collectible
A game character
A ticket
A membership credential
A digital certificate
A real-world asset
A composable digital asset
That decision affects the contract design, metadata structure, token standard, and transfer model.
For example, a collection of unique digital artworks has a different structure from a game inventory containing thousands of identical consumable items.
This is why token standard selection should happen before implementation.
ERC-721 vs ERC-1155
For a simple one-of-a-kind asset, ERC-721 is usually the easier mental model.
Each token has its own ID:
Collection
│
├── Token #1
├── Token #2
├── Token #3
└── Token #4
ERC-1155 takes a different approach.
A single contract can represent multiple token types and quantities:
Contract
│
├── Item #1001 → 5,000 copies
├── Item #1002 → 2,000 copies
└── Item #1003 → 1 copy
This makes ERC-1155 useful for applications such as gaming, where an inventory may contain both unique assets and high-volume items.
The choice is therefore not about which standard is “better”.
It depends on the asset model.
A useful rule is:
Unique asset
↓
ERC-721
Multiple / batch-oriented assets
↓
ERC-1155
NFTs that own other assets
↓
Composable design such as ERC-998
The source article also identifies ERC-721, ERC-1155, ERC-998 and TRC-721 as common options, with the choice depending on asset structure, blockchain and marketplace compatibility.
The Image Is Not the NFT
This is one of the most common misconceptions.
Suppose an NFT displays:
Golden Sword
Attack: 95
Rarity: Legendary
Image: sword.png
Those properties are generally represented through metadata.
A simplified metadata document could look like:
{
"name": "Golden Sword",
"description": "Legendary in-game weapon",
"image": "ipfs://...",
"attributes": [
{
"trait_type": "Rarity",
"value": "Legendary"
},
{
"trait_type": "Attack",
"value": 95
}
]
}
The smart contract does not necessarily store this entire JSON object.
Instead, the token commonly exposes a metadata URI:
NFT #1842
│
▼
tokenURI(1842)
│
▼
ipfs://metadata...
This keeps large assets and metadata outside the blockchain while allowing the token to reference them.
The source architecture similarly separates metadata from the token itself, with metadata commonly stored off-chain using systems such as IPFS or Arweave and referenced from the token.
Why Put Metadata Off-Chain?
Storing an image directly on-chain is possible, but it can be expensive and impractical for many collections.
A common architecture is:
Blockchain
│
│ tokenURI
▼
Metadata URI
│
▼
Decentralized Storage
│
┌─────────┴─────────┐
▼ ▼
Metadata Image
│ │
└─────────┬─────────┘
▼
NFT Interface
The important distinction is:
The blockchain proves token ownership.
It does not automatically guarantee that every external asset referenced by the token will remain available forever.
That is why storage architecture is part of NFT engineering, not just a frontend concern.
The Smart Contract Defines Ownership
A minimal ERC-721 contract might look like:
contract GameAsset is ERC721 {
uint256 private nextTokenId;
function mint(address to)
external
returns (uint256)
{
uint256 tokenId = nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}
}
The important operation here is not the image or metadata.
It is:
_mint(to, tokenId)
The blockchain now records that:
Token #1842
↓
Owner = 0xA91...
That ownership state can then be independently verified.
A marketplace does not need to trust your database to determine who owns the NFT.
It can query the contract or indexed blockchain data.
What Actually Happens During Minting?
Let's follow a mint transaction.
- The application prepares the transaction
The frontend knows:
Contract address
Token ID / mint parameters
Recipient
Metadata configuration
It prepares a contract call.
mint(recipient)
- The wallet signs it
The user confirms the transaction through their wallet.
The wallet signs the transaction with the user's private key.
The application does not receive the private key.
User
│
▼
Wallet
│
│ signed transaction
▼
Blockchain Network
- The transaction reaches the smart contract
The blockchain executes the contract.
The contract checks whatever rules have been implemented:
Can this caller mint?
│
├── No → revert
│
└── Yes
↓
Create token
↓
Assign owner
↓
Emit event
- Ownership is written to blockchain state
After successful execution:
Token #1842
│
├── Owner → 0xA91...
└── Metadata → ipfs://...
The transaction receipt and event logs provide evidence of the state transition.
Minting Is Also a Security Boundary
The simple contract above has a major problem:
Who is allowed to call mint()?
If anyone can call it, anyone can create unlimited tokens.
Production contracts therefore need access control.
Conceptually:
function mint(address to)
external
onlyRole(MINTER_ROLE)
{
...
}
Other controls may include:
Maximum supply
Per-wallet limits
Pausing
Signature-based mint authorization
Payment validation
Allow lists
Mint phases
Upgrade permissions
This is why smart contract development is one of the security-sensitive stages of NFT development. The source article specifically identifies smart contract security, access control, and transaction integrity as major risks.
The Event Is Just as Important as the State
Suppose the contract emits:
event NFTMinted(
address indexed owner,
uint256 indexed tokenId
);
The event can then be consumed by off-chain infrastructure:
Smart Contract
│
│ NFTMinted
▼
Blockchain Node
│
▼
Event Listener
│
▼
Indexer
│
▼
Application Database
│
▼
Marketplace / UI
This is how the application can discover that a new NFT has been minted without constantly scanning every transaction manually.
The blockchain remains the source of truth.
The indexer becomes the query layer.
That distinction matters.
Why an Indexer Is Necessary
Imagine a marketplace displaying:
Latest NFTs
Trending Collections
Owner History
Sales History
Floor Price
Trading Volume
Querying raw blockchain state for every page load quickly becomes impractical.
An indexer can transform blockchain events into application-friendly data:
Blockchain Events
│
▼
Indexer
│
├── NFT ownership
├── Transfers
├── Sales
├── Collections
└── Metadata
│
▼
Database
│
▼
Frontend
But indexing introduces its own engineering problems.
You need to handle:
Duplicate events
RPC failures
Delayed confirmations
Chain reorganizations
Reprocessing
Idempotency
Metadata synchronization
So the NFT does not become “visible everywhere” immediately after the user clicks Mint.
There is a state transition between transaction submitted and application synchronized.
The Marketplace Does Not Own the NFT
A marketplace typically facilitates trading.
The underlying NFT remains governed by its smart contract.
A simplified secondary sale looks like:
Seller
│
│ list NFT
▼
Marketplace
│
│ buyer purchases
▼
Smart Contract
│
├── transfer NFT
├── transfer payment
└── emit events
│
▼
Indexer
│
▼
Updated marketplace
This is an important architectural boundary.
The marketplace database should not become the authority for ownership.
The contract should enforce the actual asset transfer.
Royalties Are a Contract Design Problem
NFT projects often want creators to receive a percentage from secondary sales.
That sounds straightforward:
Sale = $1,000
Royalty = 5%
Creator = $50
Seller = $950
But royalty behavior is not uniformly enforced across every marketplace and trading environment.
This means royalty requirements need to be considered at both levels:
Smart Contract
+
Marketplace Integration
A technically correct royalty implementation does not guarantee identical behavior across every external marketplace.
This is one reason marketplace compatibility should be considered early rather than after the contract is deployed.
Immutable vs Upgradeable Contracts
Another architectural decision is whether the contract should be immutable or upgradeable.
An immutable contract provides a strong guarantee:
Deployed
↓
Logic stays fixed
An upgradeable architecture introduces:
Proxy
│
▼
Implementation
│
├── V1
├── V2
└── V3
Upgradeability can be useful when requirements are expected to evolve.
But it also introduces additional trust and security assumptions.
For example:
Who controls upgrades?
Can the administrator change ownership logic?
Can metadata behavior change?
Is the upgrade mechanism secured?
What happens if the upgrade key is compromised?
There is no universal answer.
The important thing is to make the trust model explicit before deployment.
Gas Is an Architecture Constraint
Every on-chain operation has a cost.
The cost can be affected by:
Blockchain network
Contract complexity
Storage operations
Minting model
Batch size
Current network demand
For large collections, this can influence whether you use:
One transaction per NFT
or a batch-oriented approach.
ERC-1155 can be particularly useful when many token types or quantities need to be managed efficiently.
This is why blockchain selection should happen before implementation rather than being treated as an infrastructure detail at the end.
The Architecture I Would Start With
For a typical NFT platform, I would separate responsibilities like this:
User
│
▼
Web / Mobile App
│
┌───────────┴───────────┐
▼ ▼
Wallet Backend
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Metadata Indexer Marketplace
Service │
│ │
▼ ▼
IPFS / Storage Application DB
│
│
Blockchain
│
┌──────────┴──────────┐
▼ ▼
NFT Contract Token Contract
The responsibilities stay clear:
Wallet → authorization
Frontend → user interaction
Backend → application logic
Smart contract → ownership + on-chain rules
Storage → metadata + assets
Indexer → blockchain read model
Marketplace → trading experience
Blockchain → settlement + ownership truth
This separation also makes it easier to replace individual components without redesigning the entire system.
The Hard Part Isn't Minting
Writing a basic NFT contract is not particularly difficult.
Writing a basic NFT contract is not particularly difficult.
The difficult questions appear around the contract:
What exactly does the token represent?
Where does the metadata live?
Who can mint?
Can the contract be upgraded?
How is ownership indexed?
How are royalties handled?
What happens when the RPC provider fails?
How does the application reconcile pending transactions?
Which marketplaces support the required behavior?
What happens if metadata becomes unavailable?
In our blockchain work at SotaTek, these are often the questions that require more architectural consideration than the minting logic itself. The contract may only take a few functions to implement, but making the entire system reliable means thinking through storage, indexing, wallet flows, marketplace compatibility, and the boundary between on-chain and off-chain state.
These are architecture questions, not Solidity syntax questions.
That is why a production NFT implementation needs to consider smart contracts, metadata, storage, wallets, indexing, marketplaces, security, and blockchain infrastructure as one system.
Final Takeaway
An NFT is not an image stored on a blockchain.
It is better understood as a combination of:
Token Standard
+
Smart Contract
+
Ownership State
+
Metadata
+
Off-chain Storage
+
Wallet
+
Blockchain
+
Indexing
The blockchain provides verifiable ownership and transaction history.
The smart contract defines the rules.
The metadata describes the asset.
The wallet authorizes transactions.
The indexer makes blockchain state usable by applications.
And the marketplace connects that infrastructure to users.
Once you understand these boundaries, NFT development becomes less about “minting a token” and more about designing a system where on-chain truth and off-chain application state remain consistent.
For a broader overview of NFT token standards, development stages, use cases, and the cost factors involved in taking an NFT project from contract deployment to marketplace integration, this NFT token development guide provides additional context.
Top comments (0)