Skip to content
⌘K

Contracts

Marketplace contract

The market Move package behind Rare Marketplace: listings, offers, offer pools, swaps, raffles, bundles, fees, events and PTB examples.

The market package runs trading on Rare Marketplace. It covers fixed-price listings for kiosk and wallet items, item, collection and trait offers, offer pools, item-for-item swaps, raffles, bundles and the crate shop. It depends on the Sui framework and on Mysten's kiosk rules package (royalty_rule, kiosk_lock_rule, personal_kiosk_rule, floor_price_rule).

Not yet live on mainnet The package id appears here once the package is published.

On this page, PACKAGE_ID is the latest package id, T is the item type, and every amount is in MIST. Shared objects are passed as normal object inputs: the SDK resolves initialSharedVersion.

Modules

ModuleWhat it does
configThe shared Config: fee rates, treasury, pause switch and version.
sale_kioskListings of kiosk items through an exclusive PurchaseCap.
sale_escrowListings of wallet items held in escrow (types without a TransferPolicy).
offerSingle item, collection and trait (Merkle set) offers.
offer_poolOne SUI balance backing up to 128 offers.
barterItem and SUI swaps between two parties.
raffleTicketed raffles drawn with sui::random.
bundleSeveral items sold as one lot, with bids and an optional sealed reserve.
crate_shopFixed-price crate purchases paid to the treasury.
courierKiosk extension that lets the market deliver items into your kiosk.
clearingRoyalty quotes and the helper that settles standard transfer policy rules.
merkleThe hashing used by trait offers.
eventsEvery event the package emits.

Shared objects and caps

ObjectSharingKey fields
config::Configshared, onetreasury, rates {bundle_bps, raffle_bps, swap_flat, swap_bps, offer_bps, listing_bps}, queued, paused, version
config::ConfigCapownedFee and treasury changes, pause, crate prices. Handed to a multisig after publishing.
crate_shop::Shopshared, oneprices: vector<u64> (by tier), open, version
sale_kiosk::KioskOrder<T>shared, one per listingcap: PurchaseCap<T>, maker, ask, cut, kiosk, item, version
sale_escrow::EscrowOrder<T>shared, one per listingask, cut, item, maker, version. The item is held as a dynamic object field.
offer::Offer<T>shared, one per offerescrow, terms, maker, version
offer_pool::OfferPoolshared, one per poolfunds, held, members (at most 128), label, maker, version
barter::Dealshared, one per swapbid_sui, ask_sui, fee_bps, give, want, taker, expiry_epoch, maker_kiosk, maker, live
raffle::Raffle<T>shared, one per rafflepot, price, sold, min_tickets, max_tickets, ends_ms, winner, phase (0 selling, 1 drawn, 2 void), host, item
bundle::Bundleshared, one per bundleslots, ask, seal, maker, live
bundle::BundleBidshared, one per bidescrow, amount, fee, reserve, until_ms, dest, bundle, bidder

Listings, offers and bids are deleted when they close, so a listing that no longer exists is no longer live.

barter::Swap, barter::Unwind, bundle::Checkout, bundle::Handover and bundle::Unstack are hot potatoes: they have no abilities and must be consumed in the same PTB that created them.

Rules every call follows

  • Config gate. Every call that opens an order or fills one takes &Config and aborts with 101 while the market is paused, or 102 when this package version is retired. Cancels, refunds, payouts and unlistings never take Config, so neither a pause nor an upgrade can lock your items or SUI.
  • No contention. Buys and fills read Config by &. Each order, offer, pool, raffle, bundle and swap is its own shared object, so trades on different objects never wait on each other. The collection's TransferPolicy is taken &mut whenever royalty is paid, because royalty_rule::pay writes to it.
  • Transfer policies. Calls that move an item into a kiosk satisfy the four standard rules: they pay royalty, lock the item when the policy has a lock rule (otherwise they place it), and prove the personal kiosk and floor price rules. They return the TransferRequest<T>. You add receipts for any other rule and call 0x2::transfer_policy::confirm_request<T>(policy, request). The item is already in its destination when the request comes back.
  • Payments are &mut Coin<SUI>. Each call splits off exactly what it needs and you keep the rest. Calls that pay you return a Coin<SUI>.
  • Deliveries to someone who is not signing (offer fills, bundle bids, items a swap maker receives) go into a kiosk that has the courier extension, and only if that kiosk's owner is the recipient. Set this up once with courier::open or courier::enable.
  • Type names. kind in events and offer terms is type_name::with_defining_ids<T>(). In JSON it is the type string without the 0x prefix.

