Docs

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:

  1. makes your wallet its only owner, with a threshold of one;
  2. delegate-calls Safe's own SafeModuleSetup.enableModules([recovery module]), which switches the module on (a module can only be enabled by the Safe itself);
  3. calls the module's addGuardianWithThreshold once 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

  1. Each keyholder calls confirmRecovery(bothy, [new wallet], 1, start). It records their agreement for that exact new wallet.
  2. 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.
  3. During those seven days the owner can call cancelRecovery through the bothy. Bothy also calls invalidateNonce in the same transaction, so every agreement given so far is void.
  4. 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.

ContractAddressSame bytes
USDG (Global Dollar, Paxos)0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168per chain
Uniswap SwapRouter020xCaf681a66D020601342297493863E78C959E5cb2per chain
Uniswap QuoterV20x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7per chain
Uniswap v3 Factory0x1f7d7550B1b028f7571E69A784071F0205FD2EfAper chain
Multicall30xcA11bde05977b3631167028862bE2a173976CA11yes
Safe 1.4.1 SafeProxyFactory0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67yes
Safe 1.4.1 SafeL2 (singleton)0x29fcB43b46531BcA003ddC8FCB67FFE91900C762yes
Safe 1.4.1 CompatibilityFallbackHandler0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99yes
Safe 1.4.1 MultiSendCallOnly0x9641d764fc13c8B624c04430C7356C1C7C8102e2yes
Safe 1.4.1 MultiSend0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526yes
Safe SafeModuleSetup 0.3.00x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47yes
CANDIDE Social Recovery Module 0.0.1 (7 days)0x088f6cfD8BB1dDb1BB069CCb3fc1A98927D233f2yes

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.

PropertyWhat was shownChecks
create4 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
rules4 bad keyholder sets refused by the page AND by the chain; 4 bad additions refused by the module with its own reasons27
moveUSDG, a stock and ETH moved in and out to the unit (odd amounts), one-by-one and batched42
ownerkeyholders and strangers refused with Safe's own codes (GS025, GS026); the module refuses them too; the owner goes through11
tradebought 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.6054
buyin$250.000003 from the wallet became 1.082904 NVDA inside the bothy in one transaction, exactly as quoted; the permit cannot be replayed8
recover2 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 refused24
cancelremoving 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 voided17
heira 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 zero9
keysadded 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 for21
findthe build announces one event per keyholder; each keyholder finds the bothy from those events alone, a stranger finds nothing6

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).