Tenzro Testnet is live —request testnet TNZO
← Back to Tutorials
TokensIntermediate

Build an NFT Marketplace

Build a cross-VM NFT marketplace on Tenzro Network. You will create ERC-721 collections, mint NFTs with metadata URIs, list and query NFT information, transfer NFTs between users, register cross-VM pointers (EVM to SVM), and settle payments using TNZO escrow.

What We're Building

Prerequisites

  • Tenzro SDK installed (cargo add tenzro-sdk or npm install tenzro-sdk)
  • A funded wallet on testnet (100 TNZO from faucet)

Estimated time: 35 minutes

Step 1: Create an NFT Collection

Create a new ERC-721 collection registered in Tenzro's unified token registry. The registry tracks collections across all VMs (EVM, SVM, DAML):

Rust

use tenzro_sdk::{TenzroClient, config::SdkConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = SdkConfig::testnet();
    let client = TenzroClient::connect(config).await?;
    let nft = client.nft();

    // Create an ERC-721 collection
    let collection = nft.create_collection(
        "Tenzro Genesis",                           // name
        "TGEN",                                      // symbol
        "erc721",                                    // NFT standard
        "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4", // creator address
    ).await?;

    println!("Collection created!");
    println!("  Collection ID: {}", collection.collection_id);
    println!("  Name: {}", collection.name);
    println!("  Symbol: {}", collection.symbol);
    println!("  EVM Address: {}", collection.evm_address);

    Ok(())
}

TypeScript

import { TenzroClient, TESTNET_CONFIG } from "tenzro-sdk";

const client = new TenzroClient(TESTNET_CONFIG);
const nft = client.nft;

// Create an ERC-721 collection
const collection = await nft.createCollection(
  "Tenzro Genesis",                             // name
  "TGEN",                                        // symbol
  "erc721",                                      // NFT standard
  "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",   // creator address
);

console.log("Collection created!");
console.log("  Collection ID:", collection.collection_id);
console.log("  Name:", collection.name);
console.log("  EVM Address:", collection.evm_address);

Supported NFT Types

Tenzro supports three NFT standards: erc721 (unique tokens), erc1155 (semi-fungible, multiple editions), and metaplex (Solana-native via the SVM adapter). All three register in the same unified token registry.

Step 2: Mint NFTs

Mint individual NFTs into the collection. Each NFT has a unique token ID and a metadata URI (typically IPFS):

Rust

// Mint NFT #1
let nft1 = nft.mint_nft(
    &collection.collection_id,                      // collection
    "1",                                             // token ID
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",   // recipient
    "ipfs://QmYwAPJzv5CZsnA6p6ERhisExYDiS3N5xEkfXRsS5y1dMw/1.json",
).await?;
println!("Minted NFT #1: tx {}", nft1.tx_hash);

// Mint NFT #2
let nft2 = nft.mint_nft(
    &collection.collection_id,
    "2",
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
    "ipfs://QmYwAPJzv5CZsnA6p6ERhisExYDiS3N5xEkfXRsS5y1dMw/2.json",
).await?;
println!("Minted NFT #2: tx {}", nft2.tx_hash);

// Mint NFT #3 to a different address
let nft3 = nft.mint_nft(
    &collection.collection_id,
    "3",
    "0xdAC17F958D2ee523a2206206994597C13D831ec7",
    "ipfs://QmYwAPJzv5CZsnA6p6ERhisExYDiS3N5xEkfXRsS5y1dMw/3.json",
).await?;
println!("Minted NFT #3: tx {}", nft3.tx_hash);

TypeScript

// Mint NFT #1
const nft1 = await nft.mintNft(
  collection.collection_id,
  "1",
  "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
  "ipfs://QmYwAPJzv5CZsnA6p6ERhisExYDiS3N5xEkfXRsS5y1dMw/1.json",
);
console.log("Minted NFT #1:", nft1.tx_hash);

