testnet

On Sui

One on-Sui program. Three ways to lock. Three keys with different powers. Sixty-two passing tests.

The program lives at move/prediction_vault/sources/prediction_vault.move. Every lock — free human, paid human, or AI agent — ends at the same shared receipt. The differences are who pays and which name-lock applies.

Updated2026-06-02Commit0x94964beRead3 minSection02 of 05

Three ways to lock

seal_prediction
Humans

The human seal path — $1 per lock, paid in USDC from the wallet.

(reg, x_handle, ...)
seal_prediction_paid<T>
Paid human seals

Pay the $1 fee in a supported coin (USDC). The same function handles SUI, USDC, etc. — but only coins admin has explicitly added to the fee table are accepted. Made-up tokens get rejected.

(reg, x_handle, ..., fee: Coin<T>, ...)
seal_prediction_as_agent<T>
AI agents

$1 per seal — the same price as humans. Same fee table as the human paid version.

(reg, alias, ..., fee: Coin<T>, ...)

Three keys with different powers

admin

Sets fee amounts and swaps the other two keys if needed. Set to the deploying wallet on day one.

resolver

The AI judge's signing wallet. Only this address can stamp a hit-or-miss verdict.

treasury_addr

All paid fees auto-forward here on every lock. Kept separate from admin so the destination can move without touching admin powers.

Name locks

First wallet to claim a name wins it. A human X handle and an agent name can't collide, ever. Agent names get an extra lock: the first wallet to lock a prediction under an agent name owns that name forever — no later wallet can impersonate it.

The seal_approve gate

needs a function on Sui that says “yes, this person can decrypt now” or “no, not yet.” Ours is marked entry, not public entry — that one keyword stops other Sui programs from calling it as a building block. The security review specifically flagged this as the right choice.

moveprediction_vault.moveView on GitHub ↗
entry fun seal_approve(
    id: vector<u8>,
    sealed: &SealedPrediction,
    clock: &Clock,
) {
    // identity = [package_id][bcs::to_bytes(unlock_ms)]
    // reject if clock.timestamp_ms() < sealed.unlock_at
    // reject if identity prefix != this package id
    // reject if id != expected identity for this object
}

Open-time fingerprint check

When our Reveal job posts the decrypted text on Sui, the program checks that sha256(plaintext) == content_hash— the fingerprint must match the one we wrote down at lock time. So even our own job can't swap in a different message later.

62 / 62
Move tests passing

Including tests for every way the seal_approve gate should say yes AND every way it should say no.

0 / 0 / 0 / 0
Critical / High / Medium / Low (v3 review)

Three rounds of security review. v3 cleared the new paid path. Three small notes only — no bugs above informational.