Hand over your key
The bot signs as you. It can place the trades you wanted — and it can also transfer everything you own to anywhere it likes.
Everything below moves when you move it. Drag the fences, spend the budget, walk a trade through to the moment the chain undoes it. The prices are read from the XPR testnet while you read.
A new account that belongs to you, with a small program on it. The program keeps your limits, checks the price itself, and has nowhere to send your money except the exchange and back.
There are normally two deals on offer, and they end the same way when the operator is hacked.
The bot signs as you. It can place the trades you wanted — and it can also transfer everything you own to anywhere it likes.
You deposit into the operator’s wallet or pool. What you hold afterwards is a promise, and the tokens are theirs to move.
Your tokens stay in an account you own. The rules live in a small contract on that account, and our key reaches one action inside limits you set.
The contract that holds your rules lives on your own account. Here is every action on it, and what happens when you filter it down to the ones our key can open.
There is no withdraw in that list, and no action anywhere on the contract that names a recipient. The only address the contract can send tokens to is the exchange, and the same transaction pulls them back.
The contract reads the chain’s own price feed, checks it is fresh and backed by several providers, and draws a corridor around it. A sale must be priced above the floor and a purchase below the ceiling.
A buy cannot fill right now: the exchange is asking 0.003920 and your ceiling is 0.002530. The bot will skip the tick rather than pay more.
sellFloorRaw(pxBid, pxAsk, 100, 6) = 2433 buyCeilRaw (pxBid, pxAsk, 300, 6) = 2530 pxBid = 0.0024566 USD (oracles::data feed 3, aggregate) pxAsk = 1.0 (XMD is pinned, so no feed is needed)
Both functions are imported from packages/shared, which is a bit-for-bit mirror of the contract’s own arithmetic. Bands are hard-capped at 10% in the code, so no signature — yours or ours — can widen them past that.
A price band alone is not a budget. The bot before ours had a $600 per-call cap and no limit at all on how many calls it could make. These live in the contract’s own storage, not in our configuration.
$36 is left in this 24-hour window. The next trade would be at most $25 — the smaller of your per-trade cap and what the budget has room for.
The budget is measured in dollars. A keep floor is measured in tokens, on each side of the market, and it is absolute: whatever the budget says, the contract refuses any trade that would leave you holding less than the floor. It is what stops a leaked key converting your whole position into one asset over a few days.
The wizard will not enable a market with a zero keep on either token without making you say so explicitly.
All of it is a single transaction. Step through it, then break it on purpose and watch the chain undo every part.
It names the next nonce, the market, the side, an amount, and its own limit price. That is the entire surface it has.
trade(nonce, market_id, side, amount, limit) — authorised by vault@keeper
Assume our servers are already breached and the thief is patient and clever. They clear the book, rest their own orders exactly at your band edge, and trade against themselves with our key. This is what that costs you.
$500 × 1% = $5.00. That is the number the chain can prove, because the contract bounds the price against the feed. The second figure adds the 1.9% gap between the feed and the exchange, which the chain cannot prove and we will not pretend it can. The thief also burns about $0.50 in exchange fees getting there.
The window edge. The budget is a 24-slot hourly ring, so the honest statement is “no more than your budget in any rolling 23 hours”. Across a window edge roughly twice the budget can move in about 24 hours. Over D days undetected, the ceiling is (D+1) × budget × band.
Direction, not just value. A thief can convert everything above your keep floors into one asset at band-edge prices. The direct loss is still bounded by the band, but the exposure you end up holding is not the one you chose. Keep floors on both tokens are what bound that, and converting a position of V takes at least V / budget days.
Computed by worstCasePerDayMicro(budget, band) in packages/shared, the mirror of the contract’s own maths.
You sign two transfers. We build the vault. The last action of the build removes us from it, and you can read that off the chain.
Its owner is already the final two-of-three. A wallet cannot change an owner key afterwards, so the vault is born with the right owners and nobody ever signs an owner change.
This really happened on the XPR testnet on 17 September 2026: 406bf9…8eb3 (opens the block explorer in a new tab)
An ordinary transfer, signed with your own key, straight out of your vault. No contract action is involved, no request comes to us, and there is no exit fee the contract could enforce even if we wanted one. It works whether or not we are still in business.
Two of your three seats can delete the keeper permission entirely and leave the vault sitting there with your tokens in it, still yours, still pausable, simply no longer trading.
One signature from your wallet pauses everything, immediately, without going through any server of ours. Starting it again takes two seats — tightening is always easier than loosening.
The two testnet vaults on this page are real: xcbvault1 has traded, and xcbvault2 was built by the exact flow described above.