Listings

sale_kiosk: kiosk items

Use this for any type that has a TransferPolicy. The item stays in the seller's kiosk, listed exclusively through a PurchaseCap held by the order, so it can only leave through this module.

Move
public fun post<T: key + store>(
    cfg: &Config,
    kiosk: &mut Kiosk,
    kiosk_cap: &KioskOwnerCap,
    item: ID,
    ask: u64,
    ctx: &mut TxContext,
): ID

Lists item at ask and returns the order id. cut = ask * listing_bps / 10000, and the item is listed in the kiosk at ask - cut. Aborts: 211 when ask is 0.

Move
public fun buy<T: key + store>(
    order: KioskOrder<T>,
    cfg: &Config,
    kiosk: &mut Kiosk,          // the seller's kiosk
    dest: &mut Kiosk,           // the buyer's kiosk
    dest_cap: &KioskOwnerCap,
    policy: &mut TransferPolicy<T>,
    pay: &mut Coin<SUI>,
    ctx: &mut TxContext,
): TransferRequest<T>

Buys the item into the buyer's kiosk. Takes ask from pay, sends cut to the treasury, then pays royalty on ask - cut from the same coin. Confirm the returned request. Aborts: 212 wrong seller kiosk, 214 when pay is below ask, 901 when what is left cannot cover the royalty.

Move
public fun take<T: key + store>(
    order: KioskOrder<T>,
    cfg: &Config,
    kiosk: &mut Kiosk,
    pay: &mut Coin<SUI>,
    ctx: &mut TxContext,
): (T, TransferRequest<T>)

Raw purchase. You get the item and the request and resolve the policy yourself, for example with clearing::settle_into(policy, &mut request, item, kiosk, kiosk_cap, royalty_coin), then confirm.

Move
public fun reprice<T: key + store>(order: KioskOrder<T>, cfg: &Config, kiosk: &mut Kiosk, kiosk_cap: &KioskOwnerCap, ask: u64, ctx: &mut TxContext): ID
public fun pull<T: key + store>(order: KioskOrder<T>, kiosk: &mut Kiosk, kiosk_cap: &KioskOwnerCap)

reprice replaces the order with a new one and returns the new id. pull unlists and is never gated. Both need the kiosk's owner cap (212 wrong kiosk, 213 no access).

The buyer pays ask plus royalty. The treasury gets cut. The seller's kiosk profits get ask - cut, which the seller withdraws with 0x2::kiosk::withdraw.

sale_escrow: wallet items

For types without a TransferPolicy. This path cannot enforce a policy, so route any type that has one to sale_kiosk.

Move
public fun post<T: key + store>(cfg: &Config, item: T, ask: u64, ctx: &mut TxContext): ID
public fun take<T: key + store>(order: EscrowOrder<T>, cfg: &Config, pay: &mut Coin<SUI>, ctx: &mut TxContext): T
public fun reprice<T: key + store>(order: &mut EscrowOrder<T>, cfg: &Config, ask: u64, ctx: &mut TxContext)
public fun pull<T: key + store>(order: EscrowOrder<T>, ctx: &mut TxContext): T

take sends cut to the treasury and ask - cut to the maker's address, and returns the item. reprice and pull are maker only (202). pull is never gated. Aborts: 201 zero ask, 203 short payment.

Sweeps

There is no sweep function. A sweep is one PTB that calls sale_kiosk::buy (or sale_escrow::take) once per order and passes the same payment coin to every call. Each call splits off what it needs, so you do not need a SplitCoins per listing. Orders are independent objects, so a sweep only touches the orders and seller kiosks involved.

Offers

offer: item, collection and trait offers

