Docs menu

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 tokensKeeps the booksTalks to SaucerSwap
YieldLinksNoYesNo
SauceStakingSourceYesNoYes

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.

Diagram
  Sender ──createLink──▶ YieldLinks ──deposit──▶ SauceStakingSource ──enter──▶ SaucerSwap
                            │                       (holds xSAUCE)            Infinity Pool
  Relayer ───claim────────▶ │ ──withdraw──▶ source ──leave──▶ pool
                            │                  │
                            │                  └──HIP-904 airdrop (0x167)──▶ recipient
                            └── fee ──▶ relayer

02The contracts#

ContractJob
YieldLinksEscrow, pooled share accounting, EIP-712 claims, refunds, prepaid fees
IYieldSourceThe seam: three functions every yield venue implements
ControlledSourceOne-time controller binding, HBAR fee reserve, HIP-904 payout
SauceStakingSourceStakes SAUCE in SaucerSwap's Infinity Pool, values xSAUCE at the pool ratio
IdleSourceNo yield. The reference implementation, used on plain Anvil.
HtsLibToken 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.

Solidity
// 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.

The + 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#

RouteDoesRefuses
POST /api/claimSimulates, then sends claim on the deployed YieldLinks, gas capped at 3MUnknown chains, links that prepaid under 1.35 HBAR, more than 10 requests a minute per IP
POST /api/create-accountCreates an ED25519 account for a generated wallet with the Hiero SDKCallers 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 hereWhy
An ownerNobody can pause, drain or redirect escrow
An upgrade proxyThe rules a sender funded are the rules that pay out
A keeperRefunds after expiry are permissionless, so nothing has to run on time
The Schedule ServiceHIP-1215 scheduleCall has an open bug from delegatecall frames and a 62-day cap. Nothing depends on it.
A protocol feeThe 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.

Next · 6 minAdd a yield source