NFTs, Digital Assets, Kiosk & Transfer Policies
On Sui, any key + store object can be an NFT — there is no mandatory interface.
34.1 NFTs on Sui
On Sui, any key + store object can be an NFT — there is no mandatory interface. Combine it with Display for rendering and Kiosk for trading.
module nft::hero;
use std::string::String;
use sui::event;
public struct Hero has key, store {
id: UID,
name: String,
image_url: String,
power: u64,
}
public struct MintCap has key { id: UID, minted: u64, max_supply: u64 }
public struct HeroMinted has copy, drop { id: ID, to: address }
const ESoldOut: u64 = 0;
fun init(ctx: &mut TxContext) {
transfer::transfer(MintCap { id: object::new(ctx), minted: 0, max_supply: 10_000 }, ctx.sender());
}
public fun mint(cap: &mut MintCap, name: String, image_url: String, to: address, ctx: &mut TxContext) {
assert!(cap.minted < cap.max_supply, ESoldOut);
cap.minted = cap.minted + 1;
let hero = Hero { id: object::new(ctx), name, image_url, power: 1 };
event::emit(HeroMinted { id: object::id(&hero), to });
transfer::public_transfer(hero, to);
}
public fun burn(hero: Hero) {
let Hero { id, name: _, image_url: _, power: _ } = hero;
id.delete();
}34.2 Kiosk (Sui)
Kiosk (sui::kiosk) is a shared object owned by a user that stores, lists, and sells assets while enforcing the creator's transfer policy (e.g., royalties). Marketplaces integrate with kiosks rather than taking custody of NFTs.
use sui::kiosk::{Self, Kiosk, KioskOwnerCap};
use sui::transfer_policy::{Self, TransferPolicy};
use sui::coin::Coin;
use sui::sui::SUI;
/// Seller: create a kiosk, place and list an item.
public fun open_and_list(hero: Hero, price: u64, ctx: &mut TxContext) {
let (mut kiosk, cap) = kiosk::new(ctx);
let id = object::id(&hero);
kiosk.place(&cap, hero);
kiosk.list<Hero>(&cap, id, price);
transfer::public_share_object(kiosk);
transfer::public_transfer(cap, ctx.sender());
}
/// Buyer: purchase, satisfy the policy, take the item.
public fun buy(
kiosk: &mut Kiosk,
policy: &TransferPolicy<Hero>,
id: ID,
payment: Coin<SUI>,
): Hero {
let (hero, request) = kiosk.purchase<Hero>(id, payment); // request is a hot potato
// ...satisfy rules here (e.g., pay royalty) which add receipts to `request`...
policy.confirm_request(request);
hero
}34.3 Transfer policies
The creator (holding the Publisher) creates a TransferPolicy<T>; every Kiosk purchase produces a TransferRequest<T> that must be confirmed against it:
Shared object listing required rules for T Lets the creator add/remove rules and withdraw collected fees
Hot potato from purchase; rules add receipts; confirm_request checks all receipts
Royalty, kiosk lock (item must stay in a kiosk), personal kiosk, floor price — reference implementations in Mysten's kiosk package
Place an item so it can never leave kiosks (enforced royalties)
fun init(otw: HERO, ctx: &mut TxContext) {
let publisher = sui::package::claim(otw, ctx);
let (policy, policy_cap) = transfer_policy::new<Hero>(&publisher, ctx);
transfer::public_share_object(policy);
transfer::public_transfer(policy_cap, ctx.sender());
transfer::public_transfer(publisher, ctx.sender());
}34.4 Aptos Digital Asset standard (Token Objects)
Aptos NFTs use aptos_token_objects (address 0x4), built on the Object framework: a collection object and token objects.
Aptos DA concept create_fixed_collection / create_unlimited_collection; name unique per creator
create_named_token (deterministic address), create (unique address)
Add your own resources to the token object (stats, equipment) using the object signer
Disable ungated transfer with TransferRef aptos_token_objects::royalty (marketplace-enforced by convention)
aptos_token_objects::aptos_token offers ready-made mint/burn/property maps
0x3::token (Token v1) — older standard
module my_addr::heroes {
use std::option;
use std::string::{String, utf8};
use aptos_token_objects::collection;
use aptos_token_objects::token;
use aptos_token_objects::royalty;
const COLLECTION: vector<u8> = b"Move Heroes";
fun init_module(creator: &signer) {
collection::create_fixed_collection(
creator,
utf8(b"Heroes from the Move course"),
10_000, // max supply
utf8(COLLECTION),
option::some(royalty::create(5, 100, @my_addr)), // 5% royalty
utf8(b"https://example.com/collection.json"),
);
}
public entry fun mint(creator: &signer, name: String, uri: String, to: address) {
let constructor_ref = token::create_named_token(
creator,
utf8(COLLECTION),
utf8(b"A Move hero"),
name,
option::none(), // royalty: inherit from collection
uri,
);
let transfer_ref = aptos_framework::object::generate_transfer_ref(&constructor_ref);
let linear = aptos_framework::object::generate_linear_transfer_ref(&transfer_ref);
a
…