The shape of an account

Three parts, and you own all of them

A Paddock account is not a Paddock contract. It is a Safe, with one module, and one small contract the module consults about price.

Safe v1.4.1 (L2)                      you are the only owner, threshold 1
 └── module: Zodiac Roles v2.1.0      owner = the Safe · avatar = the Safe
      ├── role PADDOCK_BUY            SwapRouter02.exactInputSingle, dollars in
      ├── role PADDOCK_SELL           SwapRouter02.exactInputSingle, dollars out
      └── the trader, one address, a member of both roles
           └── each swap is checked against:
                recipient        = this account            (EqualToAvatar)
                fee              → the Fence               (Custom)
                amountIn / min   ≤ dollars left today      (WithinAllowance)

The account's address is decided before it exists: Safe's factory deploys with CREATE2, so your address plus a fixed salt gives the same account address every time. That is why this site can find your accounts again with no database, no login and nothing stored in your browser.

Two roles rather than one branching role for an unglamorous reason. Roles can express "either this shape or that one" with an Or, but when an Or refuses a call it reports only OrViolation and throws away the reason underneath — so every explanation the Fence gives would have been lost, and this page could not tell you why your trade was refused. One role per direction keeps the reasons.

The permission

What the trader may call, exactly

One function, on one contract: exactInputSingle on Uniswap's SwapRouter02. Everything else — approvals, transfers, ether, any other contract, any other function — is refused by the module before it reaches the chain. Within that one function, each field is pinned:

FieldBuyingSellingWhy
tokenIn= USDGanythingEvery trade has dollars on one side, so the daily limit can be counted in dollars.
tokenOutanything= USDG
feethe Fence decidesThe pool is the trader's choice; the price is not. The condition hangs off this field, and the Fence reads the whole call.
recipient= this accountRoles compares it with the account's own address. This is what makes withdrawal impossible.
amountInwithin the daily limitDollars spent buying and dollars promised selling both count against one daily allowance, which refills every 24 hours.
amountOutMinimumwithin the daily limit
sqrtPriceLimitX96freeIt can only make a swap stop early, never pay less than the minimum.

The two settings that live inside the permission — the band and the freshness limit — are packed into twelve bytes the module carries with the condition. Nothing on chain can read a condition back, so the Fence answers a question instead: a swap of an impossible size is refused with its reason set to those settings, and this page reads them from the refusal. Your account tells you its own rules.

The Fence

The one contract that is ours

Roles can compare a field with a constant. It cannot ask whether a price is fair. So the fee field carries a Custom condition pointing at the Fence: 0x76EA82Df90d8926bE3488a109ae8FD7bA6D455B3.

The Fence is a 4,128-byte contract with no owner, no admin, no upgrade path and no storage anything can write after it is built. It cannot move a token. It is view: the module reaches it through a staticcall, and it can only answer yes or no.

What it does, for every swap:

  • refuses anything that is not exactInputSingle on the router it was built with;
  • refuses a swap that does not have USDG on exactly one side, or a stock it has no feed for;
  • reads Robinhood's feed for that stock and Robinhood's USDG/USD feed, and refuses if either has not printed within your freshness limit;
  • works out what the trade is worth at those prices, takes your band off it, and refuses unless amountOutMinimum is at least that;
  • refuses the router's two sentinel values — amountIn = 0 ("swap whatever the router is holding") and recipient = address(2) ("leave it in the router").

The floor it computes is the same number this site shows you before you trade — the page asks the contract, rather than doing the sum again in JavaScript where the two could drift apart. And the suite checks that number against a third calculation done outside the contract entirely, straight from the feeds: they agree to the wei.

The Fence is deployed through the standard CREATE2 deployer, so its address is a function of its bytecode and its stock list. Rebuild it from contracts/Fence.sol and you get the same address, or you have different code.

Which stocks

Listed, and not listed

A stock is listed only if Robinhood publishes a price feed for it on this chain and it has a Uniswap v3 pool against USDG with real money in it. Feeds are not enumerable, so they are found through Morpho's markets — each market names an oracle, and the real oracles name a Chainlink-style feed underneath — and then checked one by one.

At block 65,568,552, 244 Morpho markets named 235 oracles, which named 82 feeds. 26 stocks came out of that with a Robinhood feed of their own AND a Uniswap v3 pool against USDG holding at least $5,000. 23 did not, and here is every one of them.

