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.
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:
| Field | Buying | Selling | Why |
|---|---|---|---|
tokenIn | = USDG | anything | Every trade has dollars on one side, so the daily limit can be counted in dollars. |
tokenOut | anything | = USDG | |
fee | the Fence decides | The 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 account | Roles compares it with the account's own address. This is what makes withdrawal impossible. | |
amountIn | within the daily limit | — | Dollars spent buying and dollars promised selling both count against one daily allowance, which refills every 24 hours. |
amountOutMinimum | — | within the daily limit | |
sqrtPriceLimitX96 | free | It 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 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
exactInputSingleon 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
amountOutMinimumis at least that; - refuses the router's two sentinel values —
amountIn = 0("swap whatever the router is holding") andrecipient = 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.
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 listed | Why |
|---|---|
| GLD | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| RDDT | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| COST | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| DJT | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| NFLX | reads a Uniswap pool ("Uniswap V3 Pool Price in USD"); "ETH / USD" does not name NFLX in USD |
| RBLX | reads a Uniswap pool ("Uniswap V3 Pool Price in USD"); "ETH / USD" does not name RBLX in USD |
| MRNA | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| RIVN | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| HIMS | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| TTWO | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| AMC | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| LLY | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| LULU | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| IBM | reads a Uniswap pool ("Uniswap V3 Pool Price in USD") |
| CRWV | no v3 pool against USDG |
| ORCL | no v3 pool against USDG |
| COIN | no v3 pool against USDG |
| IONQ | deepest v3 pool holds $0 (< $5000) |
| RGTI | no v3 pool against USDG |
| RKLB | deepest v3 pool holds $1 (< $5000) |
| NBIS | no v3 pool against USDG |
| CLSK | no v3 pool against USDG |
| EWY | no 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.
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.
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 proves | Result |
|---|---|---|
| P0 | the account opens at all: the Safe is made and its rules are set 36 calls in one owner transaction, 4,796,989 gas | pass |
| P1 | the account is the Safe the page predicted, owned by nobody else 1 module, 36 calls, 4,796,989 gas | pass |
| P2 | the module, the role and the allowance are exactly as configured 1 trader, $1000/day, 26 tokens approved to the router | pass |
| P3 | the trader sells a stock and the ACCOUNT gets the dollars sold 0.5 AAPL for $168.08 to the account | pass |
| P4 | the trader buys a stock with the account dollars $200 bought 0.5944 AAPL | pass |
| P5 | a swap paying the trader is refused (control: paying the account is not) condition status 7 | pass |
| P6 | a 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 |
| P7 | a stock with no Robinhood feed is refused (control: a listed one is not) PADDOCK: stock not listed | pass |
| P8 | stock-for-stock is refused (control: through USDG it is not) condition status 7 | pass |
| P9 | over the daily limit is refused (control: under it is not) $634.83 left of $1000; $635.83 refused | pass |
| P10 | another function on the router is refused (control: the scoped one is not) condition status 3 | pass |
| P11 | another contract is refused — the trader cannot transfer tokens condition status 2 | pass |
| P12 | a stranger with the same calldata is refused (control: the trader is not) this address is not the trader on this account | pass |
| P13 | the 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 |
| P14 | the owner withdraws everything in one transaction 20.094352662945607 AAPL + $19968.08 out in one transaction | pass |
| P15 | the owner swaps the trader; the old one is refused, the new one is not swapped; the list names one address | pass |
| P16 | the owner freezes trading and can still withdraw frozen and unfrozen | pass |
| P17 | closing the account removes the module and every permission with it module removed; the Safe and its contents are untouched | pass |
| P19 | the live permission reports its own band and freshness limit 200bp / 345600s, then 75bp / 7200s | pass |
| P20 | the 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 |
| P22 | the router's own sentinels are refused (CONTRACT_BALANCE, ADDRESS_THIS) PADDOCK: zero amount in; PADDOCK: pays the router | pass |
| P23 | the Fence is deployed by the account itself, at the address the page predicted 4128 bytes, 26 stocks | pass |
| P24 | the allowance meters DOLLARS: what a buy spends, what a sale promises buy −$25, sell −$16.52 | pass |
| P25 | each role takes only its own direction: dollars in on buy, dollars out on sell condition status 7 | pass |
| P26 | the 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 |
| P27 | the Fence itself refuses what the permission also refuses (asked directly) PADDOCK: neither side is USDG; PADDOCK: stock not listed | pass |
| P21 | the daily limit refills after the period and not before spent $500, refilled to $500 after 86400s | pass |
| P18 | a 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.
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.
Everything this site touches
| What | Address | Checked |
|---|---|---|
| SafeL2 1.4.1 | 0x29fcB43b…00C762 | code hash = Safe registry 0xb1f926978a… (v1.4.1) |
| SafeProxyFactory 1.4.1 | 0x4e1DCf7A…20ec67 | code hash = Safe registry 0x50c3cdc407… (v1.4.1) |
| CompatibilityFallbackHandler | 0xfd0732Dc…59Ec99 | code hash = Safe registry 0x7c6007a5d7… (v1.4.1) |
| MultiSendCallOnly 1.4.1 | 0x9641d764…8102e2 | code hash = Safe registry 0xecd5bd14a0… (v1.4.1) |
| Roles 2.1.0 mastercopy | 0x9646fDAD…43D337 | CREATE2(0xce0042…, registry initcode) = address; 24401 bytes live |
| Roles Packer 2.1.0 | 0x61C5B1bE…728a8C | CREATE2(0xce0042…, registry initcode) = address; 2138 bytes live |
| Roles Integrity 2.1.0 | 0x6a6Af4b1…d41049 | CREATE2(0xce0042…, registry initcode) = address; 5637 bytes live |
| Zodiac ModuleProxyFactory | 0x00000000…Dda236 | 2046 bytes, verified source in research/src/moduleFactory |
| role key | 0x9646fDAD…43D337 | bytes32("PADDOCK_TRADER"), bytes32("PADDOCK_DAILY_USDG") |
| USDG | 0x5fc5360D…F1d168 | USDG, 6 decimals |
| USDG / USD feed | 0x61B7e565…1B9aD2 | "USDG / USD", 8 dp, 0.99991 at 2026-09-17T15:35:04.000Z |
| SwapRouter02 | 0xCaf681a6…9E5cb2 | factory() = v3Factory; WETH9 0x0Bd7D308… |
| QuoterV2 | 0x33e885eD…c8A9E7 | factory() = v3Factory |
| v3 factory | 0x1f7d7550…FD2EfA | 24535 bytes |
| Multicall3 | 0xcA11bde0…76CA11 | 3808 bytes |
| CREATE2 deployer | 0x4e59b448…B4956C | runtime 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.