Changelog¶
All notable changes to the Tributary protocol are documented here. Dates are
the date a change landed on Mainnet (or Unreleased while in review).
The format is based on Keep a Changelog.
[Unreleased] — Composable Policy Release¶
The composable-policy family merges into the regular program: validation hooks (Lighthouse) and forward hooks (Meteora DLMM) are now first-class citizens alongside direct pull payments.
Added¶
PolicyType::OneTime(discriminator3) — fixed-amount, single-fire pull payment with the full gateway lifecycle (PDA, pausable, deletable, schedulable, composable hooks).due_date <= 0= immediate;expiry_date = None= never expires. See ADR-0019.PolicyType::UpTo(discriminator4) — single-use, time-bound variable-amount authorization. Caller-supplied settle amount bounded bymax_amount(0 <= actual <= max, enforced on-chain from the immutable policy). Recipient-triggerable like Pay-as-you-go. The x402uptoprimitive. See ADR-0020.PolicyExpirederror variant — shared by OneTime'sexpiry_dateand UpTo'sdeadlinegates.- SDK —
getCreateOneTimePolicyInstruction/createOneTimePaymentandgetCreateUpToPolicyInstruction/createUpToAuthorization/settleUpTo. - x402 package —
"x402://upto"scheme with verify + settle helpers (src/upto.ts) and short-lived JWT (exp = deadline). ComposablePolicyaccount and instructions (create_composable_policy,execute_composable,change_composable_status,delete_composable_policy) — programmable pull payments with optional validation + forward hooks executed between the pull and the settlement.ValidationPdaaccount (["composable_validation", composable_policy]) — separate account for Lighthouse assertion data (≤512 bytes), decoupling storage cost from theComposablePolicyitself.ForwardConfig+ByteRangeCheck— on-chain instruction-data validation for forward CPIs. Up to 4 byte-range checks per policy; at least one must pin the discriminator at offset 0.ValidationConfig— opt-in validation hook;SystemProgramsentinel disables it.NATIVE_OUTPUTforward flag (FORWARD_FLAG_NATIVE_OUTPUT = 1) — WSOL → native SOL sweep via a Tributary-controlledcloseAccountwhose destination is pinned torecipienton-chain. Requiresoutput_mint == NATIVE_MINT.- Lighthouse SDK facade in
@tributary-so/sdk— fluent builder for assertion data (lighthouse.tokenAccount(ata).amount(n, "<").build()), wrapping the vendored official Lighthouse client. - Referral program —
ReferralAccount, 3-level chain, configurable gateway-scoped allocation and tiers. ProgramConfig.emergency_pause— global pause flag that blocks allexecute_paymentandexecute_composablecalls.PaymentsDelegatelegacy PDA accepted for backward compatibility with v0-approved token accounts.
Changed¶
PolicyTypeenum is now shared betweenPaymentPolicyandComposablePolicy. Previously composable had a duplicateScheduleTypethat drifted in month arithmetic; both now use the unified, 128-byte-fixed enum. Seereports/M-04-inconsistent-month-arithmetic.md.UserPaymentis the modern delegate.execute_payment/execute_composablesign as the per-(owner, mint)UserPaymentPDA; the legacy globalPaymentsDelegatePDA remains accepted but is not written for new policies.PaymentGatewaygainsfeature_flags,referral_allocation_bps,referral_tiers_bps,custom_protocol_fee_bps, andsignerfields.- Fee math:
gateway_fee_bps + effective_protocol_fee_bpsmust be strictly< 10000(was:<= 10000). At exactly 10000 the recipient received zero; the new check rejects that config. min_output_amountonForwardConfigis now checked against the net (post-fee) output, matching DeFiamountOutMinconvention.
Security¶
- Intermediate ATA ownership decoupled — intermediate input/output
ATAs are owned by the
ComposablePolicyPDA, not theUserPaymentPDA. This means a forward program can only ever touch transient intermediate balances, never the user's source funds. - CPI signer sanitization (C-1) — validation and forward CPI builders
no longer forward
is_signerfromremaining_accounts. Closes a privilege-pass-through vector where the fee payer (a Signer) re-passed as a remaining account could grant Lighthouse / DLMM unintended signer authority. - Allowlists for forward (
ALLOWED_FORWARD_PROGRAMS— Meteora DLMM) and validation (ALLOWED_VALIDATION_PROGRAMS— Lighthouse) target programs. Sentinels (Pubkey::default()/SystemProgram) disable the hooks. ByteRangeChecklength unbounded (H-06) — create-time guard now rejectslength > 8; runtimevalidatealso defends against the panic for hostile / malformed accounts.- Manual
ValidationPdainit freshness check (M-02) — explicitis_freshcheck against pre-funded or wrong-owner accounts to prevent type cosplay and re-initialization. - Combined-fee underflow (M5) —
validate_combined_bpsnow runs after both fee fields reach their final values, preventing anArithmeticOverflowon every payment through misconfigured gateways.
Removed¶
- Five dead error variants purged during the ponytail #8 cleanup of
error.rs(post-cleanup variant count: 58).
[Pre-release] — Direct Pull Payments¶
The original program: PaymentPolicy (Subscription / Milestone / PayAsYouGo)
with direct gateway pulls, PaymentGateway, UserPayment, ProgramConfig,
and the transfer helper.
Added¶
initialize,create_user_payment,create_payment_gateway,create_payment_policy,execute_payment,change_payment_policy_status,delete_payment_policy,delete_user_payment,delete_payment_gateway,change_gateway_signer,change_gateway_fee_recipient,change_gateway_fee_bps,transfer.ProgramConfigwith admin, protocol fee recipient,protocol_fee_bps(default 100).PaymentGatewaywith per-authority fees and signer.PolicyType::Subscription,Milestone,PayAsYouGo— fixed 128-byte variants.
Versioning notes¶
- Tributary does not maintain separate program IDs per environment —
Devnet and Mainnet share
TRibg8W8zmPHQqWtyAD1rEBRXEdyU13Mu6qX1Sg42tJ. - Account layouts are padded for forward-compatibility; adding fields
consumes reserved padding without a state migration. Variants of
PolicyTypeare pinned at 128 bytes — adding a variant is safe as long as the body fits. - See Deployment for how to verify the current on-chain build, and Error Codes for the up-to-date error table.