Skip to main content
ERC-721 tokens are unique, non-fungible assets—each has a distinct ID and metadata. Use them for characters, weapons, land, collectibles, or any asset that should be one-of-a-kind.

Why Use ERC-721?

Players own their assets on-chain. They can trade, sell, or use them across any compatible game or marketplace.
Fixed supply is enforced by the blockchain. Players can verify rarity and authenticity of any item.
Earn royalties on every resale. Set your percentage at deployment and receive automatic payments.
Standard ERC-721 works with all Ethereum wallets, marketplaces, and tools out of the box.

Features

  • Unique Token IDs: Each token has distinct metadata
  • EIP-2981 Royalties: Automatic royalty enforcement
  • Operator Allowlist: Control which contracts can transfer
  • Batch Minting: Create multiple tokens in one transaction
  • Minting API: Server-side minting at scale

Deploy via Hub

Deploy Contracts

Deploy ERC-721 contracts in Hub

Metadata

Each NFT needs metadata following the ERC-721 standard. Metadata defines how items appear in wallets, marketplaces, and your game.

Required Fields

{
  "name": "Legendary Sword",
  "description": "A powerful weapon forged in ancient fires",
  "image": "https://assets.example.com/sword.png"
}
FieldRequiredDescription
nameYesItem’s display name
descriptionYesShort description of the item
imageYesURL to item image (PNG, WEBP recommended)

Optional Fields

FieldDescription
animation_urlURL to video, audio, or 3D asset (MP4, WEBM, GLB)
external_urlLink to item details on your website
background_colorSix-character hex (without #) for item background
attributesArray of traits for filtering and display

Attributes

Use attributes to define item properties visible in marketplaces:
{
  "name": "Flame Blade",
  "description": "A legendary sword imbued with fire magic",
  "image": "https://assets.example.com/flame-blade.png",
  "attributes": [
    { "trait_type": "Rarity", "value": "Legendary" },
    { "trait_type": "Damage", "display_type": "number", "value": 150 },
    { "trait_type": "Element", "value": "Fire" },
    { "trait_type": "Level Requirement", "display_type": "number", "value": 25 }
  ]
}

Attribute Display Types

Control how attributes appear in wallets and marketplaces:
Display TypeUse ForExample ValueHow It Displays
(none)Text values"Legendary"Rarity: Legendary
numberNumeric stats150Damage: 150
boost_percentagePercentage bonuses10Fire Resistance: +10%
boost_numberNumeric bonuses5Speed: +5
dateTimestamps1672531200Minted: Jan 1, 2023
Naming consistency: Use the same trait_type across all items. “Rarity” everywhere, not “Rarity” for some items and “Tier” for others. This improves marketplace filtering.

Metadata Hosting Options

Include metadata directly in mint requests. Immutable stores and serves it automatically:
POST {baseURL}/v1/chains/{chain_name}/collections/{contract_address}/nfts/mint-requests
{
  "assets": [{
    "owner_address": "0xRecipient",
    "token_id": "1",
    "metadata": {
      "name": "Epic Sword",
      "image": "https://assets.example.com/sword.png",
      "attributes": [{ "trait_type": "Damage", "value": 100 }]
    }
  }]
}
EnvironmentBase URL
Testnethttps://api.sandbox.immutable.com
Mainnethttps://api.immutable.com
Pros:
  • No hosting infrastructure required
  • Automatic IPFS pinning for permanence
  • Included in Minting API workflow
Cons:
  • Metadata becomes immutable after minting
  • Images still need external hosting

IPFS (Decentralized)

Upload metadata JSON to IPFS for permanent, decentralized storage: Pros:
  • Fully decentralized and permanent
  • Content-addressed (tamper-proof)
  • No ongoing hosting costs
Cons:
  • Requires IPFS setup or pinning service (Pinata, NFT.Storage)
  • Metadata cannot be updated

Self-Hosted

Host metadata on your own servers. Set baseURI during contract deployment: Pros:
  • Full control over metadata
  • Can update metadata after minting
  • Dynamic attributes possible
Cons:
  • Hosting infrastructure required
  • Centralization (server downtime affects metadata)
  • Must maintain availability

Minting

Via Minting API

Enable Minting API access in Hub, then:
curl -X POST '{baseURL}/v1/chains/{chain_name}/collections/{contract_address}/nfts/mint-requests' \
  -H 'x-immutable-api-key: YOUR_SECRET_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "assets": [{
      "owner_address": "0xRecipient",
      "token_id": "1",
      "metadata": {
        "name": "Epic Sword",
        "image": "https://assets.example.com/sword.png",
        "attributes": [{ "trait_type": "Damage", "value": 100 }]
      }
    }]
  }'

Batch Minting

Mint up to 100 tokens per request:
const assets = Array.from({ length: 100 }, (_, i) => ({
  owner_address: recipientAddress,
  token_id: String(i + 1),
  metadata: {
    name: `Item #${i + 1}`,
    image: `https://assets.example.com/${i + 1}.png`,
  },
}));

await fetch(`${API_URL}/collections/${contract}/nfts/mint-requests`, {
  method: 'POST',
  headers: { 'x-immutable-api-key': secretKey },
  body: JSON.stringify({ assets }),
});

Deploy via Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@imtbl/contracts/contracts/token/erc721/preset/ImmutableERC721.sol";

contract MyGameNFT is ImmutableERC721 {
    constructor(
        address owner,
        string memory name,
        string memory symbol,
        string memory baseURI,
        string memory contractURI,
        address operatorAllowlist,
        address royaltyReceiver,
        uint96 royaltyFeeNumerator
    ) ImmutableERC721(
        owner, name, symbol, baseURI, contractURI,
        operatorAllowlist, royaltyReceiver, royaltyFeeNumerator
    ) {}
}

Next Steps