Move
public fun place<T: key + store>(
    cfg: &Config,
    policy: &TransferPolicy<T>,
    scope: u8,                   // 0 any item of T, 1 one item, 2 any item in a Merkle set
    item: Option<ID>,            // scope 1 only
    root: Option<vector<u8>>,    // scope 2 only, 32 bytes
    each: u64,                   // price per unit, paid to the seller
    qty: u64,
    until_ms: Option<u64>,       // expiry, none for no expiry
    dest: Option<ID>,            // your courier-enabled kiosk, needed for kiosk fills
    pay: &mut Coin<SUI>,
    clock: &Clock,
    ctx: &mut TxContext,
): ID

Escrows qty * (each + fee + reserve), where fee = each * offer_bps / 10000 and reserve = clearing::royalty_due(policy, each). Scope 1 needs item and qty = 1. Aborts: 301 bad scope arguments, 302 zero price, 303 zero quantity, 304 expiry in the past, 305 short payment.

place_unruled<T> takes the same arguments without policy and sets reserve = 0. Kiosk fills of a type whose policy charges royalty then abort with 901. Wallet fills still work.

Move
public fun fill<T: key + store>(
    offer: &mut Offer<T>,
    cfg: &Config,
    seller_kiosk: &mut Kiosk,
    seller_cap: &KioskOwnerCap,
    item: ID,
    dest: &mut Kiosk,                // must be the offer's dest kiosk
    policy: &mut TransferPolicy<T>,
    proof: vector<vector<u8>>,       // empty unless scope 2
    clock: &Clock,
    ctx: &mut TxContext,
): (Coin<SUI>, TransferRequest<T>)

Sells a kiosk item into the offer. The item is bought out of the seller's kiosk at each and delivered into the maker's kiosk through courier. Royalty comes out of the offer's reserve and the unused reserve for that unit goes back to the maker. Returns the seller's proceeds (each, in full) and the request to confirm. Aborts: 307 no units left, 308 expired, 309 wrong item, 310 not in the set, 311 wrong type, 312 offer has no dest, 313 wrong dest, 802 the dest kiosk is not the maker's, 901 reserve short.

Move
public fun fill_owned<T: key + store>(offer: &mut Offer<T>, cfg: &Config, item: T, proof: vector<vector<u8>>, clock: &Clock, ctx: &mut TxContext): Coin<SUI>
public fun revoke<T>(offer: Offer<T>, ctx: &mut TxContext): Coin<SUI>
public fun retire<T>(offer: Offer<T>, clock: &Clock, ctx: &mut TxContext)

fill_owned sells a wallet item: it goes to the maker's address, the reserve goes back to the maker and the seller gets each. revoke is maker only (306) and refunds the whole remaining escrow, fees included. It is never gated. retire can be called by anyone once the offer is expired or fully filled (314 otherwise) and sends the escrow to the maker.

Views: terms, maker, escrowed, units_left, unit_price, unit_fee, unit_reserve, scope_any(), scope_item(), scope_set().

Trait offers (scope 2)

The bidder commits a Merkle root over the eligible item ids. The seller passes the proof for their item.

  • Leaf: blake2b256(0x00 || bcs(id)).
  • Node: blake2b256(0x01 || min(a, b) || max(a, b)), in lexicographic byte order.
  • Proof: the sibling hashes from leaf to root.

merkle::leaf, merkle::join and merkle::contains compute the same thing on chain.

offer_pool: many offers on one balance

One balance backs up to 128 offers. Each member reserves its full escrow from the pool, and held can never exceed funds.

Move
public fun new(cfg: &Config, label: Option<String>, ctx: &mut TxContext): OfferPool   // unshared
public fun share(pool: OfferPool)
public fun deposit(pool: &mut OfferPool, pay: Coin<SUI>, ctx: &mut TxContext)
public fun withdraw(pool: &mut OfferPool, amount: u64, ctx: &mut TxContext): Coin<SUI>
public fun add<T: key + store>(pool: &mut OfferPool, cfg: &Config, policy: &TransferPolicy<T>, scope: u8, item: Option<ID>, root: Option<vector<u8>>, each: u64, qty: u64, until_ms: Option<u64>, dest: Option<ID>, clock: &Clock, ctx: &mut TxContext): ID
public fun fill<T: key + store>(pool: &mut OfferPool, member: ID, cfg: &Config, seller_kiosk: &mut Kiosk, seller_cap: &KioskOwnerCap, item: ID, dest: &mut Kiosk, policy: &mut TransferPolicy<T>, proof: vector<vector<u8>>, clock: &Clock, ctx: &mut TxContext): (Coin<SUI>, TransferRequest<T>)
public fun fill_owned<T: key + store>(pool: &mut OfferPool, member: ID, cfg: &Config, item: T, proof: vector<vector<u8>>, clock: &Clock, ctx: &mut TxContext): Coin<SUI>
public fun drop_member(pool: &mut OfferPool, member: ID, ctx: &mut TxContext)
public fun prune(pool: &mut OfferPool, member: ID, clock: &Clock)
public fun close(pool: OfferPool, ctx: &mut TxContext): Coin<SUI>

