How it works.
Pairpad is two contracts and one message format. The hub lives on Robinhood Chain and holds every token, every curve and every balance. A custody lives on each far chain and holds the quote asset. They talk over LayerZero V2. The custody never computes a price; the hub never touches the quote asset.
The hub
The hub contract is Pairpad.sol (v2; v1 Farpad.sol is retired and drained). launch() deploys a fixed-supply ERC-20 of 1,000,000,000 tokens straight into the hub and opens a pair: which far chain (eid), which asset there (bytes32, zero for the chain's native asset), and the opening depth y0 in that asset's raw units.
The pair is a constant-product curve with virtual reserves. Token reserve starts at X0 = 1.125 × supply, quote reserve at y0. The extra 12.5% supply on the curve keeps the price finite when the real supply runs out: the last token sells at (X0 / (X0 − supply))² = 81× the opening price. Division rounds toward the pool, so k never falls.
Three balances matter. The token, an ordinary ERC-20. Far credit, a claim on a custody, keyed by (eid, asset, owner). And per pair, y − y0, the real quote that pair's buyers have put in, which sits in the custody too.
| Call | What it does | Message? |
|---|---|---|
| launch(…) | New token, new pair. Needs a custody peer for that eid. | no |
| buy(id, quoteIn, minOut) | Spend far credit on the curve. Fee first, then the fill; unused quote (curve sold out) comes back as credit. | no |
| sell(id, coinIn, minOut) | Tokens back on the curve. Proceeds minus fee become far credit. | no |
| transferCredit(eid, asset, to, amount) | Move credit between hub addresses. | no |
| withdraw(eid, asset, to, amount, options) | Burn credit here, send PAYOUT. Caller pays the LayerZero fee in ETH. | hub → custody |
| attestDeposit(eid, ref, id, asset, recipient, amount, minOut) | Attested rails only. The named relayer reports a deposit; capped per window, one report per far-chain signature. Then the same fill-or-credit path. | relayer |
| lzReceive(DEPOSIT) | Fill at the price on arrival. Anything that cannot fill (unknown pair, wrong asset, minOut missed, curve full) is written down as credit to the recipient. Never reverts on a valid packet. | custody → hub |
The custody
FarSide.sol on each EVM far chain, one per chain, shared by every pair quoted there. depositNative() and depositToken() lock the asset and send a DEPOSIT to the hub. On a PAYOUT from the hub it transfers the asset to the address named. It accepts messages from the hub's eid only.
Solana has no custody program yet, so it runs as an attested rail. The custody is a plain wallet, D2NKuueCLSNJkrLcX4UuKivgYKJCNsKxHBvhABX2FSJe. A depositor sends SOL to it with a memo pp:<pairId>:<0xRobinhoodRecipient>:<minOut>; the app builds that transaction for Phantom. A relayer, whose address the hub owner sets as rail(30168).attester, reports the deposit with attestDeposit, using the Solana signature as a once-only reference. The hub then does exactly what it does for a LayerZero deposit.
Withdrawals on that rail post a PayoutRequested event instead of a message, with no fee; the relayer pays SOL from the custody wallet, net of the 0.000005 SOL network fee, and records the Solana signature with markPaid. The wallet keeps 0.001 SOL as rent reserve.
What bounds the relayer: it can only mint credit up to rail.cap per rolling window, 5 SOL an hour today, and every report needs a fresh Solana signature. A dishonest relayer is therefore worth at most the cap per hour, and the owner can close the rail with one call. This is a weaker trust model than the two-DVN LayerZero path to Base, and the app labels every Solana pair with it.
Solvency
For each pair, a sell pays y − ceil(k / (x + dx)). Every token in circulation came off the curve, so x + dx ≤ X0; and k ≥ X0·y0 because rounding only ever raises it. Hence k / (x + dx) ≥ y0, and the payout is at most y − y0: exactly the real quote that pair has taken in. No sell can reach into another pair's quote or anyone's credit.
Credits are only ever minted against a reported deposit or a curve sale, and only ever burned by withdraw. So at all times, per (eid, asset):
custody balance ≥ totalCredit(eid, asset) + Σ over pairs in that asset of (y − y0)
Both sides of that line are public reads. The landing page shows them for Base ETH; a fuzz test in the repo asserts it after every random deposit, sell, credit buy and withdraw across two pairs, and the gap is rounding dust.
Wire format
Fixed-width, big-endian, so a Solana program decodes with offsets and nothing else.
DEPOSIT, 105 bytes, custody → hub [0] kind = 1 [1..9) pairId uint64 [9..41) asset bytes32 far-chain asset id, 0x0 = native [41..73) recipient bytes32 hub address, left padded [73..89) amount uint128 raw units, fee not yet taken [89..105) minOut uint128 least tokens acceptable, else credited PAYOUT, 81 bytes, hub → custody [0] kind = 2 [1..33) asset bytes32 [33..65) to bytes32 far-chain account [65..81) amount uint128
Messaging
- LayerZero V2 to Base. Hub endpoint id 30416, Base 30184. Solana (30168) is an attested rail, see above.
- Both directions require two DVNs, LayerZero Labs and Nethermind, at 10 block confirmations. These pathways have no default DVN on these chains, so the OApps set their own send and receive ULN config. A single-DVN, 5-confirmation config was never verified on Base → Robinhood; the two-DVN config that other live OApps on the pathway use verified in under a minute.
- Receive gas is enforced by the sender's OApp: 250,000 for a DEPOSIT landing on the hub, 120,000 for a PAYOUT landing on a custody. A depositor cannot underpay it.
- If delivery runs out of gas anyway, the packet is stored and anyone can re-execute it. The deposit stays in custody throughout.
Fees
Launching is free. Each pair has a trading fee of at most 3%, chosen by its creator, taken on the quote leg both ways. The creator picks how much of it is theirs; the rest goes to the protocol treasury. Both accrue as far credit, and are withdrawn like any other credit.
The maker
A bot with an ordinary wallet, funded from the treasury's fee credit. Per pair it keeps an exponential moving average of the curve price. When the price sits below the average by more than twice the fee it buys a slice with credit; above, it sells a slice of tokens. Slice size scales with the distance and is capped at a fraction of inventory. It has no special rights in the hub, and anyone can run the same code with their own credit.
What you are trusting
- On the Solana rail: one relayer, bounded to 5 SOL of new credit per hour and closable by the owner. Its custody wallet is a plain keypair.
- Robinhood Chain and the far chain themselves.
- The two DVNs (LayerZero Labs and Nethermind, both required) and the executor, for delivery of messages. Both DVNs would have to collude to forge a DEPOSIT, which mints credit. The custody code caps payouts per asset per rolling window and queues the rest for anyone to release later, which bounds even that per hour. The Base custody live today was deployed before that cap existed and does not have it; the next custody deploy ships with it.
- The hub owner can set peers and messaging config. It cannot move tokens, credit or quote.