Referrals#
How Seesaw's referral attribution and payout system works on-chain. Every trader can pick exactly one referrer — once, ever (first-touch). For 365 days after that, the configured referral share of taker fees is reserved for that referrer. The shipped share is 5% of the fee; see fee defaults.
<!-- src: program/src/logic/fee.rs:168 FeeSplit4::TARGET.referral_bps --> <!-- F-01: update with builder fee allocation -->The fee is first retained in the market vault, then a permissionless rollup moves it to the referrer's bound treasury shard and makes it claimable. After the 365 days lapse, the attribution stops earning.
If you're a user, the friendlier explanation is at
docs/for-traders/referrals-and-fees.
How referral attribution works#
The referral system uses five coupled on-chain account types. Understanding what each one does is the key to understanding the whole flow:
| Account | PDA seeds | What it stores |
|---|---|---|
ReferralAccount | ["seesaw", "referral", referee_pubkey] | Immutable trader → referrer binding and expiration. |
UserPositionAccount | market + trader | Market-local cached binding plus pending_referral_fees retained in the market vault. |
MarketAccount | market identity | Aggregate accumulated_referral_fees liability that blocks teardown until cleared. |
ReferrerEarningsAccount | ["seesaw", "referrer_earnings", referrer_pubkey] | Claimable amount credited only by rollup, plus its immutable shard index. |
referrer_treasury PDA | ["seesaw", "referrer_treasury", &[shard_index]] | 8 sharded token-account PDAs holding rolled-up, unclaimed earnings. |
The lifecycle#
0. Provision the treasury shards: InitializeReferrerTreasuryShard (0x37)#
Any funded caller may idempotently create a canonical shard for the configured settlement mint. The selected shard must exist before an earnings account can bind to it.
1. The referrer registers: InitReferrerEarningsAccount (0x22)#
Before anyone can name them as a referrer, the referrer signs creation of their
ReferrerEarningsAccount, choosing a
treasury_index ∈ [0, 8). That index permanently binds the referrer
to one of the 8 referrer_treasury shards (INV-REFERRAL-T1) — every fee
they ever earn routes through that shard. This instruction validates the
already-provisioned shard; it does not create it.
2. The trader opts in: SetReferrer (0x21)#
The trader (referee) signs SetReferrer, which:
- Requires the referee's signature — no third party can set someone else's referrer.
- Rejects self-referral (
referrer == referee). - Requires the referrer's
ReferrerEarningsAccountto already exist (step 1). - Creates the
ReferralAccountPDA withexpires_at = now + 365 days(REFERRAL_DURATION_SECONDS).
First-touch is final. If a ReferralAccount already exists for the
referee — even an expired one — SetReferrer fails with
ReferrerAlreadySet. There is no changing, clearing, or re-pointing a
referral. When the 365 days lapse, the attribution stops earning; it does
not become re-assignable.
3. Attribution at fill time#
When a taker order fills, the fill processor:
- Computes the total taker fee from the capped-decay curve (see
README.md§ Fee Structure). - Allocates it across four configured legs that sum to exactly 100%.
Under the shipped defaults:
- 50% → protocol treasury (see
treasury-and-fee-split.md) - 5% → the market's
accumulated_creator_fees - 5% → the referral slice
- 40% → eligible resting makers'
TraderLedger.quote_free, withdrawn withWithdrawFunds
- 50% → protocol treasury (see
- The transaction may supply the taker's canonical
ReferralAccountas one optional binding account. Once validated, the binding is cached in the market position for later fills:- Active binding → the referral slice stays in the market vault and increments
position.pending_referral_feesplusmarket.accumulated_referral_fees. It is not claimable yet. - No binding or expired binding → the referral slice forfeits to the indexed
protocol treasury (
ReferralForfeited).
- Active binding → the referral slice stays in the market vault and increments
The taker pays the same total fee either way — referrer presence only changes who receives the referral slice. Ineligible maker rebates and split rounding dust also go to the protocol.
4. Rollup: RollupReferralFees (0x36)#
Any caller can submit 1–8 sorted positions for one referrer. The instruction
atomically transfers their pending amount market-vault → bound shard, clears
the position and market pending liabilities, and credits the equal amount to
ReferrerEarningsAccount.accumulated. The shard must cover the full resulting
claimable balance.
5. Claiming: ClaimReferrerEarnings (0x24)#
When the referrer wants to withdraw:
- The referrer must sign — PDA seeds tie the earnings account to their pubkey.
- The program transfers exactly
accumulatedfrom the referrer's bound shard to the referrer's stablecoin account and resets the counter. - The supplied shard account is re-validated against the bound
treasury_index(ReferrerTreasuryMismatchon a wrong shard).
Only rolled-up earnings are claimable. There's no minimum claim, but each claim costs a transaction fee, so most referrers batch claims.
Terminal rollup deadline#
Pending referral amounts are protected by a seven-day grace period after market
resolution. Once that deadline passes, any caller may close a settled position;
the instruction clears its pending referral amount, decrements the market
liability, and reclassifies the same amount as creator fees. It emits
ReferralPendingForfeited. The path does not require proof that the referrer is
unreachable, so rollup operators should clear pending positions before the
deadline to preserve the referrer's entitlement.
Why 8 shards?#
- Write contention: Solana parallelizes transactions only when their writable accounts don't overlap. One global referral treasury would serialize every referral rollup and claim; 8 shards let rollups and claims for referrers on different shards execute in parallel; ordinary fills stay market-local and do not lock a global referral shard.
- Cheap to reconcile: 8 is small enough for the indexer to enumerate every shard and verify the system-wide solvency invariant (Σ shard balances ≥ Σ accumulated earnings) on a schedule.
- Deterministic addresses: each shard is a stable PDA, so the claim and accrual paths always know exactly which account to validate.
A referrer's shard is whatever treasury_index was chosen when their
earnings account was initialized — set once, immutable afterwards.
Account and parameter reference#
Invariants#
Referral invariants are catalogued in
security/invariants.md as INV-F2, INV-F3,
INV-F4 (fee-split and attribution guarantees) and INV-REFERRAL-T1
through INV-REFERRAL-T3 (per-shard credit/transfer pairing and
system-wide solvency). In summary: the four configured fee shares sum to 100%,
first-touch attribution is permanently immutable and earns for 365 days, every rollup credit is
paired with an equal shard transfer, and referrer treasuries always cover
the sum of all accumulated earnings. One referral per trader, self-referral
rejected, taker fee identical with or without a referrer.
What can the referrer see?#
The referrer sees:
- The list of traders who picked them (publicly derivable from on-chain
ReferralAccounts). - Their aggregate accrued earnings (via
ReferrerEarningsAccount).
They do not get any special view of individual referee trades — though all Solana activity is publicly visible by wallet address anyway, so the referral system introduces no privacy delta.
Edge cases#
- Referrer's wallet is lost: the
ReferrerEarningsAccountPDA still exists and keeps accruing; the referrer just can't signClaimReferrerEarnings. There's no clawback or escheat. - Referral expires mid-stream: fills after
expires_atroute the referral slice to the protocol (ReferralForfeited); nothing else changes for the taker. The binding cannot be replaced afterwards. - Client omits the referral binding account: an already-cached active position binding continues to accrue. A first fill with no cached or supplied binding forfeits that fill's referral slice to the protocol.
- Multisig / program-owned referrer: works — the referrer pubkey is whatever the trader pointed at; only claiming requires that key to sign.
Related docs#
for-traders/referrals-and-fees— user-facing prosesdk/referral-and-creator-fees— SDK-level integrationhow-it-works/treasury-and-fee-split— the protocol-treasury side of the same feehow-it-works/flow-of-funds— where every fee slice physically goessecurity/invariants— full invariant catalog