Skip to main content

NFT

NFTs (Non-Fungible Tokens) in Chia are singleton-based assets that can hold metadata, have ownership controls, and support royalties. Each NFT has a unique launcher ID that serves as its permanent identifier.

Overview

Chia NFTs are built on the singleton pattern, meaning:

  • Each NFT has a unique identity that persists across spends
  • Only one instance of an NFT can exist at any time
  • The NFT's launcher ID remains constant even as the coin ID changes

NFTs consist of multiple layers:

LayerPurpose
SingletonEnsures uniqueness and provides the launcher ID
NFT StateHolds metadata URIs and hash
NFT OwnershipControls transfer and royalty logic
Inner (p2)Defines who can spend the NFT

Key Types

TypeDescription
Nft<T>An NFT with its metadata and inner puzzle info
NftInfoLauncher ID, metadata, owner info, royalties
NftMetadataURIs for data, metadata, and license
ProofSingleton lineage proof for validation

Minting NFTs

To mint a new NFT, use the Nft::mint function:

use chia_wallet_sdk::prelude::*;

let ctx = &mut SpendContext::new();
let p2 = StandardLayer::new(public_key);

// Define the NFT metadata
let metadata = NftMetadata {
data_uris: vec!["https://example.com/image.png".to_string()],
data_hash: Some(image_hash),
metadata_uris: vec!["https://example.com/metadata.json".to_string()],
metadata_hash: Some(metadata_hash),
license_uris: vec![],
license_hash: None,
edition_number: Some(1),
edition_total: Some(100),
};

// Mint the NFT
let (mint_conditions, nft) = Nft::mint(
ctx,
parent_coin_id, // Coin being spent to mint
owner_puzzle_hash, // Initial owner
Some(royalty_puzzle_hash), // Royalty recipient (or None)
royalty_percentage, // Royalty in basis points (e.g., 300 = 3%)
metadata,
owner_puzzle_hash, // Hint for discovery
)?;

// Spend the parent coin with mint conditions
p2.spend(ctx, parent_coin, mint_conditions)?;

// The NFT's permanent identifier
let launcher_id = nft.info.launcher_id;

Transferring NFTs

To transfer an NFT to a new owner:

use chia_wallet_sdk::prelude::*;

let ctx = &mut SpendContext::new();

// Transfer to a new owner
let new_nft = nft.transfer(
ctx,
&p2, // Current owner's p2 layer
new_owner_puzzle_hash, // New owner
Conditions::new(), // Additional conditions (fees, etc.)
)?;

let coin_spends = ctx.take();

For more control over the transfer, use transfer_with_metadata:

let new_nft = nft.transfer_with_metadata(
ctx,
&p2,
new_owner_puzzle_hash,
updated_metadata, // Can update metadata during transfer
Conditions::new(),
)?;

Spending NFTs with Custom Conditions

For complex operations beyond simple transfers:

let ctx = &mut SpendContext::new();

// Build conditions for the NFT spend
let conditions = Conditions::new()
.create_coin_announcement(b"nft_action")
.reserve_fee(fee_amount);

// Spend with custom conditions
let new_nft = nft.spend(
ctx,
p2.spend_with_conditions(ctx, conditions)?,
)?;

Royalties

NFTs support royalties that are enforced during trades. The royalty is specified as a percentage in basis points (1/100th of a percent):

Basis PointsPercentage
1001%
2502.5%
5005%
100010%
// 5% royalty
let royalty_basis_points = 500;

let (mint_conditions, nft) = Nft::mint(
ctx,
parent_coin_id,
owner_puzzle_hash,
Some(royalty_puzzle_hash), // Where royalties are paid
royalty_basis_points,
metadata,
owner_puzzle_hash,
)?;
info

Royalties are enforced through the offer system. Direct transfers don't automatically pay royalties - they're applied when NFTs are traded via offers.

Parsing NFTs

When you need to reconstruct an NFT from blockchain data:

// Parse an NFT from a parent coin spend
let nft = Nft::parse_child(
ctx,
parent_coin_spend,
child_coin,
)?;

// Or parse directly from puzzle/solution
let parsed = Nft::<NftMetadata>::parse(
&ctx.allocator(),
puzzle_ptr,
solution_ptr,
)?;

NFT Ownership Layer

The ownership layer tracks:

  • Current owner (puzzle hash)
  • Transfer program (for trading/offers)
  • Royalty information

When building custom NFT interactions, be aware that the ownership layer requires proper handling for the NFT to remain valid.

Complete Example

Here's a full example of minting and transferring an NFT:

use chia_wallet_sdk::prelude::*;

fn mint_and_transfer_nft(
minter_coin: Coin,
minter_public_key: PublicKey,
recipient_puzzle_hash: Bytes32,
metadata: NftMetadata,
) -> Result<(Bytes32, Vec<CoinSpend>), DriverError> {
let ctx = &mut SpendContext::new();
let p2 = StandardLayer::new(minter_public_key);
let minter_puzzle_hash = StandardLayer::puzzle_hash(minter_public_key);

// Step 1: Mint the NFT
let (mint_conditions, nft) = Nft::mint(
ctx,
minter_coin.coin_id(),
minter_puzzle_hash,
None, // No royalties for this example
0,
metadata,
minter_puzzle_hash,
)?;

p2.spend(ctx, minter_coin, mint_conditions)?;
let launcher_id = nft.info.launcher_id;

// Step 2: Transfer to recipient
let _new_nft = nft.transfer(
ctx,
&p2,
recipient_puzzle_hash,
Conditions::new(),
)?;

Ok((launcher_id, ctx.take()))
}

API Reference

For the complete NFT API, see docs.rs.