You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every payment path today is instantaneous: pay transfers now, deposit then distribute transfers now. There is no notion of time. Payroll, vesting, grants, and subscriptions all need "release X per unit time to these recipients until the funding runs out," and Tributary cannot express it.
Proposal
Add a stream primitive layered on the split routing table: a funder locks an amount against a split with a start time, end time (or rate), and the contract makes vested = f(now) withdrawable. Anyone can trigger withdraw_vested(stream_id, token), which pays out the newly-vested portion through the split's existing recipient/share routing (so streams compose with nested splits for free).
Why this is hard
Introduces the first time-dependent state machine in the contract, with env.ledger().timestamp() as the clock. Vesting math must be monotonic and never over-release, even across many partial withdrawals.
Precision: linear vesting with i128 and 7-decimal assets needs careful rounding so the sum of withdrawals never exceeds the locked amount and the final withdrawal cleans up dust (mirror the existing dust-to-last rule).
Cancellation and top-ups: define what happens to unvested funds on cancel (refund to funder), who may cancel (funder? controller?), and how top-ups extend a live stream.
Contract: create_stream, withdraw_vested, cancel_stream, top_up, vested_of (view), plus events.
SDK helpers and a dashboard stream view showing live vested/withdrawn/remaining.
Tests including fake-time progression and property tests (never over-release; conservation).
Acceptance criteria
Maintainer-approved RFC before implementation.
withdrawn_total <= locked_total holds under any interleaving of withdrawals, proven by property test.
Vested amount is exact at boundaries (0 at start, full at end) and dust is handled at the final withdrawal.
Cancellation refunds exactly the unvested remainder; authorization is enforced and tested.
Streams route through nested Recipient::Split correctly.
TTL is extended so a live stream cannot archive; documented.
Related
Distinct from recurring pull payments (separate hard issue): streaming is continuous release of pre-locked funds; recurring is scheduled pulls from an external balance.
Difficulty: Expert. Scope: multi-week epic. Contract + SDK + dashboard.
Problem
Every payment path today is instantaneous:
paytransfers now,depositthendistributetransfers now. There is no notion of time. Payroll, vesting, grants, and subscriptions all need "release X per unit time to these recipients until the funding runs out," and Tributary cannot express it.Proposal
Add a stream primitive layered on the split routing table: a funder locks an amount against a split with a start time, end time (or rate), and the contract makes
vested = f(now)withdrawable. Anyone can triggerwithdraw_vested(stream_id, token), which pays out the newly-vested portion through the split's existing recipient/share routing (so streams compose with nested splits for free).Why this is hard
env.ledger().timestamp()as the clock. Vesting math must be monotonic and never over-release, even across many partial withdrawals.Deliverables
create_stream,withdraw_vested,cancel_stream,top_up,vested_of(view), plus events.Acceptance criteria
withdrawn_total <= locked_totalholds under any interleaving of withdrawals, proven by property test.Recipient::Splitcorrectly.Related
Distinct from recurring pull payments (separate hard issue): streaming is continuous release of pre-locked funds; recurring is scheduled pulls from an external balance.