Create the pool with new, deposit and add members in the same PTB, then share it. fill and fill_owned follow the same rules as the offer versions, and a filled-out member is deleted. add_unruled<T> adds a member with no royalty reserve. deposit, withdraw, relabel, drop_member and close are maker only (351). withdraw is limited to funds - held (352). prune lets anyone remove an expired or filled-out member (355). withdraw, drop_member and close are never gated. Aborts: 353 more than 128 members, 354 unknown member.

Swaps (barter)

A swap trades items and SUI between a maker and a taker, both sides in kiosks.

The maker builds the deal in one PTB:

Move
public fun draft(cfg: &Config, maker_kiosk: &mut Kiosk, kiosk_cap: &KioskOwnerCap, want: vector<ID>, ask_sui: u64, bid_sui: u64, taker: Option<address>, ttl_epochs: u64, pay: &mut Coin<SUI>, ctx: &mut TxContext): Deal
public fun pledge<T: key + store>(deal: &mut Deal, maker_kiosk: &mut Kiosk, kiosk_cap: &KioskOwnerCap, item: ID, ctx: &mut TxContext)
public fun publish(deal: Deal)

draft enables courier on the maker's kiosk, pays the swap fee (bid_sui * swap_bps / 10000 + swap_flat) and escrows bid_sui. The kiosk owner must be the sender (402). pledge adds each offered item (it must not be listed, 416). publish shares the deal (405 when nothing is offered). Up to 32 wanted items, with no duplicates (414, 415).

The taker settles in one PTB:

Move
public fun begin(deal: &mut Deal, cfg: &Config, pay: &mut Coin<SUI>, ctx: &mut TxContext): Swap
public fun hand<T: key + store>(swap: &mut Swap, deal: &mut Deal, maker_kiosk: &mut Kiosk, taker_kiosk: &mut Kiosk, taker_cap: &KioskOwnerCap, policy: &mut TransferPolicy<T>, item: ID, pay: &mut Coin<SUI>, ctx: &mut TxContext): TransferRequest<T>
public fun receive<T: key + store>(swap: &mut Swap, deal: &mut Deal, taker_kiosk: &mut Kiosk, taker_cap: &KioskOwnerCap, maker_kiosk: &mut Kiosk, policy: &mut TransferPolicy<T>, item: ID, pay: &mut Coin<SUI>, ctx: &mut TxContext): TransferRequest<T>
public fun finish(swap: Swap, deal: Deal, ctx: &mut TxContext)

begin checks the deal is live (404), not expired (406) and that the sender is the named taker, if any (407). It takes ask_sui from pay and sends ask_sui * fee_bps / 10000 of it to the treasury. Call hand once per offered item (it moves into the taker's kiosk) and receive once per wanted item (it moves into the maker's kiosk through courier, and the taker is paid an even part of bid_sui). Royalty for both comes from pay. finish aborts unless every item moved (411). Confirm every returned request. If a side has no items, its SUI goes straight to the other party.

To cancel, call unwind(&mut Deal), then restore<T> per pledged item, then unwound. The maker can always unwind. Anyone can once epoch > expiry_epoch (412 otherwise). The escrowed SUI goes back to the maker. Unwinding is never gated.

Raffles

