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
- Create an NFT collection (ERC-721) via the unified token registry
- Mint NFTs with IPFS metadata URIs
- List collections and query individual NFTs
- Transfer NFTs between users
- Register cross-VM pointers so the same collection is accessible from EVM and SVM
- Settle NFT purchases using TNZO escrow for trustless exchange
Prerequisites
- Tenzro SDK installed (
cargo add tenzro-sdkornpm 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 stateTypeScript
// 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
- NFT collections -- creating ERC-721 collections in the unified token registry
- Minting -- minting NFTs with metadata URIs (IPFS)
- Querying -- listing collections and looking up individual NFTs
- Transfers -- transferring NFTs between addresses
- Cross-VM pointers -- making collections accessible from EVM and SVM simultaneously
- Escrow settlement -- trustless NFT purchases with TNZO escrow
Next Steps
- Build a Compliance Token -- add KYC and transfer restrictions
- VM Workflows -- deep dive into cross-VM execution
- Build an AI Payment Agent -- automate NFT purchases with agents
Build More with Tenzro
Explore the full NFT and token capabilities in the SDK documentation.