Contracts
Items contract
The rare_items Move package: game items as kiosk objects, studio classes and kinds, signed claim and state vouchers, royalties, events and error codes.
The rare_items package turns game items into Sui objects. Each class of items is its own type Item<C>, with one marker type C per class. Studios mint items from their server or hand players signed vouchers, and the items trade on any Sui marketplace because their TransferPolicy uses only Mysten's standard kiosk rules. The package depends only on the Sui framework and Mysten's kiosk rules.
Published on mainnet. No game has registered item classes yet.
0xda09fa9db4a54d4b0c337fb7ee41311f4679a7f6049e58372a4951968a32f73c0xd0b9551e6559ce3a93e4778bb37decf3951cf30c358f6fe38f4bac358a9be097On this page, PACKAGE_ID is the package id, CONFIG_ID is the shared Config, a Class<C> is the shared object for one class, POL is that class's shared TransferPolicy<Item<C>> and 0x6 is the Clock.
Modules
Shared objects and caps
Each class is its own shared object, so claims in different classes never wait on each other.
Locked and unlocked classes
Royalties are paid on every marketplace sale of either class. A purchase that skips the royalty cannot confirm its TransferRequest.
Studio calls
public fun create<C>(cfg: &mut Config, publisher: &Publisher, game: String, name: String, royalty_bps: u16, royalty_min_mist: u64, lock: bool, issuer: vector<u8>, ctx: &mut TxContext): (Class<C>, ItemCap<C>)
public fun share<C>(class: Class<C>)
public fun add_kind<C>(cap: &ItemCap<C>, class: &mut Class<C>, name: String, description: String, image_url: String, max_supply: u64): u64
public fun set_issuer<C>(cap: &ItemCap<C>, class: &mut Class<C>, pk: vector<u8>)
public fun set_paused<C>(cap: &ItemCap<C>, class: &mut Class<C>, paused: bool)
public fun withdraw_royalties<C>(cap: &ItemCap<C>, class: &Class<C>, policy: &mut TransferPolicy<Item<C>>, ctx: &mut TxContext): Coin<SUI>createneeds thePublisherof the module that definesC(201), and eachCgets one class (103). The marker is a one-module package the studio publishes from its wallet. It shares the policy and returns the class unshared with itsItemCap, so kinds can be added in the same PTB beforeshare. Royaltybpsis at most 10,000 (203); 0 bps and a 0 floor add no royalty rule.issueris 32 bytes, or empty for no vouchers (202).add_kindreturns kind ids in order from 0.max_supply = 0is open supply.set_issuersets or rotates the game key. The old key stops working in the same transaction. Empty turns vouchers off; studio mints still work.set_pausedstops claims, studio mints and state updates. Trading and burning are never paused.withdraw_royaltiestakes every royalty collected in the class's policy.mint::mint<C>(cap, class, kind, keys, vals): Item<C>mints from the studio's own PTB, for airdrops. For a locked class, the same PTB must lock the item in a kiosk, or it is not tradable under the policy.
Text limits: game slug 1 to 96 bytes, names 1 to 128, description 1,024, URL 512 (204). Attribute keys are 1 to 64 bytes, values up to 512 (601), at most 64 per item (602). A key given twice keeps the last value.
Player calls
public fun claim<C>(class: &mut Class<C>, policy: &TransferPolicy<Item<C>>, kiosk: &mut Kiosk, kcap: &KioskOwnerCap, kind: u64, keys: vector<String>, vals: vector<String>, nonce: u64, expires_ms: u64, sig: vector<u8>, clock: &Clock, ctx: &mut TxContext): ID
public fun claim_fresh<C>(class: &mut Class<C>, policy: &TransferPolicy<Item<C>>, kind: u64, keys: vector<String>, vals: vector<String>, nonce: u64, expires_ms: u64, sig: vector<u8>, clock: &Clock, ctx: &mut TxContext): ID
public fun apply<C>(class: &Class<C>, item: &mut Item<C>, keys: vector<String>, vals: vector<String>, nonce: u64, expires_ms: u64, sig: vector<u8>, clock: &Clock)
public fun burn<C>(class: &mut Class<C>, item: Item<C>, ctx: &TxContext)mint::claimputs the item in the sender's kiosk, locked or placed as the class says.kcapmust open that kiosk (301).mint::claim_freshcreates a shared kiosk, claims into it and sends the kiosk cap to the sender.state::applytakes the item by mutable reference. In a PTB:kiosk::borrow_val(works for locked and placed items, not while listed),apply,kiosk::return_val. It writes only the keys it is given and bumpsrev.mint::burndestroys an item of an unlocked class (302for locked,303for the wrong class). Take it out of the kiosk first.
The player is always the transaction sender. A sponsor can pay the gas.
Voucher checks, in order
- The class is current and not paused (
207,208). - Claims only: the policy is the class's (
206). - A game key is set (
501). clock < expires_ms(502).- The signature is the game key's over the message (
503). - Replay guard. Claims: the (player, nonce) pair is unused in this class (
211), then recorded on the class. State: the message names the item's currentrev, so a voucher works once and only in order. - The kind exists (
209) and is not sold out (210), and the attributes pass the limits above.
Voucher format
The studio's game server signs these raw bytes with ed25519 (no hashing, 64-byte signature):
claim: "RARE_ITEM_V1" || class id (32) || player (32) || kind (u64 LE) || bcs(keys) || bcs(vals) || nonce (u64 LE) || expires_ms (u64 LE)
state: "RARE_ITEM_STATE_V1" || item id (32) || rev (u64 LE) || bcs(keys) || bcs(vals) || nonce (u64 LE) || expires_ms (u64 LE)bcs(keys) is the BCS of vector<String>: a ULEB128 count, then each string as a ULEB128 byte length and its UTF-8 bytes. voucher::claim_message and voucher::state_message return the same bytes on chain. Object ids differ between networks, so a voucher can never be replayed on another network.
Display
Held inside the class, so it cannot be edited outside it: name, description, image_url, thumbnail_url (same as the image), project_url (https://rare.market/games/<game>), creator (the class name), attributes (from attrs), serial.
Views
config:version,paused,class_count,class_of<C>(): Option<ID>class:cap_class,game,name,issuer,policy,lock,paused,version,minted,burned,kind_count,kind(id)with thekind_*getters,nonce_used(player, nonce),displayitem:class,kind,serial,name,description,image_url,attrs,attr(key),revmint:source_mint()= 0,source_claim()= 1
Fees
The package charges no fees. Studios set their own royalty per class, collected in the class's policy.
Events
All events are in PACKAGE_ID::events.
Sales are indexed from kiosk and marketplace events, as for any kiosk item.
Error codes
Example
Create a class with two kinds and share it, from the studio's wallet. MARKER is the studio's marker type, such as 0x...::hat::HAT.
import { Transaction } from "@mysten/sui/transactions";
const tx = new Transaction();
const [cls, cap] = tx.moveCall({
target: `${PACKAGE_ID}::class::create`,
typeArguments: [MARKER],
arguments: [
tx.object(CONFIG_ID),
tx.object(PUBLISHER_ID), // the marker package's Publisher
tx.pure.string(gameSlug),
tx.pure.string(gameName),
tx.pure.u16(500), // 5% royalty
tx.pure.u64(0), // no floor
tx.pure.bool(true), // locked (cosmetics)
tx.pure.vector("u8", gameKey), // 32-byte ed25519 public key
],
});
for (const k of kinds) {
tx.moveCall({
target: `${PACKAGE_ID}::class::add_kind`,
typeArguments: [MARKER],
arguments: [cap, cls, tx.pure.string(k.name), tx.pure.string(k.description), tx.pure.string(k.imageUrl), tx.pure.u64(k.maxSupply)],
});
}
tx.moveCall({ target: `${PACKAGE_ID}::class::share`, typeArguments: [MARKER], arguments: [cls] });
tx.transferObjects([cap], studioAddress);