Move
public fun open_kiosk<T: key + store>(cfg: &Config, policy: &TransferPolicy<T>, kiosk: &mut Kiosk, kiosk_cap: &KioskOwnerCap, item: ID, price: u64, min_tickets: u64, max_tickets: u64, ends_ms: u64, clock: &Clock, ctx: &mut TxContext): ID
public fun open_owned<T: key + store>(cfg: &Config, prize: T, price: u64, min_tickets: u64, max_tickets: u64, ends_ms: u64, clock: &Clock, ctx: &mut TxContext): ID
public fun buy<T>(raffle: &mut Raffle<T>, cfg: &Config, qty: u64, pay: &mut Coin<SUI>, clock: &Clock, ctx: &mut TxContext)
entry fun draw<T>(raffle: &mut Raffle<T>, rnd: &Random, clock: &Clock, ctx: &mut TxContext)

open_kiosk keeps the prize in the host's kiosk under an exclusive cap and binds the policy. open_owned escrows a wallet item. Limits: 1 <= min_tickets <= max_tickets <= 1,000,000 (502), price above 0 (501), and ends_ms in the future and within 30 days (503). buy works before ends_ms (505) up to max_tickets (506).

draw is an entry function that reads 0x8 (Random). Anyone can call it once the raffle has ended or sold out (509) with at least min_tickets sold (510). Because a call that uses Random cannot be followed by other commands, settlement is a separate transaction:

  • settle_kiosk<T>(raffle, host_kiosk, policy): TransferRequest<T>: anyone. The prize goes into a new kiosk owned by the winner (personal when the policy needs it). The policy must be the bound one (518).
  • claim_kiosk<T>(raffle, host_kiosk, policy, kiosk, kiosk_cap): TransferRequest<T>: winner only (512), into their own kiosk.
  • settle_owned<T>(raffle): anyone, for wallet prizes.

The pot pays the fee to the treasury, then (for kiosk prizes) royalty on the sale price, and the rest to the host. void cancels a raffle: the host before any sale, anyone after the end with too few tickets sold, or anyone when a drawn raffle has gone unsettled for 7 days. After void, anyone can call refund<T>(raffle, holder) to pay a ticket holder back in full and reclaim_kiosk<T> to return a kiosk prize, and dispose deletes the raffle once everything is returned. None of these are gated.

Bundles

The maker lists several kiosk items as one lot:

Move
public fun draft(cfg: &Config, ctx: &mut TxContext): Bundle
public fun stack<T: key + store>(bundle: &mut Bundle, kiosk: &mut Kiosk, kiosk_cap: &KioskOwnerCap, item: ID, ctx: &mut TxContext)
public fun publish(bundle: Bundle, ask: u64, seal: Option<vector<u8>>)

At most 32 items (603). The price is split evenly across the items and the last item takes the remainder. seal is an optional sealed reserve for bids: commitment(reserve, salt) = blake2b256(bcs(reserve) || salt). reseal changes it later. Unlisting is unstack, restore<T> per item, then unstacked, and is never gated.

A buyer takes the whole bundle in one PTB:

Move
public fun checkout(bundle: &mut Bundle, cfg: &Config, pay: &mut Coin<SUI>, _ctx: &mut TxContext): Checkout
public fun collect<T: key + store>(co: &mut Checkout, bundle: &mut Bundle, seller_kiosk: &mut Kiosk, dest: &mut Kiosk, dest_cap: &KioskOwnerCap, policy: &mut TransferPolicy<T>, item: ID, royalty: &mut Coin<SUI>, ctx: &mut TxContext): TransferRequest<T>
public fun checkout_done(co: Checkout, bundle: Bundle, cfg: &Config, ctx: &mut TxContext)

checkout takes ask + ask * bundle_bps / 10000 (607 if short). Call collect once per item, then checkout_done, which aborts unless every item was collected (610).

Bids escrow amount + amount * bundle_bps / 10000 + reserve, where reserve is the royalty budget, and name the bidder's courier-enabled kiosk as dest:

Move
public fun bid(bundle: &Bundle, cfg: &Config, amount: u64, reserve: u64, until_ms: u64, dest: ID, pay: &mut Coin<SUI>, clock: &Clock, ctx: &mut TxContext): ID
public fun withdraw_bid(b: BundleBid, ctx: &mut TxContext): Coin<SUI>
public fun lapse_bid(b: BundleBid, clock: &Clock, ctx: &mut TxContext)

