How a bothy works
Every transaction, every address and every limit, in plain words. Nothing here is ours on chain: it is Safe, CANDIDE and Uniswap.
What a bothy is
A bothy is a Safe 1.4.1 smart account with exactly one owner — the wallet you already use — and one module switched on: CANDIDE's Social Recovery Module 0.0.1, which holds a list of keyholders and a number of them that must agree.
- Because you are the only owner and the Safe needs one signature, your wallet acts alone, one transaction at a time. Bothy sends each action as a Safe transaction that your wallet submits itself, so nothing is signed off-chain and nothing can be replayed.
- The module can do one thing to your Safe: after enough keyholders agree and seven days pass without the owner cancelling, it swaps the owner for the wallet they named. It cannot move a token.
- Bothy deploys nothing and holds nothing. It is these pages, and the transactions they build (in
js/bothy.js, which the tests run).
Building one
One transaction to the Safe factory, createProxyWithNonce(SafeL2, setup(…), salt). Inside setup, the new Safe:
- makes your wallet its only owner, with a threshold of one;
- delegate-calls Safe's own
SafeModuleSetup.enableModules([recovery module]), which switches the module on (a module can only be enabled by the Safe itself); - calls the module's
addGuardianWithThresholdonce per keyholder, raising the threshold only with the last one (the module refuses a threshold above the keyholders named so far).
The page predicts the bothy's address before you send (CREATE2), and the tests require the factory's own event to name that exact address. It costs 463k gas.
Using it
Adding
Move in is a plain transfer from your wallet to the bothy. Buy in is one call to Uniswap's SwapRouter02: multicall([selfPermit(USDG…), exactInputSingle(…, recipient: your bothy)]), after you sign an EIP-2612 permit for exactly the dollars you spend. The shares go straight to the bothy.
Trading and sending
Each is one Safe execTransaction from your wallet, with Safe's "approved by the sender" signature. A trade is a batch through Safe's MultiSendCallOnly: approve(router, exactly the amount), then exactInputSingle with the bothy as recipient and a minimum 0.5% under Uniswap's own quote. If the minimum is not met, Uniswap refuses and nothing moves.
Keyholders
Adding, removing and changing how many must agree are calls the bothy itself makes to the module (addGuardianWithThreshold, revokeGuardianWithThreshold, changeThreshold). The module listens only to the Safe it protects, so nobody else can change them.
The handover
- Each keyholder calls
confirmRecovery(bothy, [new wallet], 1, start). It records their agreement for that exact new wallet. - When as many as you chose have agreed, the last one (or anyone) starts the clock:
executeRecovery. The module writes the moment it can finish: now plus seven days. - During those seven days the owner can call
cancelRecoverythrough the bothy. Bothy also callsinvalidateNoncein the same transaction, so every agreement given so far is void. - After seven days,
finalizeRecovery(bothy)— callable by anyone — removes the old owner and makes the new wallet the only owner. The keyholders stay.
The seven days are an immutable in the module's code: this site reads them out of the deployed bytecode on every scan, and checks that the 3- and 14-day values of CANDIDE's sibling deployments are absent.
Limits, honestly
- The new owner cannot be a keyholder. The module lets such a handover start and refuses it only at the very end, a week later. Bothy refuses it on day zero. An heir who is also a keyholder uses a second account.
- Removing a keyholder does not stop a running handover in version 0.0.1 of the module, which is the one deployed here (the tests show it). Cancel the handover first — Bothy's alert has the button.
- Nobody will phone you. A handover is visible on chain and on your bothy's page the moment it starts, but no email or notification is sent. Choose keyholders who would not act without talking to you, and more than one of them.
- Agreeing costs keyholders gas — a fraction of a cent in ETH on Robinhood Chain. They need a little ETH.
- Trades use one pool per stock: the deepest USDG pool Bothy's scan found. Very large trades would do better split across pools.
Addresses
Every contract a bothy touches, on Robinhood Chain (4663). "Same bytes" means the deployed code is byte-for-byte identical to the same address on Arbitrum and on Base — the same audited build, not a look-alike. Read at block 76,732,680, 30 Sep 2026.
| Contract | Address | Same bytes |
|---|---|---|
| USDG (Global Dollar, Paxos) | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 | per chain |
| Uniswap SwapRouter02 | 0xCaf681a66D020601342297493863E78C959E5cb2 | per chain |
| Uniswap QuoterV2 | 0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7 | per chain |
| Uniswap v3 Factory | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA | per chain |
| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 | yes |
| Safe 1.4.1 SafeProxyFactory | 0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67 | yes |
| Safe 1.4.1 SafeL2 (singleton) | 0x29fcB43b46531BcA003ddC8FCB67FFE91900C762 | yes |
| Safe 1.4.1 CompatibilityFallbackHandler | 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99 | yes |
| Safe 1.4.1 MultiSendCallOnly | 0x9641d764fc13c8B624c04430C7356C1C7C8102e2 | yes |
| Safe 1.4.1 MultiSend | 0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526 | yes |
| Safe SafeModuleSetup 0.3.0 | 0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47 | yes |
| CANDIDE Social Recovery Module 0.0.1 (7 days) | 0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2 | yes |
Tests
The last run: 11/11 properties and 259 checks passed against live state at block 76,754,183 (30 Sep 2026). Each property builds its transactions with the page's own js/bothy.js and runs them with eth_simulateV1 against the real Safe, recovery module and Uniswap pools, from test wallets with real keys. Nothing is broadcast.
| Property | What was shown | Checks |
|---|---|---|
| create | 4 bothies built, each at the exact address the page predicted, owned by the reader alone, recovery on, the right keyholders and threshold: 1-of-1 (396k gas), 2-of-2 (430k gas), 2-of-3 (463k gas), 3-of-5 (527k gas) | 40 |
| rules | 4 bad keyholder sets refused by the page AND by the chain; 4 bad additions refused by the module with its own reasons | 27 |
| move | USDG, a stock and ETH moved in and out to the unit (odd amounts), one-by-one and batched | 42 |
| owner | keyholders and strangers refused with Safe's own codes (GS025, GS026); the module refuses them too; the owner goes through | 11 |
| trade | bought and sold inside the bothy at Uniswap's quote to the unit, no allowance left, a missed minimum refused: NVDA 400→1.7315→$399.60; TSLA 400→1.1317→$399.60 | 54 |
| buyin | $250.000003 from the wallet became 1.082904 NVDA inside the bothy in one transaction, exactly as quoted; the permit cannot be replayed | 8 |
| recover | 2 of 3 keyholders agreed; refused with 1 and from a stranger; refused a minute before 7 days; finished by a stranger a minute after; the new wallet owns and spends it, the old one is refused | 24 |
| cancel | removing a keyholder did not stop it; cancelled on day 3; refused on day 8; agreements given before the cancel (even quietly renewed ones) cannot restart it; a keyholder cannot cancel; a pending agreement voided | 17 |
| heir | a keyholder named as the new owner starts fine and is refused a week later with "new owner cannot be guardian" — the page refuses it on day zero | 9 |
| keys | added one (3 of 4), removed from the middle, head and tail of the module's list, lowered the threshold, removed the last; every step left exactly the set asked for | 21 |
| find | the build announces one event per keyholder; each keyholder finds the bothy from those events alone, a stranger finds nothing | 6 |
Then a sabotage sweep plants 25 bugs, one at a time, in a copy of js/bothy.js — a keyholder dropped, a trade with the wrong recipient, a handover to the wrong wallet, a cancel that forgets to void — and requires the property named for each one to fail. 25/25 were caught.
The browser run: 7/8 journeys (30 checks) clicked through the real pages in Chrome with test wallets, against a private copy of the live chain (30 Sep 2026).