Skip to main content
Each event market is a full Move publish because Sui’s one-time-witness rule means each Coin type needs its own published module. The template lives at packages/binary-market-template/ and is excluded from the pnpm workspace (it’s Move, not TS).

Layout

binary-market-template/
├── Move.toml                              # placeholder package name; rewritten per market
└── sources/
    ├── yes.move                           # Coin<YES> OTW + TreasuryCap
    ├── no.move                            # Coin<NO> OTW + TreasuryCap
    └── settlement.move                    # Settlement<COLLATERAL>: mint_pair / burn_pair /
                                           #   mark_resolved (ResolverCap) / redeem_{yes,no}

yes.move / no.move

module binary_market_<slug>::yes;
use sui::coin;

public struct YES has drop {}

fun init(otw: YES, ctx: &mut TxContext) {
    let (treasury_cap, metadata) = coin::create_currency(
        otw,
        6,                          // decimals: matches DUSDC
        b"<slug>-YES",
        b"<slug> YES",
        b"YES token for <label>",
        option::none(),
        ctx,
    );
    transfer::public_freeze_object(metadata);
    transfer::public_transfer(treasury_cap, ctx.sender());
}
The <slug> substitution happens during scaffolding; the resulting type is 0x<pkg>::yes::YES / 0x<pkg>::no::NO.

settlement.move

module binary_market_<slug>::settlement;

public struct Settlement<phantom COLLATERAL> has key {
    id: UID,
    label: String,
    expiry_ms: u64,
    yes_treasury: TreasuryCap<YES>,
    no_treasury: TreasuryCap<NO>,
    collateral: Balance<COLLATERAL>,
    final_outcome: u8,            // 0 = unresolved, 1 = YES, 2 = NO
}

public struct ResolverCap has key, store {
    id: UID,
    settlement_id: ID,
}
Entry functions:
  • create_market<COLLATERAL>(yes_cap, no_cap, label, expiry_ms, resolver, ctx). Wraps both caps, shares the Settlement, transfers ResolverCap to resolver.
  • mint_pair<COLLATERAL>(settlement, collateral_coin, ctx) -> (Coin<YES>, Coin<NO>). Pre-resolution. Takes equal collateral, mints equal YES + NO.
  • burn_pair<COLLATERAL>(settlement, yes_coin, no_coin, ctx) -> Coin<COLLATERAL>. Pre-resolution. Pays back the matching collateral.
  • mark_resolved(settlement, cap, outcome, clock, ctx). Owner-gated by the ResolverCap. Asserts clock.timestamp_ms >= expiry_ms. Sets final_outcome.
  • redeem_yes<COLLATERAL>(settlement, yes_coin, ctx) -> Coin<COLLATERAL>. Post-resolution. Pays $1 per YES if final_outcome == 1, else aborts.
  • redeem_no<COLLATERAL>. Symmetric.
Emits MarketCreated, PairMinted, PairBurned, MarketResolved, Redeemed.

Scaffolder

scripts/create-binary-market.ts does the following:
  1. Materialises template into .binary-markets/binary_market_<slug>/.
  2. Substitutes slug + label into module names + metadata.
  3. Runs sui move build to verify substitution (always, even in dry-run).
  4. With --execute: publishes, extracts package + TreasuryCap ids from objectChanges.
  5. Runs settlement::create_market<DUSDC> to consume both caps and mint ResolverCap.
  6. Appends to .binary-markets/registry.json.
Run via pnpm binary-market:create --slug … --label … --expiry-ms … --resolver 0x… --execute. Slug must match /^[a-z0-9_]+$/. See Create a Binary Market for the full walkthrough.