feat(streams): client-side amount validation, list() union, forceCancel + transferRecipient wrappers - #465
Merged
Jaydbrown merged 1 commit intoAug 26, 2026
Conversation
…el + transferRecipient wrappers - withdraw()/topUp() now reject amount <= 0n client-side before any RPC round-trip, matching create()'s fail-fast validation (conduit-protocol#451) - list() returns the de-duplicated union when both sender and recipient filters are given instead of silently dropping recipient (conduit-protocol#452) - add StreamsModule.forceCancel() wrapping DripStream::force_cancel, making StreamErrorCode.PauseThresholdNotMet reachable through the SDK (conduit-protocol#453) - add StreamsModule.transferRecipient() wrapping DripStream::transfer_recipient (conduit-protocol#454) - update docs (api.md, architecture.md, README, CHANGELOG) and add unit tests 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@TemiMustapha Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Contributor
|
Thanks for the contribution here — squash-merging this now. Any follow-ups we'll track in a fresh issue. 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four
StreamsModuleenhancements from the Stellar Wave program, each previously either silently wrong, unguarded, or only a prose TODO:Enhancement: withdraw()/topUp() don't validate amount > 0n client-side, unlike create()'s validation #451 —
withdraw()/topUp()now rejectamount <= 0nclient-side, before any RPC round-trip, matching the fail-fast validationcreate()already applies (src/builder.tsphilosophy: "mandatory client-side validation prevents invalid payloads from reaching the smart contract"). Zero/negative amounts previously went straight into the contract call and only failed after a full simulate+reject cycle surfacingStreamErrorCode.InvalidAmount.Enhancement: list() silently ignores recipient when both sender and recipient are provided #452 —
list()no longer silently dropsrecipientwhen bothsenderandrecipientare provided. Theif (sender) { ... } else if (recipient) { ... }chain returned sender-only results with no indication anything was ignored. Both filters are now queried concurrently and merged into a de-duplicated union (streams where the address is either sender or recipient). Documented indocs/api.mdandREADME.md.Enhancement: StreamsModule has no wrapper for force_cancel(), and StreamErrorCode.PauseThresholdNotMet is unreachable through the SDK #453 — New
StreamsModule.forceCancel(streamId)wrapping the contract'sDripStream::force_cancel(). Recipients can now force-cancel a stream paused beyond the 30-day threshold; it settles atomically likecancel()(earned tokens → recipient, remainder → sender). This also makesStreamErrorCode.PauseThresholdNotMetreachable through the SDK for the first time — previously nothing instreams.tsinvokedforce_cancel, so that error code could never be thrown via a public SDK call path.Enhancement: StreamsModule has no wrapper for transfer_recipient() #454 — New
StreamsModule.transferRecipient(streamId, newRecipient)wrapping the contract'sDripStream::transfer_recipient(). The current recipient can reassign the recipient role (including accrued withdrawable balance) without dropping to rawbuildContractCallTx/invokeContractcalls. Validates the new recipient address client-side.Changes
src/streams.tswithdraw(): guardamount !== undefined && amount <= 0n→ throws'Invalid amount: must be greater than zero'. The omitted-amount path (defaults to full withdrawable balance) is unaffected — a zero balance there still correctly surfacesNothingToWithdraw.topUp(): guardamount <= 0n→ throws before any RPC call (also coverstopUpStream()and per-itembatchWithdraw()failures).list(): when both filters are present, fetchstreamsBySender+streamsByRecipientconcurrently and return[...new Set([...senderIds, ...recipientIds])].forceCancel(streamId):_invoke→force_cancel(no args).transferRecipient(streamId, newRecipient): validates a non-empty recipient,_invoke→transfer_recipientwith the newAddressScVal.list().Tests (
src/tests/streams.test.ts,src/tests/streams-success.test.ts)withdraw,topUp,topUpStream, and per-itembatchWithdrawfailure reporting.withdraw()still defaults to the withdrawable balance.list()with both filters: queries both factory methods with the same offset/limit, and returns the de-duplicated union.forceCancel()/transferRecipient(): keypair guard, success paths returning the tx hash,transfer_recipientinvoked with the new recipient address, andPauseThresholdNotMet/NotPausedmapped toConduitErrorcodes 13 / 10.transferRecipient()empty / whitespace recipient rejection.Docs
docs/api.md: newforceCancel/transferRecipientsections, union semantics + client-side validation notes onlist/withdraw/topUp.docs/architecture.md: module map updated; "What's not wrapped yet" now lists onlystreamed_total.README.md: new method sections,list()union note.CHANGELOG.md:[Unreleased]→ Added (both wrappers), Fixed (Enhancement: withdraw()/topUp() don't validate amount > 0n client-side, unlike create()'s validation #451, Enhancement: list() silently ignores recipient when both sender and recipient are provided #452); removed the two completed items from Planned.Verification
npx tsc --noEmit— cleannpx vitest run— 740 passed, 2 skipped (58 files)Closes #451, Closes #452, Closes #453, Closes #454