14 · Verify it yourself
Verify it yourself
The testnet is tested with a protocol. Here it is as checks you can run on your own trades, using only public reads: what each one proves, the number to expect, the tolerance, and how to change it. Use it as a proxy for a numerical audit of your own account.
https://protect.temporal.exchange/api/venue and your own wallet's numbers. Nothing here needs a key from the venue. The few checks that need the operator's price controls are described and marked Operator-run; you do not run them, and their results are published (see the last section). You see yourself and the aggregate only: no other trader or maker appears in any read.How the checks work#
Each check is a before-and-after. Read your state (/chain/state), do one act (Trade with your AI agent), read it again, and compare the change with the rule. The small script below does the arithmetic and prints one row per rule: PASS, FALSE or NOT EXPOSED (it could not read what it needed: never a silent pass). The rows are the same shape the venue's own referee prints (check, expected, observed, pass).
// audit.mjs: re-derive the venue's numbers from its public reads. No key, no signing, nothing is sent: it only reads.
// node audit.mjs snapshot <wallet> > before.json your state, the custody gap and your HyperCore balance, now
// node audit.mjs fee <qty> <markUSD> <premiumUSD> <feeUSD> the taker fee is the published formula
// node audit.mjs fill <fillPriceUSD> <frontPriceUSD> [tolPct] a close fills within a % of the front you read just before
// node audit.mjs close-keeps-money before.json after.json <account> <closeCostUSD>
// node audit.mjs pays-out before.json after.json <account> <residualUSD> <payoutUSD> [new]
// node audit.mjs custody the venue's own money check, from /chain/health
import { readFileSync } from "node:fs";
const BASE = process.env.TEMPORAL_BASE ?? "https://protect.temporal.exchange/api/venue";
const get = (p) => fetch(BASE + p).then((r) => r.json());
// The published numbers. Change them only if the docs page changes them: a check in the repo fails when they differ.
const FEE = { bps: 3, capOfPremiumPct: 12.5, floorUSD: 0.25, tolUSD: 0.01 };
const TOL = { cashUSD: 0.01, hypercoreUSD: 0.02, depositUSD: 0.01, closeFillPct: 1, newAddressFeeUSD: 1 };
const rows = [];
const row = (check, pass, expected, observed) => {
rows.push({ check, pass, expected, observed });
console.log(`${pass === null ? "NOT EXPOSED" : pass ? "PASS" : "FALSE"} ${check}\n expected: ${expected}\n observed: ${observed}`);
};
const money = (x) => (x == null ? "n/a" : `$${Number(x).toFixed(2)}`);
const [cmd, ...args] = process.argv.slice(2);
const load = (f) => JSON.parse(readFileSync(f, "utf8"));
if (cmd === "snapshot") {
const wallet = args[0];
const [state, health, hc] = await Promise.all([get(`/chain/state?wallet=${wallet}`), get("/chain/health"), get(`/hypercore/balance?wallet=${wallet}`)]);
console.log(JSON.stringify({ at: new Date().toISOString(), state, custody: health.custody ?? null, hypercoreUSD: hc.perpUSD != null ? hc.perpUSD + hc.spotUSD : null }, null, 1));
process.exit(0);
}
if (cmd === "fee") {
const [qty, mark, premium, fee] = args.map(Number);
const byNotional = (qty * mark * FEE.bps) / 1e4;
const cap = (premium * FEE.capOfPremiumPct) / 100;
const expected = Math.max(Math.min(byNotional, cap), FEE.floorUSD);
row("taker fee = max(min(3 bps of notional, 12.5 % of premium), $0.25)", Math.abs(fee - expected) <= FEE.tolUSD, money(expected), `${money(fee)} (notional fee ${money(byNotional)}, premium cap ${money(cap)})`);
}
if (cmd === "fill") {
const [fill, front, tol = TOL.closeFillPct] = args.map(Number);
const dev = ((fill - front) / front) * 100;
row(`the fill is within ${tol} % of the front quoted just before`, Math.abs(dev) <= tol, `within ${tol} %`, `fill ${money(fill)} vs front ${money(front)} (${dev >= 0 ? "+" : ""}${dev.toFixed(3)} %)`);
}
if (cmd === "close-keeps-money" || cmd === "pays-out") {
const [fb, fa, account, ...rest] = args;
const b = load(fb), a = load(fa);
const acct = (s) => Object.values(s.state.accounts).find((x) => String(x.id) === account || x.name === account);
const [ab, aa] = [acct(b), acct(a)];
const dCash = a.state.cashUSD - b.state.cashUSD;
if (cmd === "close-keeps-money") {
const cost = Number(rest[0]);
const tot = (x) => x.depositUSD + (x.heldUSD ?? 0);
const dTot = tot(aa) - tot(ab);
row("(R3a) free cash unchanged by a single-leg close", Math.abs(dCash) <= TOL.cashUSD, `0 ± ${money(TOL.cashUSD)}`, `${money(b.state.cashUSD)} -> ${money(a.state.cashUSD)}`);
row("(R3b) deposit + held falls by the close's own cost and its fee, no more", dTot <= -cost + TOL.cashUSD && dTot >= -cost * 1.01 - TOL.cashUSD, `${money(-cost)} to ${money(-cost * 1.01)}`, `${money(tot(ab))} -> ${money(tot(aa))} (change ${money(dTot)})`);
const dHc = a.hypercoreUSD - b.hypercoreUSD;
row("(R3c) nothing paid out: your HyperCore total is unchanged", Math.abs(dHc) <= TOL.hypercoreUSD, `0 ± ${money(TOL.hypercoreUSD)}`, `${money(b.hypercoreUSD)} -> ${money(a.hypercoreUSD)}`);
} else {
const [residual, payout, isNew] = [Number(rest[0]), Number(rest[1]), rest[2] === "new"];
const expected = residual - (isNew ? TOL.newAddressFeeUSD : 0);
const dHc = a.hypercoreUSD - b.hypercoreUSD;
row("(R4b) payout = residual minus the fee rule ($1 only to an address new to HyperCore)", Math.abs(payout - expected) <= TOL.hypercoreUSD, money(expected), money(payout));
row("(R4c) your HyperCore wallet rose by the payout", Math.abs(dHc - payout) <= TOL.hypercoreUSD, `${money(payout)} ± ${money(TOL.hypercoreUSD)}`, `${money(b.hypercoreUSD)} -> ${money(a.hypercoreUSD)} (change ${money(dHc)})`);
row("(R4d) free cash unchanged: the residual passed through and out", Math.abs(dCash) <= TOL.cashUSD, `0 ± ${money(TOL.cashUSD)}`, `${money(b.state.cashUSD)} -> ${money(a.state.cashUSD)}`);
row("(R4e) the account holds nothing", !aa || aa.depositUSD <= TOL.depositUSD, `deposit <= ${money(TOL.depositUSD)}`, aa ? money(aa.depositUSD) : "the account is gone from the list");
}
}
if (cmd === "custody") {
const c = (await get("/chain/health")).custody;
row("the venue's HyperCore money matches what its ledger attributes", c ? c.ok === true : null, c ? `|gap| <= ${money(c.toleranceUSD)}` : "custody served", c ? `ledger ${money(c.ledgerUSD)}, HyperCore ${money(c.hypercoreUSD)}, gap ${money(c.gapUSD)}` : "not served");
}
const bad = rows.filter((r) => r.pass === false).length;
console.log(`\n${rows.length} row(s): ${rows.filter((r) => r.pass).length} PASS, ${bad} FALSE`);
process.exit(bad ? 1 : 0);You are auditing my own account on the Temporal testnet venue using https://protect.temporal.exchange/docs/verify. Take a snapshot with audit.mjs (node audit.mjs snapshot <my wallet> > before.json), do exactly the one act I ask for using https://protect.temporal.exchange/docs/agents (dry run and my yes first), snapshot again to after.json, then run every check on that page that applies to the act. Print a table: check, expected, observed, PASS or FALSE. For any FALSE, show the raw numbers and stop; do not retry.
Fees and prices#
The taker fee is the published formula
- Proves
- You paid exactly what the fee rule says, no more, on every open and close.
- Run it
Read the fee, then recompute it curl -s "https://protect.temporal.exchange/api/venue/chain/quote?asset=BTC&side=buy&wing=call&k=1.05&qty=0.01&months=3" node audit.mjs fee 0.01 79815 42.619015 0.25 # qty, mark, premium, the fee the quote showedQty Mark Premium Fee What sets it 0.01 $79815.00 $42.62 $0.25 3 bps of the notional is $0.24, under the floor: the floor sets the fee 1 $79815.00 $4261.90 $23.94 3 bps of the notional, under the premium cap: the bps fee sets the fee 1 $79815.00 $100.00 $12.50 a cheap far-out option: 12.5 % of the premium is under 3 bps of the notional, so the cap sets the fee After a trade, the fee is in your own fill row too:
GET /chain/fills?wallet=…, fieldfeeUSD.- Expect
- One request pays max( min( 3 bps of notional, 12.5 % of the premium ), $0.25 ), once per side however many makers fill it, to the cent. Makers pay 0.
- Change it
- Any size, strike, asset or decay: the quote returns the premium, the fee and the mark, and the formula needs only those three numbers and the quantity.
The fill is at the front, inside your bound
- Proves
- You were filled at the price the front quoted, not a worse one, and never past the limit you signed.
- Run it
Compare the fill with the front you read just before curl -s "https://protect.temporal.exchange/api/venue/chain/fills?wallet=0xYOUR_WALLET&account=all&limit=5" # rows[0].priceUSD is the fill. The front you read before is the quote's pricePerUnitUSD. node audit.mjs fill 4262.10 4261.90 1 # fill, front, tolerance %- Expect
- The quote you read before signing matches the front's own quote for the same cell to ±0.5 % + $1 (the book moves between two reads); an open fills within ±3 % of the quote read right after; a close prices at the front's closeable side to ±1 % + $1. The money round judges a close at 1 % alone. A bought leg never costs more than its signed consent covers, and a PROTECT leg never fills past its 1 % bound.
- Change it
- Any asset, side, strike or size. Read the quote immediately before and immediately after the act, and compare priceUSD in your fill row.
A round trip is legs minus fees plus funding
- Proves
- Open and close the same option and the money that moved is exactly the legs, the fees and the funding. Nothing else.
- Run it
Sum a leg's rows curl -s "https://protect.temporal.exchange/api/venue/chain/fills?wallet=0xYOUR_WALLET&account=T1&limit=50" # for the leg: sum(premiumUSD) - sum(feeUSD) vs change in accounts.T1.depositUSD + heldUSD between two states, less funding- Expect
- Net change in the account's deposit + held = Σ premiums received − Σ premiums paid − Σ fees − funding paid (+ funding received). Every fill row has its premium and fee; funding is on the leg in /chain/state. Compare to the cent; allow up to 1 % of a close's own cost for rounding, as the money round does.
- Change it
- Open any option (buy or sell, call or put, any strike or decay), close it, then sum your fill rows for that leg.
Positions and equity#
Your equity is the ledger's own number
- Proves
- What the website (or your agent) says your account is worth is what the contract itself computes at the current prices.
- Run it
The ledger's marked margin for each of your accounts curl -s "https://protect.temporal.exchange/api/venue/chain/state?wallet=0xYOUR_WALLET" # accounts.<key>.margin.marked -> equity, maintenance, initial, ageMs- Expect
- The equity and maintenance level on your account equal the ledger's marked margin (
margin.markedin your state) to ±$0.15, after taking off the Hyperliquid funding booked on an open perp. The perp mark behind it is never older than 600 s; an older one makes every open refuse on chain. - Change it
- Any account kind. Open a PROTECT with a different leverage or floor, or an OPTIONS account with a sold leg, and read the same fields.
PROTECT delivers the floor you typed
- Proves
- The structure the venue sells you puts the liquidation floor where you asked, and the floor printed is the venue's own number.
- Run it
Ask for a floor, then read the account after the open curl -s -X POST https://protect.temporal.exchange/api/venue/perp/plan -H 'content-type: application/json' \ -d '{"side":1,"depositUSD":100,"lev":3,"asset":"BTC","target":52000,"sellFromRoe":1,"mode":"protect"}' # plan.floorUSD, plan.floorBeforeUSD (the perp alone), plan.legs[] curl -s "https://protect.temporal.exchange/api/venue/chain/state?wallet=0xYOUR_WALLET" # accounts.P1.perps[] (status open) and legs[] both present- Expect
plan.floorUSDis at or beyond your typed floor (a floor it cannot deliver is refused with the nearest one it can). After the open, the liquidation price shown for the account equals the venue's own number to ±$1.00. The perp and every leg open together or not at all: a state with a perp and no legs is a failure.- Change it
- Leverage, margin, floor, asset (BTC or HYPE) and side (side 1 long, -1 short) are all in the plan request. Try the same floor at 3x and at 10x.
Leaving a position#
Closing one leg keeps the money in the account (R3)
- Proves
- A single close pays the maker and the fee, and the rest stays in the account. Nothing leaves it.
- Run it
Snapshot, close one leg, snapshot, compare node audit.mjs snapshot 0xYOUR_WALLET > before.json CONFIRM=yes node close.mjs leg 5 0 0.01 node audit.mjs snapshot 0xYOUR_WALLET > after.json node audit.mjs close-keeps-money before.json after.json 5 41.20 # account, the close's cost = qty x fill price- Expect
- Free cash is unchanged (±$0.01). Deposit + held falls by the close's own cost (quantity × fill price) plus the fee, and by no more than 1 % over it. Your HyperCore total is unchanged (±$0.02); no payout is made.
- Change it
- Any option you hold: buy or sell, call or put. Close one of several legs, or only part of a leg.
CLOSE ACCOUNT pays the whole residual to your wallet (R4)
- Proves
- Ending an account sends everything left in it to your own HyperCore wallet, minus the one fee that rule allows.
- Run it
Close the account and check the payout node audit.mjs snapshot 0xYOUR_WALLET > before.json CONFIRM=yes node close.mjs account 5 # ...wait until /chain/state shows the account closed and the payout in /chain/withdrawals... node audit.mjs snapshot 0xYOUR_WALLET > after.json node audit.mjs pays-out before.json after.json 5 62.40 62.40 # account, residual, payout (add: new if the address was new to HyperCore)- Expect
- Payout = the residual minus $1.00 only when the destination is an address new to HyperCore, otherwise nothing. Your HyperCore wallet rises by the payout (±$0.02); free cash does not move (±$0.01); the account holds nothing (≤ $0.01). On a PROTECT account the page also shows the residual landing in your HyperCore Perps balance to ±$0.05.
- Change it
- OPTIONS, PROTECT or EARN accounts; one leg or many; with or without a perp.
Withdrawals queue by the published limits
- Proves
- A limit never refuses you: it queues the request and says when it pays.
- Run it
The limits, live curl -s "https://protect.temporal.exchange/api/venue/chain/withdrawals?wallet=0xYOUR_WALLET" # limits.limits: { accountDailyUSD, venueDailyUSD, instantUSD, delayMs } limits.account / limits.venue: used and left- Expect
- Up to $10,000 pays at once; a larger request waits 30 minutes; a wallet can take out $25,000 in any rolling 24 hours. The venue's total is its own setting: read it from
/chain/withdrawals(limits.limits.venueDailyUSD), which may be lower on testnet than the default of $250,000. - Change it
- Ask for an amount below, at and above each threshold and read what comes back: paid, or queued with an execution time.
The venue's own money#
The money trail is closed
- Proves
- The dollars the ledger says it holds are the dollars HyperCore holds, and your own money only ever moved between your wallet and the venue, never into the venue's hedge wallets.
- Run it
The gap, and your deposits node audit.mjs custody curl -s "https://protect.temporal.exchange/api/venue/chain/deposits?wallet=0xYOUR_WALLET" # transfers[]: your sends and their credit statusThe check that no transfer links your wallet to the venue's two direction wallets (LONG and SHORT) reads Hyperliquid's public ledger updates for your address and for those two addresses. The venue's direction-wallet addresses are given by the operator; once you have them it is a read.
- Expect
- The custody gap (HyperCore minus what the ledger attributes) is within max($1.00, 0.5 % of the larger side). The venue publishes the gap, the tolerance and every named, dated one-off offset under
custody.entries. A gap over the tolerance is an alarm the operator investigates, and it shows here before anyone tells you. Your deposit shows as a credit in/chain/depositswithin a minute or two of your transfer. - Change it
- Run it before and after any act of yours; the gap should not move by your trade.
Prices are fresh before anything is judged
- Proves
- No act and no number above is judged on stale prices.
- Run it
Per-asset price state curl -s https://protect.temporal.exchange/api/venue/health # oracleTier.BTC.tier, surfaceAgeMs, spotAgeMs- Expect
- Each asset's tier is "normal" (options data at most 15 minutes old, spot at most 60 seconds). A frozen asset refuses every open and close, and no one is liquidated on stale data.
- Change it
- Read it for the asset you trade; stop the audit when it is not normal and rerun after it recovers.
Making a market#
A maker is paid its own quote, and AUTO quotes the oracle
- Proves
- Fills at your own curve happen at your own prices, and an AUTO maker quotes exactly the oracle's bid and ask, with no lean.
- Run it
Your maker fills curl -s "https://protect.temporal.exchange/api/venue/chain/fills?wallet=0xYOUR_WALLET&account=all&limit=20" # rows with role "maker": priceUSD, qty, strikeUSD, monthsThe matching rule itself (price priority, pro-rata inside a level, nothing lost, each maker paid its own quote) is checked offline on the venue's exact arithmetic and on the real contract; that run is the protocol's first round and needs no key. You can reproduce the rule from the posted quotes and your own fills.
- Expect
- Your maker fill rows (role: maker) are at your posted price at that strike and decay, allowing for the book moving between your read and the block. A fill never takes you past your cap: a side at its cap is simply absent from the front. Fills at one price level split by standing size.
- Change it
- Post a CUSTOM curve above, at and below the AUTO price at one strike and compare which fills first: better prices fill first, equal prices share by size.
What only the operator runs#
Some checks need the price to move, and the price is the shared testnet's. Those are run by the operator with an oracle-scope key and a test session that holds the live feed for a bounded number of moves, ends by itself, and is announced on /health (testingBlock: new trades from other wallets are paused while it runs, with the end time). You never run them; their results are published.
Prices shaken: marks, equity and liquidation followOperator-run
- Proves
- When the options surface, the spot or the perp mark moves, every leg's mark moves the right way, account equity moves by the sum of its parts, the maintenance level updates and a liquidation happens exactly where the rule says.
- How it runs
Results are published as recorded walkthroughs: a video, a captioned report and a
results.jsonof every step and every check, listed with their verdicts in the venue'sdocs/qa/footage/INDEX.mdand kept in the venue's proof storage. The last five proof rounds are kept. Each row names its check, the number expected, the number observed and whether it passed.- Expect
- A call's mark falls as spot falls and a put's rises; the change in a mark per unit never exceeds the change in spot; equity moves by Σ(side × qty × mark change) plus the perp term; an account at or under its maintenance is flagged, one clear of it is not. After release the oracle is back on the live price. The closing identity: Σ real money = Σ venue P/L − Σ discrepancy, to 1 cent.
- Change it
- The operator sets the shock as a spec (surface ±vol and skew, spot ±%, perp ±%) or a preset (crash, rally, basis). Ask for a case you want covered by writing it in the feedback tab.
The whole protocol#
| Round | Proves | Public check above |
|---|---|---|
| Apportionment | Who fills an order, how much, at what price | Maker fills |
| Money | Closes, payouts and margin follow the ruled rules (R1 to R5) | Close, CLOSE ACCOUNT, money trail |
| Price / shake | Marks, equity and liquidation under moving prices | Operator-run; results published |
| Flow / arbitrage | No attempt ends richer than the spreads it paid | Round trip |
| Real wallet | A person with a browser wallet gets through PROTECT, sees the chain's numbers and gets the residual back | Equity, PROTECT floor |
A refusal is part of the test: an illegal act must be refused by name, and a refused act must change nothing. If you find a number that does not match, file it with the check, the expected and observed numbers and your transaction hash through the feedback tab.
Kept in lockstep with the code: a check fails if this page drifts.