// Mint NFT #2
const nft2 = await nft.mintNft(
  collection.collection_id,
  "2",
  "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
  "ipfs://QmYwAPJzv5CZsnA6p6ERhisExYDiS3N5xEkfXRsS5y1dMw/2.json",
);
console.log("Minted NFT #2:", nft2.tx_hash);

Step 3: Query NFT Information

List collections and look up individual NFTs:

Rust

// List all collections
let collections = nft.list_collections(None).await?;
println!("Total collections: {}", collections.len());
for c in &collections {
    println!("  {} ({}) - {} - EVM: {}", c.name, c.symbol, c.nft_type, c.evm_address);
}

// Get specific NFT info
let info = nft.get_nft_info(
    &collection.collection_id,
    "1",
).await?;
println!("NFT #1:");
println!("  Name: {}", info.name);
println!("  Owner: {}", info.owner.unwrap_or_default());
println!("  Metadata: {}", info.metadata_uri.unwrap_or_default());

TypeScript

// List all collections
const collections = await nft.listCollections();
console.log("Total collections:", collections.length);
for (const c of collections) {
  console.log(`  ${c.name} (${c.symbol}) - ${c.nft_type}`);
}

// Get specific NFT info
const info = await nft.getNftInfo(collection.collection_id, "1");
console.log("NFT #1:");
console.log("  Name:", info.name);
console.log("  Owner:", info.owner);
console.log("  Metadata:", info.metadata_uri);

Step 4: Transfer NFTs

Transfer an NFT from one address to another:

Rust

// Transfer NFT #2 to a buyer
let transfer = nft.transfer_nft(
    &collection.collection_id,
    "2",                                             // token ID
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",   // from
    "0xdAC17F958D2ee523a2206206994597C13D831ec7",   // to
).await?;

println!("NFT transferred!");
println!("  Tx hash: {}", transfer.tx_hash);
println!("  From: {}", transfer.from);
println!("  To: {}", transfer.to);
println!("  Status: {}", transfer.status);

TypeScript

// Transfer NFT #2 to a buyer
const transfer = await nft.transferNft(
  collection.collection_id,
  "2",                                              // token ID
  "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",    // from
  "0xdAC17F958D2ee523a2206206994597C13D831ec7",    // to
);

console.log("NFT transferred!");
console.log("  Tx hash:", transfer.tx_hash);
console.log("  From:", transfer.from);
console.log("  To:", transfer.to);

Step 5: Cross-VM Pointers

Register a cross-VM pointer so the same NFT collection can be accessed from both EVM and SVM. This uses the Sei V2 pointer model -- no bridging, no wrapping, same underlying data:

Rust

// Register SVM pointer for the collection
let pointer = nft.register_pointer(
    &collection.collection_id,
    "svm",    // target VM
).await?;

println!("Cross-VM pointer registered!");
println!("  Target VM: {}", pointer.target_vm);
println!("  SVM Address: {}", pointer.target_address);
println!("  Status: {}", pointer.status);

// Now Solana programs can access the same NFT via the SVM address
// No bridging required -- both addresses point to the same state

TypeScript

// Register SVM pointer for the collection
const pointer = await nft.registerPointer(
  collection.collection_id,
  "svm",  // target VM
);

console.log("Cross-VM pointer registered!");
console.log("  SVM Address:", pointer.target_address);

Cross-VM Pointer Model

Tenzro uses the Sei V2 pointer model for cross-VM interoperability. When you register a pointer, the SVM address maps directly to the same underlying EVM state. Transfers on either VM are reflected on both. There is no bridge risk and no liquidity fragmentation. This works for both fungible tokens (wTNZO) and NFT collections.

Step 6: NFT Purchase with Escrow

For trustless NFT purchases, use Tenzro's escrow system. The buyer locks TNZO in escrow, the seller transfers the NFT, and the escrow releases payment automatically:

Rust

let settlement = client.settlement();

