Introduction
YieldStream is a token distribution contract for Stellar's Soroban virtual machine. A project uses it to pay many wallets from one pool of tokens without sending a transaction per wallet.
The problem
Say a protocol owes 10,000 people a payout — an airdrop, retroactive yield, a contributor round. The direct approach is a loop of transfers. That means:
- the issuer funds and signs 10,000 transactions;
- the full recipient roster becomes on-chain data;
- the issuer pays in full up front, whether or not anyone claims;
- unclaimed balances become a manual reconciliation task.
The mechanism
YieldStream moves the list off-chain and keeps only a commitment on chain. The issuer hashes their recipient list into a Merkle tree and publishes the single 32-byte root. Each recipient then proves they are in that list by submitting a short path of sibling hashes — 14 hashes for a 10,000-person list — and the contract checks the path against the stored root and pays out.
The issuer commits once. Recipients pay for their own claims. Tokens nobody claims sweep back to the issuer after the campaign ends, in one call.
What is real today
Stated plainly, because the rest of these docs assume it:
- Contract: complete and deployed to Stellar testnet at
CC25…2FPG. 21 tests pass, CI runscargo fmt --check,clippy -D warnings, the test suite, and a WASM build on every push. It has not been externally audited. Do not custody mainnet funds with it. - SDK: scaffold. The leaf-hashing and tree-building functions throw — that is deliberate, tracked as sdk issues #1 and #2, and the acceptance criteria are written as skipped conformance tests against the contract's pinned vectors.
- App: this landing page and docs. The issuer dashboard and claim UI are open contributor work.
- Mainnet: not deployed.
Ledger footprint, honestly
A common pitch for Merkle distributors is that they remove on-chain storage entirely. That is not true here, and the docs won't pretend otherwise. What the contract stores today:
- one persistent entry per campaign (root, budget, counters, status);
- one persistent entry per claimed leaf — so a fully claimed 10,000-person campaign currently occupies about 10,000 entries.
The recipient list itself never lands on the ledger, and unclaimed recipients cost nothing. Claimed-state packing into 256-leaf bitmaps (≈40 entries for 10,000 claims) is open issue #13, and it is the highest-impact item in the backlog.
Where to go next
- Protocol mechanics — hashing format, proof verification, lifecycle, storage and TTL.
- Contract reference — every function, parameter, auth requirement, event and error code.
- Developer guide — building against it.