withdraw_bid is bidder only and never gated. lapse_bid returns an expired bid to its bidder and anyone can call it. To accept, call accept(bundle, bid, cfg, reveal, salt, clock), then deliver<T> per item, then accepted. The maker can accept any bid without a reveal. Anyone else needs the reveal of the sealed reserve (615), and the bid must be at least the reserve (616). A reveal that lands on chain, even in a failed transaction, discloses the reserve, so reseal after using it.

Crate shop

Move
public fun buy(shop: &Shop, cfg: &Config, tier: u8, qty: u64, max_unit: u64, pay: &mut Coin<SUI>, ctx: &mut TxContext)

Pays price * qty to the treasury. max_unit protects you from a price change. Aborts: 701 bad tier, 702 zero quantity, 703 unit price above max_unit, 704 short payment, 705 shop closed. Default tiers are 1, 30, 75 and 200 SUI.

Courier and clearing helpers

Move
public fun open(personal: bool, ctx: &mut TxContext): ID
public fun enable(kiosk: &mut Kiosk, cap: &KioskOwnerCap, ctx: &mut TxContext)
public fun ready(kiosk: &Kiosk): bool

courier::open creates and shares a kiosk for the sender with deliveries enabled. With personal = true the owner cap is wrapped as a PersonalKioskCap, which collections with a personal kiosk rule require. courier::enable installs (or re-enables) the extension on an existing kiosk. For a personal kiosk, pass the cap borrowed from your PersonalKioskCap.

Move
public fun royalty_due<T: key + store>(policy: &TransferPolicy<T>, paid: u64): u64
public fun needs_lock<T>(policy: &TransferPolicy<T>): bool
public fun needs_personal<T>(policy: &TransferPolicy<T>): bool
public fun settle_into<T: key + store>(policy: &mut TransferPolicy<T>, req: &mut TransferRequest<T>, item: T, kiosk: &mut Kiosk, cap: &KioskOwnerCap, royalty: &mut Coin<SUI>, ctx: &mut TxContext): u64

royalty_due quotes the royalty on a payment (0 without a royalty rule). settle_into stows an item you bought into your kiosk, satisfies the standard rules, pays royalty from royalty and returns the amount paid.

Fees and royalties

TradeFeeWho pays
Listingask * listing_bps / 10000, taken out of askSeller (nets ask - cut)
Offer and pool filleach * offer_bps / 10000, escrowed on top of eachOffer maker
Swapbid_sui * swap_bps / 10000 + swap_flat at draft, and ask_sui * swap_bps / 10000 out of the taker's ask_suiMaker, then taker
Rafflepot * raffle_bps / 10000 at settlementHost
Bundleask * bundle_bps / 10000 (or amount * bundle_bps / 10000 for a bid), on topBuyer or bidder
Cratethe tier priceBuyer

Every rate starts at 10 bps (0.1%), with no flat swap fee. Rates are capped at 500 bps each and the flat fee at 1 SUI (103). Changes are queued with the ConfigCap and take effect only after a 48 hour delay, through config::apply, which anyone can call. Fees round down, so a fee on an amount below 1,000 MIST is 0 and the trade goes through without one.

Royalties follow the collection's own TransferPolicy (royalty_rule). On a kiosk listing the buyer pays royalty on top, computed on the kiosk price ask - cut. On offers it comes from the reserve the maker escrowed. On swaps and bundle purchases it comes from the pay or royalty coin you pass in. Read a quote with clearing::royalty_due.

Events

All events are in PACKAGE_ID::events, with the original package id in their type. Field order never changes, so decode them from BCS with these layouts.

