05 Contract

Architecture

The pieces of the system, what each one is trusted with, and how the JavaScript engine is kept honest against the Solidity.

#The pieces

┌──────────────────────┐    ┌─────────────────────────┐
│ Web app (static)     │    │ Relayer / undo bot      │
│ home, lab, docs      │    │ (designs, not running)  │
│ • ethers             │    │ • submits signed claims │
│ • secret stays local │    │ • points to undo page   │
└──────────┬───────────┘    └────────────┬────────────┘
           │ signed transactions          │ transactions the user or a relayer signs
           ▼                              ▼
┌───────────────────────────────────────────────────────┐
│ Robinhood Chain (4663): Oopsend.sol                    │
│ links[linkKey] · feesAccrued[token] · immutable fee wallet │
└───────┬───────────────────────────────┬───────────────┘
        │ events                         │ sweepFees (anyone)
        ▼                                ▼
┌────────────────┐             ┌─────────────────────────┐
│ Indexer / app  │             │ Fee wallet (public)     │
│ "My links"     │             │ buy $OOPS → burn     │
└────────────────┘             └─────────────────────────┘

#Components and trust

ComponentWhat it doesTrust required
Oopsend.solHolds escrowed funds and is the only thing that can move themNone beyond the code: no proxy, no admin withdrawal
Web appGenerates the secret, builds links, signs claims in the browserThe page you load could be malicious, so check the domain. It cannot move funds without a signature you approve
RelayerSubmits a signed claim and pays gasNone for safety: it cannot change the recipient. It can delay or refuse, and you can submit yourself
Undo botReplies to a tweet with a link to the undo pageNone: it holds no keys, it only points
IndexerRebuilds "My links" from eventsNone for funds. If it is down you lose convenience, not money
Fee walletReceives swept fees, buys back and burns $OOPSA team-run wallet in V1: burns depend on the team executing them

#Off-chain data

None is required. A link is self-describing: the sender and the id are in the path, the secret is in the fragment, and the escrow record is readable with one call, links(linkKey(sender, id)). The app can list a sender's links from LinkCreated events filtered on the indexed sender topic. The event carries the link id, so a sender who switched device can still undo(id) any open link. Only the secret, needed to re-share a link, is not recoverable from chain data.

#Why a plain contract

Oopsend is one contract with no proxy, no hooks, and no external calls except token transfers and ETH payouts. Every function either creates a record, deletes a record while paying it out, or tunes parameters. That is a deliberate shape: a small surface is easier to read and to test.

#The JavaScript engine

The lab and the live attacks on the home page run site/assets/engine.js, a JavaScript port of Oopsend.sol. It has the same storage, the same checks in the same order, the same custom-error names and 4-byte selectors, and the same EIP-712 digest. It uses real secp256k1 keys and signatures through ethers. What it does not have is a chain: the ledger is local memory and the clock is simulated.

A port is only worth something if it cannot drift. Two safeguards:

  1. Shared vectors. The Solidity suite writes contracts/test/vectors.json: the EIP-712 domain separator, six link keys, six digests and six signatures, plus error selectors. tests-js/engine.test.mjs recomputes all of them in JavaScript and asserts equality. ECDSA signing in ethers is deterministic (RFC 6979), so even the signature bytes match Foundry's.
  2. Mirrored scenarios. The JS suite replays the same scenarios as the Solidity suite: claim, undo, expiry, front-run, wrong key, caps, pause, fee locked at creation, fee-on-transfer, and a conservation check (the escrow's balance equals open links plus unswept fees).

At the time of writing: 30 Foundry tests plus 1 fork test pass, and 15 JavaScript tests pass.

#Repository layout

PathContents
contracts/src/Oopsend.solthe contract
contracts/test/Oopsend.t.solFoundry unit, fuzz, re-entrancy and fork tests
contracts/test/vectors.jsonvectors written by Solidity, read by JavaScript
site/assets/engine.jsthe JavaScript port
tests-js/engine.test.mjsparity tests for the port
scripts/docs-e2e.mjsa real end-to-end run on a local anvil, used by these docs

#Deployment

ChainRobinhood Chain, id 4663
Contract0x7B530e91D9dc24C78Ca2eEE2B494E7dE1B680AC0
Deploy block90156561
Deploy transaction0x877015a2d1ff4810350bd413ab139e409a35a82a98e495c08c1311ccaca2da75
Admin0x59B6919AdC07a3b6a6D7b91f7773A721bD0c6c73
Fee wallet (immutable)0x59B6919AdC07a3b6a6D7b91f7773A721bD0c6c73
Fee, caps30 bps, 2 ETH per ETH link, 1% of total supply per token link
Explorerrobinhoodchain.blockscout.com/address/0x7B530e91D9dc24C78Ca2eEE2B494E7dE1B680AC0

Check this address against the one on the home page and in the site header before approving anything. The same file is in the repository as contracts/deployment.json.