Not listedWhy
GLDreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
RDDTreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
COSTreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
DJTreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
NFLXreads a Uniswap pool ("Uniswap V3 Pool Price in USD"); "ETH / USD" does not name NFLX in USD
RBLXreads a Uniswap pool ("Uniswap V3 Pool Price in USD"); "ETH / USD" does not name RBLX in USD
MRNAreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
RIVNreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
HIMSreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
TTWOreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
AMCreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
LLYreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
LULUreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
IBMreads a Uniswap pool ("Uniswap V3 Pool Price in USD")
CRWVno v3 pool against USDG
ORCLno v3 pool against USDG
COINno v3 pool against USDG
IONQdeepest v3 pool holds $0 (< $5000)
RGTIno v3 pool against USDG
RKLBdeepest v3 pool holds $1 (< $5000)
NBISno v3 pool against USDG
CLSKno v3 pool against USDG
EWYno v3 pool against USDG

Uniswap v4 is not used. Its swaps go through the Universal Router, which packs the recipient inside a command blob that no permission rule can read — so a v4 trade could not be pinned to your account, and four stocks that trade only in v4 are left out rather than let in on weaker terms.

Every check

What was actually run

Every number on this site comes out of one of these, and the build refuses to fill a figure nobody computed.

Properties, on a fork of the real chain
28/28
every refusal with a control that must succeed
Deliberate breaks caught
25/25
each by the property named for it, after a green control
Fuzzed operation sequences
2×30
eight invariants after every call; 8/8 planted breaks caught by the invariant named for each
Addresses read back on chain
20/20
4 controls that had to fail
The real page, clicked through
24/24
in Chrome, against a fork of this chain
The live site, in a browser
7/7
reading the real chain through this site's own proxy
Painted pixels, beside the reference
0.046 chroma
flattest frame 17.9% against its 16.1%, busiest 7.6 against 7.2 — measured in one run, same browser

The properties

Each one opens a real account on a fresh fork of Robinhood Chain — the real Safe factory, the real Roles module, the real Uniswap, the real feeds — and then tries to break it. Every "this is refused" carries a control in the same run proving the same call succeeds when the guard is removed.

#What it provesResult
P0the account opens at all: the Safe is made and its rules are set
36 calls in one owner transaction, 4,796,989 gas
pass
P1the account is the Safe the page predicted, owned by nobody else
1 module, 36 calls, 4,796,989 gas
pass
P2the module, the role and the allowance are exactly as configured
1 trader, $1000/day, 26 tokens approved to the router
pass
P3the trader sells a stock and the ACCOUNT gets the dollars
sold 0.5 AAPL for $168.08 to the account
pass
P4the trader buys a stock with the account dollars
$200 bought 0.5944 AAPL
pass
P5a swap paying the trader is refused (control: paying the account is not)
condition status 7
pass
P6a swap below the band is refused (control: at the floor it is not)
floor $33.0348 for 0.1 AAPL; one wei less is refused
pass
P7a stock with no Robinhood feed is refused (control: a listed one is not)
PADDOCK: stock not listed
pass
P8stock-for-stock is refused (control: through USDG it is not)
condition status 7
pass
P9over the daily limit is refused (control: under it is not)
$634.83 left of $1000; $635.83 refused
pass
P10another function on the router is refused (control: the scoped one is not)
condition status 3
pass
P11another contract is refused — the trader cannot transfer tokens
condition status 2
pass
P12a stranger with the same calldata is refused (control: the trader is not)
this address is not the trader on this account
pass
P13the trader cannot change the rules or spend as the owner
onlyOwner on the module, and Safe refuses a pre-validated signature from a non-owner
pass
P14the owner withdraws everything in one transaction
20.094352662945607 AAPL + $19968.08 out in one transaction
pass
P15the owner swaps the trader; the old one is refused, the new one is not
swapped; the list names one address
pass
P16the owner freezes trading and can still withdraw
frozen and unfrozen
pass
P17closing the account removes the module and every permission with it
module removed; the Safe and its contents are untouched
pass
P19the live permission reports its own band and freshness limit
200bp / 345600s, then 75bp / 7200s
pass
P20the Fence floor equals a floor computed outside the contract, from the feeds
sell floor 141577540 wei, buy floor 398291987147416835 wei, both within 1 wei of the outside sum
pass
P22the router's own sentinels are refused (CONTRACT_BALANCE, ADDRESS_THIS)
PADDOCK: zero amount in; PADDOCK: pays the router
pass
P23the Fence is deployed by the account itself, at the address the page predicted
4128 bytes, 26 stocks
pass
P24the allowance meters DOLLARS: what a buy spends, what a sale promises
buy −$25, sell −$16.52
pass
P25each role takes only its own direction: dollars in on buy, dollars out on sell
condition status 7
pass
P26the role and the module list each refuse a dropped trader on their own
this address is not the trader on this account; this address is not the trader on this account
pass
P27the Fence itself refuses what the permission also refuses (asked directly)
PADDOCK: neither side is USDG; PADDOCK: stock not listed
pass
P21the daily limit refills after the period and not before
spent $500, refilled to $500 after 86400s
pass
P18a stale feed refuses every trade (control: the same trade before the clock moves)
PADDOCK: price is stale
pass

