05 · 6 min read
Architecture
Two contracts on chain, two routes on the server, and one rule that holds them together: the contract that keeps the books never holds the tokens.
01The split#
| Holds tokens | Keeps the books | Talks to SaucerSwap | |
|---|---|---|---|
YieldLinks | No | Yes | No |
SauceStakingSource | Yes | No | Yes |
YieldLinks tracks links, shares, signatures and expiries. Every token sits in the source, which is bound once to one YieldLinks and obeys only it. Swap the source and the escrow logic never changes.
Sender ──createLink──▶ YieldLinks ──deposit──▶ SauceStakingSource ──enter──▶ SaucerSwap
│ (holds xSAUCE) Infinity Pool
Relayer ───claim────────▶ │ ──withdraw──▶ source ──leave──▶ pool
│ │
│ └──HIP-904 airdrop (0x167)──▶ recipient
└── fee ──▶ relayer02The contracts#
| Contract | Job |
|---|---|
YieldLinks | Escrow, pooled share accounting, EIP-712 claims, refunds, prepaid fees |
IYieldSource | The seam: three functions every yield venue implements |
ControlledSource | One-time controller binding, HBAR fee reserve, HIP-904 payout |
SauceStakingSource | Stakes SAUCE in SaucerSwap's Infinity Pool, values xSAUCE at the pool ratio |
IdleSource | No yield. The reference implementation, used on plain Anvil. |
HtsLib | Token association and airdrops through 0x167, no-ops where HTS does not exist |
script/Deploy.s.sol picks the source by chain id: Hedera testnet, mainnet and a local fork (chain id 296) get SauceStakingSource, anything else gets IdleSource. It binds the source, then writes packages/nextjs/contracts/deployedContracts.ts, which is what the frontend reads.
03Pooled shares#
Each token has one pool of shares. Nothing is written per link when the pool earns.
// minting, in createLink
shares = deposit * (totalShares + 1000) / (totalAssets + 1000)
// valuing, in claimable / claim / refund
value = min(shares * (totalAssets + 1000) / (totalShares + 1000), totalAssets)totalAssets is the SAUCE value of the source's xSAUCE plus any idle SAUCE, read live from the pool. When SaucerSwap's pool earns, every open link is worth more at once.
+ 1000 virtual offset stops a first depositor from inflating the share price against later users. It costs a permanent 0.001 SAUCE "dead" position whose sliver of yield stays in the source.04The relayer routes#
| Route | Does | Refuses |
|---|---|---|
POST /api/claim | Simulates, then sends claim on the deployed YieldLinks, gas capped at 3M | Unknown chains, links that prepaid under 1.35 HBAR, more than 10 requests a minute per IP |
POST /api/create-account | Creates an ED25519 account for a generated wallet with the Hiero SDK | Callers who cannot prove they hold an open, unexpired link. One account per link. |
The claim route reads its nonce from the mirror node, because Hashio's can lag after a failed transaction. It can only call claim; its key has no authority over escrowed funds. Shared server code lives in utils/relayer.
05What is deliberately absent#
| Not here | Why |
|---|---|
| An owner | Nobody can pause, drain or redirect escrow |
| An upgrade proxy | The rules a sender funded are the rules that pay out |
| A keeper | Refunds after expiry are permissionless, so nothing has to run on time |
| The Schedule Service | HIP-1215 scheduleCall has an open bug from delegatecall frames and a 62-day cap. Nothing depends on it. |
| A protocol fee | The only fee is the network fee the sender prepays |
The full reasoning behind each choice, and what an attacker can and cannot do, is on Security.