shroud

How it works

Units

QuantityUnit
Size1e-9 SOL
Entry price, collateral1e-6 USD (USDC base units)
Liquidation threshold, check price1e-4 USD, so a threshold fits 32 bits
Payout from the cluster1e-19 USD (the program divides by 1e13)

Opening a position

An open has two steps, so nobody trades at a price they have already seen move. The oracle (Pyth's SOL/USD account) updates about every 10 seconds; if the program filled at the price it held when the order arrived, a trader watching a faster market could buy just before a known move.

  1. Request. The browser encrypts two numbers, size and w + side * 2^40, where w = floor(collateral * 1e9 / size) is the collateral per unit of size (the side rides in bit 40, so the order is two ciphertexts). It calls request_open with the collateral and the market's fixed fee. The program takes both into the vault and holds a slot. This transaction has no Arcium accounts and is small (about 590 bytes).
  2. Fill. The keeper (anyone may) calls settle_open once the oracle publishes a price at least fill_delay_secs after the request and no later than fill_window_secs after that. That price is the entry. The program hands the sealed order to the cluster.
  3. Inside MPC, open_pos checks w against the collateral with multiplications (w * size <= coll * 1e9 < (w + 1) * size), checks the market's leverage cap (size * entry <= max_leverage * coll * 1e9), and derives the liquidation threshold from the fill price with the 2.5% maintenance margin:
    • long: v = floor(1e4 * (entry - w) / 975000), liquidated when the price is at or below v
    • short: v = floor((1e4 * (entry + w) - 1) / 1025000), liquidated when the price is above v
  4. The answer carries the book slot (threshold and side, encrypted to the book key), the close record (size and side, encrypted for the cluster) and one public bit, accepted or not. A rejected open gets its collateral and fee back through reclaim, and so does a request nobody filled in its window.

Checking positions

  1. A keeper calls chk128 on a book.
  2. The program reads the oracle price and snapshots every slot's version.
  3. It hands the cluster the book key, 128 nonces, the 128 ciphertext pairs (one account range) and the price.
  4. For each slot the cluster computes (price <= v) == long on secret shares.
  5. It answers with 128 bits, 16 bytes in all.
  6. The program marks the slots that answered 1 and still hold the same position.
  7. Anyone calls liquidate on each marked slot.

The price comes from the oracle account, so a keeper cannot choose it. Each slot has a version that changes when the slot is freed, so an answer about an old position never touches a new one.

Closing a position

A close has the same two steps. request_close (the owner, about 300 bytes) records the request; the keeper's settle_close fills it at the first oracle price inside its window, records that exit and the hours held, and queues close_pnl with the sealed close record. A close whose answer is late is queued again at the same recorded exit. Inside MPC:

value = coll * 1e13 + gain - loss - fees            (floored at 0)
gain or loss = 1e4 * size * |exit - entry|           (a gain when the side matches the move)
fees = size * ((entry + exit) * fee_bps + entry * borrow_bps)

Only value is revealed. The program divides it by 1e13, pays the trader from the vault, and frees the slot.