EventFields
KioskPostedorder, item, kind, ask, cut, maker, maker_kiosk
KioskRepricedold_order, new_order (the new order also emits KioskPosted)
KioskPulledorder, item, kind, ask, maker, maker_kiosk
KioskTakenorder, item, kind, ask, cut, royalty, maker, maker_kiosk, taker, taker_kiosk: Option<ID>. royalty and taker_kiosk are set by buy and are 0 and none for take.
EscrowPosted / EscrowRepricedorder, item, kind, ask, cut, maker
EscrowPulledorder, item, kind, ask, maker
EscrowTakenorder, item, kind, ask, cut, maker, taker
OfferPlacedoffer, pool: Option<ID>, maker, kind, scope, item: Option<ID>, root: Option<vector<u8>>, each, fee, reserve, qty, until_ms: Option<u64>, dest_kiosk: Option<ID>. For pool members, offer is the member id.
OfferFilledoffer, pool, maker, seller, kind, item, each, fee, royalty, returned, seller_kiosk: Option<ID>, dest_kiosk: Option<ID>, left
OfferClosedoffer, pool, maker, left, refunded, reason (0 revoked, 1 expired, filled out or pruned, 2 pool closed)
PoolOpenedpool, maker, label, funds
PoolFundedpool, maker, label, funds, held. Emitted on deposit, withdraw, relabel and every fill.
PoolClosedpool, maker, refunded
DealProposeddeal, maker, maker_kiosk, taker, give, want, ask_sui, bid_sui, expiry_epoch
DealSettleddeal, maker, taker, give, want, ask_sui, fee, bid_sui
DealUnwounddeal, maker, by, expired, refunded
RaffleOpenedraffle, host, item, kind, host_kiosk, price, min_tickets, max_tickets, ends_ms, fee_bps
TicketsBoughtraffle, buyer, first, qty, paid, sold. The buyer owns tickets first to first + qty - 1.
RaffleDrawnraffle, winner, ticket, sold
RaffleSettledraffle, winner, host, gross, fee, royalty, to_host, winner_kiosk
RaffleVoidedraffle, host, by, sold, pot
RaffleRefundedraffle, holder, qty, amount
BundleListedbundle, maker, items, kinds, ask, sealed_reserve
BundleResealedbundle, sealed_reserve
BundleClosedbundle, maker, reason
BundleSoldbundle, maker, buyer, price, fee, royalty, bid: Option<ID>
BundleBidPlacedbid, bundle, bidder, amount, fee, reserve, until_ms, dest_kiosk
BundleBidClosedbid, bidder, refunded, reason (0 withdrawn, 1 lapsed)
CrateBoughtbuyer, tier, qty, unit, total
CratePriced / CrateShopOpentier, price / open
ConfigQueuedconfig, ready_ms, treasury, rates: [listing_bps, offer_bps, swap_bps, swap_flat, raffle_bps, bundle_bps]
ConfigAppliedconfig, treasury, rates
ConfigDiscarded / ConfigPaused / ConfigMigratedconfig / config, paused / config, version

Error codes

RangeModule
101 to 106config: paused, outdated version, rate ceiling, nothing queued, too early, not newer
201 to 203sale_escrow
211 to 214sale_kiosk
301 to 314offer
351 to 355offer_pool
401 to 416barter
501 to 519raffle
601 to 618bundle
701 to 705crate_shop
801 to 802courier: not ready, not the recipient's kiosk
901clearing: royalty short

Examples

These use @mysten/sui with the Transaction builder. Replace the placeholder ids with the values from the status box above.

Buy a kiosk listing

TypeScript
import { Transaction } from "@mysten/sui/transactions";

const PACKAGE_ID = "0x..."; // latest market package id
const CONFIG_ID = "0x...";  // shared market::config::Config

export function buyListing(
  tx: Transaction,
  l: {
    orderId: string;      // KioskOrder id (the `order` field of KioskPosted)
    sellerKiosk: string;  // `maker_kiosk`
    itemType: string;     // T, for example "0xabc::nft::Nft"
    policyId: string;     // shared TransferPolicy<T>
    ask: bigint;          // in MIST
  },
  buyer: { kiosk: string; kioskCap: string },
) {
  // Pay the ask plus a royalty quote. Royalty is charged on `ask - cut`,
  // so a quote on `ask` is an upper bound and the change comes back.
  const [due] = tx.moveCall({
    target: `${PACKAGE_ID}::clearing::royalty_due`,
    typeArguments: [l.itemType],
    arguments: [tx.object(l.policyId), tx.pure.u64(l.ask)],
  });
  const [pay] = tx.splitCoins(tx.gas, [l.ask]);
  const [royalty] = tx.splitCoins(tx.gas, [due]);
  tx.mergeCoins(pay, [royalty]);

  const [request] = tx.moveCall({
    target: `${PACKAGE_ID}::sale_kiosk::buy`,
    typeArguments: [l.itemType],
    arguments: [
      tx.object(l.orderId),
      tx.object(CONFIG_ID),
      tx.object(l.sellerKiosk),
      tx.object(buyer.kiosk),
      tx.object(buyer.kioskCap),
      tx.object(l.policyId),
      pay,
    ],
  });
  tx.moveCall({
    target: "0x2::transfer_policy::confirm_request",
    typeArguments: [l.itemType],
    arguments: [tx.object(l.policyId), request],
  });
  tx.mergeCoins(tx.gas, [pay]); // return the change
}

