Overview & Background
In standard payment streaming architectures, recipients frequently receive multiple concurrent income streams—such as contractors working for multiple DAOs, employees receiving base compensation and vested incentive bonuses separately, or grant recipients receiving funding from multiple tranches. Currently, FlowFi's stream_contract only provides a single-stream withdrawal entrypoint:
pub fn withdraw(env: Env, recipient: Address, stream_id: u64) -> Result<i128, StreamError>
Calling withdraw separately for $N$ streams requires $N$ separate ledger transactions, incurring repeated base fees, multiple user wallet confirmations, sequence number contention, and unnecessary ledger overhead.
Detailed Problem Statement
- High Transaction & Overhead Costs:
- Recipients receiving 5–20 streams must broadcast individual transactions for each stream to collect their vested earnings.
- Each transaction consumes network base fees and ledger write footprints.
- Poor User Experience:
- The recipient's wallet (e.g. Freighter) must prompt for multiple signatures sequentially, causing friction and drop-off.
- Risk of Partial Failure:
- If an intermediate transaction fails due to network spikes or sequence number mismatches, the recipient is left in a partially withdrawn state.
Technical Specification & Architecture
1. Batch Withdrawal Entrypoint
Implement batch_withdraw in contracts/stream_contract/src/lib.rs:
pub fn batch_withdraw(
env: Env,
recipient: Address,
stream_ids: Vec<u64>,
) -> Result<Vec<(u64, i128)>, StreamError>
2. Authorization & Invariant Checks
- Strictly enforce
recipient.require_auth().
- Validate that
stream_ids.len() <= 30 to prevent exceeding Soroban CPU/memory transaction resource limits.
- For each
stream_id:
- Load stream from persistent storage via
storage::load_stream(&env, stream_id).
- Verify
stream.recipient == recipient.
- Verify
stream.is_active is true.
- Calculate claimable amount factoring in
cliff_time, current ledger timestamp, and paused state.
- If claimable amount > 0:
- Update
stream.withdrawn_amount and stream.last_update_time.
- If fully claimed, mark
stream.is_active = false and stream.status = StreamStatus::Completed.
- Save updated stream state and extend TTL.
- Transfer tokens from contract to recipient via Soroban Token client.
- Emit
TokensWithdrawn event.
- Record
(stream_id, amount_withdrawn) into the return result vector.
Target Files
contracts/stream_contract/src/lib.rs
contracts/stream_contract/src/events.rs
contracts/stream_contract/src/test.rs
Acceptance Criteria
Overview & Background
In standard payment streaming architectures, recipients frequently receive multiple concurrent income streams—such as contractors working for multiple DAOs, employees receiving base compensation and vested incentive bonuses separately, or grant recipients receiving funding from multiple tranches. Currently, FlowFi's
stream_contractonly provides a single-stream withdrawal entrypoint:Calling$N$ streams requires $N$ separate ledger transactions, incurring repeated base fees, multiple user wallet confirmations, sequence number contention, and unnecessary ledger overhead.
withdrawseparately forDetailed Problem Statement
Technical Specification & Architecture
1. Batch Withdrawal Entrypoint
Implement
batch_withdrawincontracts/stream_contract/src/lib.rs:2. Authorization & Invariant Checks
recipient.require_auth().stream_ids.len() <= 30to prevent exceeding Soroban CPU/memory transaction resource limits.stream_id:storage::load_stream(&env, stream_id).stream.recipient == recipient.stream.is_activeis true.cliff_time, current ledger timestamp, andpausedstate.stream.withdrawn_amountandstream.last_update_time.stream.is_active = falseandstream.status = StreamStatus::Completed.TokensWithdrawnevent.(stream_id, amount_withdrawn)into the return result vector.Target Files
contracts/stream_contract/src/lib.rscontracts/stream_contract/src/events.rscontracts/stream_contract/src/test.rsAcceptance Criteria
batch_withdrawsuccessfully withdraws claimable balances from multiple streams in a single atomic transaction.StreamError::Unauthorized.TokensWithdrawnevents for every processed stream.