> ## Documentation Index
> Fetch the complete documentation index at: https://docs.immutable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ERC-721 (NFTs)

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?

<AccordionGroup>
  <Accordion title="True Ownership" icon="key">
    Players own their assets on-chain. They can trade, sell, or use them across any compatible game or marketplace.
  </Accordion>

  <Accordion title="Provable Scarcity" icon="gem">
    Fixed supply is enforced by the blockchain. Players can verify rarity and authenticity of any item.
  </Accordion>

  <Accordion title="Secondary Revenue" icon="rotate">
    Earn royalties on every resale. Set your percentage at deployment and receive automatic payments.
  </Accordion>

  <Accordion title="Interoperability" icon="link">
    Standard ERC-721 works with all Ethereum wallets, marketplaces, and tools out of the box.
  </Accordion>
</AccordionGroup>

## 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](/docs/products/asset-contracts/minting-api)**: Server-side minting at scale

## Deploy via Hub

<Card title="Deploy Contracts" icon="file-contract" href="/docs/products/hub/deploy-contracts">
  Deploy ERC-721 contracts in Hub
</Card>

## Metadata

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

### Required Fields

```json theme={null}
{
  "name": "Legendary Sword",
  "description": "A powerful weapon forged in ancient fires",
  "image": "https://assets.example.com/sword.png"
}
```

| Field         | Required | Description                               |
| ------------- | -------- | ----------------------------------------- |
| `name`        | Yes      | Item's display name                       |
| `description` | Yes      | Short description of the item             |
| `image`       | Yes      | URL to item image (PNG, WEBP recommended) |

### Optional Fields

| Field              | Description                                       |
| ------------------ | ------------------------------------------------- |
| `animation_url`    | URL to video, audio, or 3D asset (MP4, WEBM, GLB) |
| `external_url`     | Link to item details on your website              |
| `background_color` | Six-character hex (without #) for item background |
| `attributes`       | Array of traits for filtering and display         |

### Attributes

Use `attributes` to define item properties visible in marketplaces:

```json theme={null}
{
  "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 Type       | Use For            | Example Value | How It Displays       |
| ------------------ | ------------------ | ------------- | --------------------- |
| (none)             | Text values        | `"Legendary"` | Rarity: Legendary     |
| `number`           | Numeric stats      | `150`         | Damage: 150           |
| `boost_percentage` | Percentage bonuses | `10`          | Fire Resistance: +10% |
| `boost_number`     | Numeric bonuses    | `5`           | Speed: +5             |
| `date`             | Timestamps         | `1672531200`  | Minted: Jan 1, 2023   |

<Tip>
  **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.
</Tip>

### Metadata Hosting Options

#### Immutable Hosted (Recommended)

Include metadata directly in mint requests. Immutable stores and serves it automatically:

```json theme={null}
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 }]
    }
  }]
}
```

| Environment | Base URL                            |
| ----------- | ----------------------------------- |
| **Testnet** | `https://api.sandbox.immutable.com` |
| **Mainnet** | `https://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](/docs/products/asset-contracts/minting-api)

Enable Minting API access in [Hub](/docs/products/hub/deploy-contracts), then:

```bash theme={null}
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:

```typescript theme={null}
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

```solidity theme={null}
// 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

<CardGroup cols={2}>
  <Card title="Primary Sales" icon="tag" href="/docs/products/asset-contracts/primary-sales">
    Sell NFTs directly to players
  </Card>

  <Card title="Crafting" icon="wand-magic-sparkles" href="/docs/products/asset-contracts/crafting">
    Let players combine items
  </Card>
</CardGroup>
