Skip to main content
The full dark-pool vault loop is live on testnet for the wc26_euro_champion World Cup binary market. Deposit, keeper mint, V3 quoting, settlement, pro-rata redeem.

1. (Once) Bootstrap a new vault

pnpm tsx scripts/create-darkpool-vault.ts \
  --market wc26_euro_champion \
  --min-deposit 1000000 \
  --execute
--market is a slug from .binary-markets/registry.json. The Move target is ${DARKPOOL_PACKAGE_ID}::dark_pool::create_vault<DUSDC>. One PTB does: balance_manager::newmint_trade_capdark_pool::create_vault. Shares the vault + BalanceManager; keeps TradeCap + VaultAdminCap with the keeper (sender). Prints VAULT_OBJECT_ID. Paste into .env.

2. Deposit

From the UI

  1. Go to /dark-pool.
  2. Pick the vault card.
  3. Click Deposit privately.
  4. Enter amount, confirm. PTB shape:
    split_coin(wallet_dusdc, amount)
      → dark_pool::deposit<DUSDC>(vault, coin, access_policy, blob_id, attribution_hash[32], clock)
      → transfer DepositReceipt to sender
    
  5. Receipt object appears in your wallet. Block explorer sees <you> → vault. Direction / strike is opaque.

From a script (smoke test / TVL seeder)

pnpm tsx scripts/vault-deposit.ts --amount 2 --execute
Uses the exact PTB shape the frontend signs.

3. Keeper tick

Deploys the pooled deposits. Idempotent. A second run with nothing idle is a no-op.
pnpm tsx scripts/keeper-tick.ts --execute            # all idle balance
pnpm tsx scripts/keeper-tick.ts --amount 5 --ask 0.55 --execute
One PTB:
dark_pool::pull_for_mint
  → settlement::mint_pair (YES + NO from binary market)
  → balance_manager::deposit (YES + NO go to vault's V3 BM)
  → dark_pool::record_mint  (aggregate_cost += spent)
If yesPoolId / noPoolId exist in the registry, the tick also posts POST_ONLY asks on both pools via the vault’s TradeCap (generate_proof_as_trader). From V3’s perspective the trader is the vault’s BalanceManager, not any depositor.
Fee headroom. The keeper quotes roundLot(held × 100/105) because place_limit_order with pay_with_deep=false locks qty + the input-token fee at placement. Without the headroom, quoting 100% of holdings aborts EBalanceManagerBalanceTooLow. Discovered during the first live run.

4. Resolve + settle

Once the binary market’s expiry has passed and someone signs binary-market:resolve, the keeper flips to settlement mode:
pnpm tsx scripts/keeper-tick.ts --execute
Now does:
cancel V3 orders
  → balance_manager::withdraw_all(YES + NO + idle DUSDC)
  → settlement::redeem_yes (or redeem_no, whichever side won)
  → dark_pool::receive_settlement (combined DUSDC payout)
  → state → SETTLED
The idle-sweep step is mandatory. Receipt redemption pays from final_payout_micro only, so idle DUSDC left in vault.balance would be stranded forever. Encoded in the script.

5. User redeem

UI: /dark-poolWithdraw button on the vault card. PTB:
dark_pool::redeem<DUSDC>(vault, receipt)   → Coin<DUSDC>
  → transferObjects(coin, sender)
Math:
payout = (receipt.shares * vault.final_payout_micro) / vault.total_shares
Receipt is destroyed; sender gets pro-rata DUSDC.