The sabotage sweep

A green suite proves nothing on its own: a check that stopped checking looks exactly like a check that passes. So the build is broken on purpose, 25 ways — a condition loosened, a role misassigned, the price formula inverted — and each break must be caught by the property named for it.

25 of 25 were caught by the property named for them. None survived.

What can go wrong

Read this before you put anything in it

  • The Fence is not audited. It is short and it holds nothing, but it is new code, written for this site. If it were wrong in the dangerous direction it would let a trade settle further below the market than you asked for; the loss is still bounded by what the trader can move in a day.
  • A trader can still lose your money honestly. Trading within the band, all day, costs you money. The daily limit is the real bound: assume it can be lost.
  • The feeds are Robinhood's. If a feed prints a wrong price, the Fence enforces the wrong price. It can only refuse a feed that has stopped printing.
  • Thin pools. The band is measured against the feed, not against the pool, so a thin pool does not let a trader take more than the band — but it does mean trades often fail to fill. That is a nuisance, not a loss.
  • Your own key. The account is a 1-of-1 Safe: whoever holds your owner key holds the account. Paddock does not add recovery. If that key is the risk you are worried about, use a hardware wallet as the owner and a hot wallet as the trader — which is one of the reasons this exists.
  • This chain is young. State is pruned after minutes, log history is served by one node, and the public RPC throttles hard. That affects reading, not your money.
Addresses

Everything this site touches

WhatAddressChecked
SafeL2 1.4.10x29fcB43b…00C762code hash = Safe registry 0xb1f926978a… (v1.4.1)
SafeProxyFactory 1.4.10x4e1DCf7A…20ec67code hash = Safe registry 0x50c3cdc407… (v1.4.1)
CompatibilityFallbackHandler0xfd0732Dc…59Ec99code hash = Safe registry 0x7c6007a5d7… (v1.4.1)
MultiSendCallOnly 1.4.10x9641d764…8102e2code hash = Safe registry 0xecd5bd14a0… (v1.4.1)
Roles 2.1.0 mastercopy0x9646fDAD…43D337CREATE2(0xce0042…, registry initcode) = address; 24401 bytes live
Roles Packer 2.1.00x61C5B1bE…728a8CCREATE2(0xce0042…, registry initcode) = address; 2138 bytes live
Roles Integrity 2.1.00x6a6Af4b1…d41049CREATE2(0xce0042…, registry initcode) = address; 5637 bytes live
Zodiac ModuleProxyFactory0x00000000…Dda2362046 bytes, verified source in research/src/moduleFactory
role key0x9646fDAD…43D337bytes32("PADDOCK_TRADER"), bytes32("PADDOCK_DAILY_USDG")
USDG0x5fc5360D…F1d168USDG, 6 decimals
USDG / USD feed0x61B7e565…1B9aD2"USDG / USD", 8 dp, 0.99991 at 2026-09-17T15:35:04.000Z
SwapRouter020xCaf681a6…9E5cb2factory() = v3Factory; WETH9 0x0Bd7D308…
QuoterV20x33e885eD…c8A9E7factory() = v3Factory
v3 factory0x1f7d7550…FD2EfA24535 bytes
Multicall30xcA11bde0…76CA113808 bytes
CREATE2 deployer0x4e59b448…B4956Cruntime hash 0x2fa86add0a… (Arachnid)

Each row was read back on the live chain by tools/verify-infra.mjs, with controls that must fail: a random address, one registry hash held against the wrong contract, and the Roles initcode with a single byte changed.