// Step 1: Buyer creates escrow with payment
let escrow_id = settlement.create_escrow(
    "0xdAC17F958D2ee523a2206206994597C13D831ec7",  // seller (payee)
    5_000_000_000_000_000_000,                       // 5 TNZO price
    "TNZO",                                          // asset
    "both_signatures",                                // release condition
).await?;
println!("Escrow created: {}", escrow_id);

// Step 2: Seller transfers the NFT to the buyer
let transfer = nft.transfer_nft(
    &collection.collection_id,
    "3",                                             // token ID
    "0xdAC17F958D2ee523a2206206994597C13D831ec7",   // seller (from)
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",   // buyer (to)
).await?;
println!("NFT transferred to buyer: {}", transfer.tx_hash);

// Step 3: Release escrow payment to seller
let receipt = settlement.release_escrow(&escrow_id).await?;
println!("Payment released to seller: {}", receipt.settlement_hash);

TypeScript

// Step 1: Buyer creates escrow
const escrowId = await client.settlement.createEscrow(
  "0xdAC17F958D2ee523a2206206994597C13D831ec7",  // seller
  5_000_000_000_000_000_000n,                      // 5 TNZO
  "TNZO",
  "both_signatures",
);
console.log("Escrow created:", escrowId);

// Step 2: Seller transfers NFT
const transfer = await nft.transferNft(
  collection.collection_id,
  "3",
  "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
);
console.log("NFT transferred:", transfer.tx_hash);

// Step 3: Release payment
const receipt = await client.settlement.releaseEscrow(escrowId);
console.log("Payment released:", receipt.settlement_hash);

Step 7: Complete Marketplace Example

Here is a complete marketplace listing and purchase flow:

import { TenzroClient, TESTNET_CONFIG } from "tenzro-sdk";

interface Listing {
  collectionId: string;
  tokenId: string;
  seller: string;
  price: bigint;
}

class NftMarketplace {
  private client: TenzroClient;
  private listings: Map<string, Listing> = new Map();

  constructor(client: TenzroClient) {
    this.client = client;
  }

  // List an NFT for sale
  async listForSale(collectionId: string, tokenId: string, seller: string, price: bigint) {
    const key = `${collectionId}:${tokenId}`;
    this.listings.set(key, { collectionId, tokenId, seller, price });
    console.log(`Listed NFT ${tokenId} for ${Number(price) / 1e18} TNZO`);
  }

  // Browse listings
  getListings(): Listing[] {
    return Array.from(this.listings.values());
  }

  // Purchase an NFT
  async purchase(collectionId: string, tokenId: string, buyer: string) {
    const key = `${collectionId}:${tokenId}`;
    const listing = this.listings.get(key);
    if (!listing) throw new Error("Listing not found");

    // 1. Create escrow
    const escrowId = await this.client.settlement.createEscrow(
      listing.seller,
      listing.price,
      "TNZO",
      "both_signatures",
    );

    // 2. Transfer NFT
    await this.client.nft.transferNft(
      collectionId,
      tokenId,
      listing.seller,
      buyer,
    );

    // 3. Release payment
    const receipt = await this.client.settlement.releaseEscrow(escrowId);

    // 4. Remove listing
    this.listings.delete(key);

    return { escrowId, receipt };
  }
}

// Usage
async function main() {
  const client = new TenzroClient(TESTNET_CONFIG);
  const marketplace = new NftMarketplace(client);

  // Create collection and mint
  const collection = await client.nft.createCollection(
    "Tenzro Art", "TART", "erc721",
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
  );

  await client.nft.mintNft(
    collection.collection_id, "1",
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
    "ipfs://QmXyz.../1.json",
  );

  // List for sale
  await marketplace.listForSale(
    collection.collection_id, "1",
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
    5_000_000_000_000_000_000n, // 5 TNZO
  );

  // Purchase
  const result = await marketplace.purchase(
    collection.collection_id, "1",
    "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  );
  console.log("Purchase complete:", result.receipt.settlement_hash);
}

main().catch(console.error);

What You Learned

Next Steps

Build More with Tenzro

Explore the full NFT and token capabilities in the SDK documentation.