Protocol Overview¶
Tributary is a non-custodial, pull-payment protocol on Solana. It lets a user authorize a gateway to pull tokens from their wallet on a schedule, with fees routed to the protocol, the gateway, and (optionally) a referral chain. The user keeps custody the entire time — authorization is a scoped SPL token delegation that the user can revoke at any moment.
The protocol exposes two policy families that share the same schedule model but differ in execution semantics:
| Family | What it does | When to use |
|---|---|---|
| PaymentPolicy | Direct pull: gateway transfers user → recipient + fees in one CPI. | Subscriptions, milestones, pay-as-you-go without swaps. |
| ComposablePolicy | Programmable pull with optional validation (Lighthouse assertion) and forward (Meteora DLMM swap) hooks between the pull and settlement. | Auto-topups, "pull USDC, deliver WSOL", gated payments that must pass on-chain conditions. |
Both reuse the same PolicyType enum (Subscription / Milestone /
PayAsYouGo), the same UserPayment account, the same fee-distribution
logic, and the same gateway/referral plumbing.
Account relationships¶
graph TD
User["👤 User<br/>(wallet owner)"]
UP["UserPayment PDA<br/>seeds: [user_payment, owner, mint]<br/>tracks counters + is the delegate"]
PP["PaymentPolicy PDA<br/>seeds: [payment_policy, user_payment, policy_id]<br/>direct pull"]
CP["ComposablePolicy PDA<br/>seeds: [composable_policy, user_payment, policy_id]<br/>programmable pull"]
VP["ValidationPda<br/>seeds: [composable_validation, composable_policy]<br/>≤512 bytes Lighthouse assertion data"]
GW["PaymentGateway PDA<br/>seeds: [gateway, authority]<br/>fees, signer, feature flags"]
CFG["ProgramConfig PDA<br/>seeds: [config]<br/>singleton — admin, protocol fee, pause"]
REF["ReferralAccount PDA<br/>seeds: [referral, gateway, code]<br/>up to 3-level chain"]
User -->|"create_user_payment"| UP
UP -->|"create_payment_policy"| PP
UP -->|"create_composable_policy"| CP
CP -.->|"stores assertion"| VP
PP -->|"references"| GW
CP -->|"references"| GW
GW -->|"referral pool"| REF
GW -->|"pays protocol fee"| CFG
classDef pda fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
classDef cfg fill:#fff3e0,stroke:#e65100,stroke-width:2px
class UP,PP,CP,VP,GW,REF pda
class CFG cfg
For the full PDA seed table and per-account field layouts, see Accounts & PDAs.
Execution lifecycle¶
1. SETUP (once per user/mint)
├── createUserPayment(owner, mint) → UserPayment PDA
├── createPaymentGateway(authority, ...) → PaymentGateway PDA (gateway op)
└── (optional) create_referral_account → ReferralAccount PDA (referrer)
2. POLICY CREATION
├── create_payment_policy → PaymentPolicy PDA
└── create_composable_policy → ComposablePolicy PDA (+ ValidationPda)
3. DELEGATE APPROVAL (user, off-chain or via SDK)
└── spl-token approve <user_ata> <UserPayment PDA> <amount>
4. EXECUTION (permissionless — any gateway signer)
├── execute_payment → transfer user_ata → recipient + fees
└── execute_composable → pull → validate → forward → settle
5. ADVANCE / CLOSE
├── advance schedule (Subscription.next_payment_due, PAYG period reset)
├── pause / resume
└── delete_* → closes accounts, refunds rent to rent_payer
The shared PolicyType enum¶
All five variants are fixed at 128 bytes (plus a 1-byte enum discriminator = 129 bytes total). This is a hard invariant: changing padding breaks deserialization of every existing account.
| Variant | Semantics |
|---|---|
| Subscription | Fixed amount every payment_frequency until max_renewals reached (or forever if auto_renew). Gated by next_payment_due. |
| Milestone | Up to 4 (amount, timestamp) milestones held in escrow. Released via release_condition bitmap: bit0=due-date, bits1–3 are mutually-exclusive signer requirements (gateway/owner/recipient). |
| PayAsYouGo | Usage-based: claim up to max_chunk_amount per call, capped at max_amount_per_period per period_length_seconds. Period auto-resets. |
| OneTime | Fixed amount, fires exactly once then Completed. due_date <= 0 = immediate; expiry_date = None = never expires. Full gateway lifecycle. See ADR-0019. |
| UpTo | Single-use, time-bound variable-amount authorization. Caller-supplied settle amount, 0 <= actual <= max_amount, enforced on-chain from the immutable policy. Recipient-triggerable. See ADR-0020. |
Fee distribution flow¶
Every execution — PaymentPolicy or ComposablePolicy — splits the pulled
amount the same way:
pulled_amount
├── protocol_fee = amount * effective_protocol_fee_bps / 10000 → ProgramConfig.fee_recipient
├── gateway_fee = amount * gateway_fee_bps / 10000 → PaymentGateway.fee_recipient
│ └── (if referral enabled) referral_pool = gateway_fee * referral_allocation_bps / 10000
│ └── split across up to 3 ReferralAccount levels per referral_tiers_bps
└── remainder (net) → policy.recipient
Key points:
- Protocol fee default is 100 bps (1%), configurable globally on
ProgramConfigand overridable per-gateway via theFEATURE_CUSTOM_PROTOCOL_FEEflag. gateway_fee_bps + effective_protocol_fee_bpsmust be< 10000(strictly). At 10000 the recipient gets zero; above 10000 the math underflows. Enforced byCombinedFeeBpsExceedsMax.- Referrals are a slice of the gateway fee, not of the payment.
referral_allocation_bpsis in bps of the gateway fee (cap 2500 = 25%).referral_tiers_bpssplits that pool across 3 levels and must sum to 10000. min_output_amounton a Composable forward is checked against the NET (post-fee) output, matching DeFi convention.- Math is
(amount * bps) / 10000with floor rounding; dust goes to the protocol.
For the full fee mechanics, see Fees and Referral Program.
Emergency controls¶
ProgramConfig.emergency_pause is a single boolean. When true, both
execute_payment and execute_composable fail with ProgramPaused. Setting
it is an admin-only instruction. It does not freeze user funds — users can
still revoke delegation and move tokens via SPL token directly. See
Security.
Where to go next¶
- Accounts & PDAs — full seed table, field layouts, rent strategy
- IDL — fetching the on-chain IDL, instruction inventory
- Deployment — program IDs, RPC endpoints, verification
- Error Codes — every
TributaryErrorvariant with remediation - Changelog — release history
- Security — delegation model, CPI hardening, audit status