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.

CallWhat it doesMessage?
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

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

  1. 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.
  2. Robinhood Chain and the far chain themselves.
  3. 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.
  4. The hub owner can set peers and messaging config. It cannot move tokens, credit or quote.