To sweep, call sale_kiosk::buy once per listing with the same pay coin, funded with the sum of the asks and royalty quotes.

If the buyer has no kiosk yet, create one in the same PTB with 0x2::kiosk::new, pass it to buy, then share it and send the cap to the buyer:

TypeScript
const [kiosk, kioskCap] = tx.moveCall({ target: "0x2::kiosk::new" });
// ... sale_kiosk::buy with `kiosk` and `kioskCap` as dest and dest_cap ...
tx.moveCall({
  target: "0x2::transfer::public_share_object",
  typeArguments: ["0x2::kiosk::Kiosk"],
  arguments: [kiosk],
});
tx.transferObjects([kioskCap], sender);

Collections with a personal kiosk rule need a personal kiosk instead.

List a kiosk item

TypeScript
tx.moveCall({
  target: `${PACKAGE_ID}::sale_kiosk::post`,
  typeArguments: [itemType],
  arguments: [
    tx.object(CONFIG_ID),
    tx.object(kioskId),
    tx.object(kioskCapId),
    tx.pure.id(itemId),
    tx.pure.u64(5_000_000_000n), // ask: 5 SUI, buyer pays this plus royalty
  ],
});

Place a collection offer

TypeScript
const SCOPE_ANY = 0;
const each = 2_000_000_000n; // 2 SUI per item
const qty = 3n;
// Escrow is qty * (each + fee + reserve):
//   fee     = each * offer_bps / 10000   (offer_bps from the Config object)
//   reserve = clearing::royalty_due(policy, each)
// Fund at least that much. Whatever the call does not use stays in `pay`.
const budget = qty * (each + fee + reserve);
const [pay] = tx.splitCoins(tx.gas, [budget]);
tx.moveCall({
  target: `${PACKAGE_ID}::offer::place`,
  typeArguments: [itemType],
  arguments: [
    tx.object(CONFIG_ID),
    tx.object(policyId),
    tx.pure.u8(SCOPE_ANY),
    tx.pure.option("address", null),       // item: none for scope 0
    tx.pure.option("vector<u8>", null),    // root: none for scope 0
    tx.pure.u64(each),
    tx.pure.u64(qty),
    tx.pure.option("u64", Date.now() + 7 * 86_400_000), // expires in 7 days
    tx.pure.option("address", myCourierKioskId),        // dest kiosk for kiosk fills
    pay,
    tx.object("0x6"),
  ],
});
tx.mergeCoins(tx.gas, [pay]);

myCourierKioskId must be a kiosk you own with the courier extension installed (courier::open or courier::enable).

Sell into an offer from your kiosk

TypeScript
const [proceeds, request] = tx.moveCall({
  target: `${PACKAGE_ID}::offer::fill`,
  typeArguments: [itemType],
  arguments: [
    tx.object(offerId),
    tx.object(CONFIG_ID),
    tx.object(myKioskId),
    tx.object(myKioskCapId),
    tx.pure.id(itemId),
    tx.object(destKioskId), // the offer's `dest_kiosk` from OfferPlaced
    tx.object(policyId),
    tx.pure.vector("vector<u8>", []), // Merkle proof for trait offers, else empty
    tx.object("0x6"),
  ],
});
tx.moveCall({
  target: "0x2::transfer_policy::confirm_request",
  typeArguments: [itemType],
  arguments: [tx.object(policyId), request],
});
tx.transferObjects([proceeds], sender);

If the item is listed natively in your kiosk, delist it first with 0x2::kiosk::delist. For pool members, call offer_pool::fill with the pool id and the member id in front of the same arguments.

--
TPS:--