fix(streams,events,soroban): serialize batchWithdraw, decode missing events, configurable fee; add Module44 (#378, #504, #506, #509) - #556
Merged
Jaydbrown merged 8 commits intoAug 31, 2026
Conversation
, conduit-protocol#506, conduit-protocol#509 - batchWithdraw() must submit sequentially, not concurrently, so it cannot be verified against the current Promise.allSettled(...map) implementation. - dispatchEvent() must decode created/force_cxl/xfer_rec/set_op/rm_op, which are not yet in TOPIC or the switch statement. - buildContractCallTx()/resolveFee() must honour a configurable inclusion fee, which does not exist yet (fee is hardcoded to BASE_FEE). All 13 new assertions fail against the current implementation.
…ollisions (conduit-protocol#504) batchWithdraw() fired all N withdraw() calls concurrently via Promise.allSettled(...map). Each withdraw() -> _invoke() -> buildContractCallTx() reads the caller account's sequence number via getAccount() and builds a transaction on top of it, so concurrent calls all read the same sequence number. With a single keypair/wallet (the normal case), at most one of the N transactions could ever land; the rest failed with txBAD_SEQ, defeating batchWithdraw's entire purpose. Submitting withdrawals one at a time (await each before starting the next) guarantees getAccount() only observes the sequence after the previous transaction has landed, so every submission gets a distinct, ordered sequence number. Closes conduit-protocol#504
…nts (conduit-protocol#506) TOPIC and dispatchEvent() only handled withdrawn, cancelled, paused, resumed, topped_up, and clawback, even though the stream contract also emits created, force_cxl (force-cancel by recipient), xfer_rec (recipient transfer), set_op (operator delegated), and rm_op (operator revoked). A subscriber was silently never notified of any of these — notably a recipient transfer, where the current subscriber may have just lost the stream. Adds the five missing topic constants, their StreamEventHandlers callbacks (onCreated, onForceCancel, onRecipientTransfer, onOperatorSet, onOperatorRevoke), and typed event payloads (CreatedEvent, ForceCancelEvent, RecipientTransferEvent, OperatorSetEvent, OperatorRevokedEvent) with tuple/scalar decoders matching the existing pattern for the six handled topics. Closes conduit-protocol#506
…rotocol#509) buildContractCallTx() and StreamsModule always submitted transactions with fee: BASE_FEE (100 stroops) and there was no ConduitConfig knob to raise it. Under inclusion-fee pressure (surge pricing, congested ledgers) a 100-stroop bid is not selected; _sendAndPoll then exhausts its poll attempts and throws a misleading "Transaction timed out" instead of a "fee too low" error. Adds ConduitConfig.fee (explicit stroops amount) and ConduitConfig.feeMultiplier (multiple of BASE_FEE), resolved once via the new exported resolveFee() and threaded through buildContractCallTx()'s new optional fee parameter (default BASE_FEE, so existing callers are unaffected) for every submitted (non-read-only) StreamsModule operation: create(), the shared _invoke() path (withdraw/cancel/pause/resume/topUp/transferRecipient/forceCancel), and clawback(). Also fixes a pre-existing, unrelated build break on main: streams.ts used the Signer type without importing it, so `npm run build` and `npm run typecheck` failed unconditionally (`Cannot find name 'Signer'`) before this PR's changes could even be verified by CI. Updates two existing tests' soroban.js mocks/assertions for the new buildContractCallTx() signature (network-switcher-validation.test.ts, streams-success.test.ts). Closes conduit-protocol#509
…uit-protocol#504, conduit-protocol#506, conduit-protocol#509 - Document batchWithdraw() (previously undocumented) and its sequential-submission behaviour. - Document ConduitConfig.fee/feeMultiplier and add an inclusion-fee callout under ConduitConfig. - Document the five new StreamEventHandlers callbacks and their decoded payload shapes in the subscribe() example and event-payload note. - Add CHANGELOG.md entries under [Unreleased] for all three fixes plus the incidental Signer-import build-break fix.
|
@SarahDoma 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! 🚀 |
…t-protocol#378) Implements Feature #44 (issue conduit-protocol#378), an SDK-side helper for flagging streams that are about to run out of scheduled balance: - assessSingleItem()/assessBatch() classify each stream's remaining runway (seconds until endTime) into 'inactive' | 'critical' | 'warning' | 'healthy', with configurable thresholds. - estimateTopUpNeeded() computes the stroops needed via top_up() to reach a target runway. - Follows the shared LruMemoCache pattern already used by Module26/Module36/Module48, and reports getPerformanceMetrics()'s measuredSpeedupPercent as an honest, workload-dependent measurement from this instance's own accumulated hit/miss timings — never a fixed assumed percentage (conduit-protocol#378 asked for "improve performance by 20%", which this repo has already established is not a claim that can be made honestly without a specific, measured workload; see the same rationale applied to Module26/Module36 in CHANGELOG.md). - 26 unit tests covering constructor validation, all four risk classifications (including open-ended and already-ended streams), cache hit/miss/eviction/bypass behaviour, batch chunking, and estimateTopUpNeeded()'s edge cases — 98.5% statement / 100% function coverage on module44.ts. Closes conduit-protocol#378
…t-protocol#378) Adds a "Module44 (Feature #44)" section to docs/api.md mirroring the existing Module26/Module36/Module48/Module49 sections, and a CHANGELOG.md [Unreleased] entry under Added.
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.
What does this PR do?
Fixes three independent SDK bugs —
batchWithdraw()submitting transactions concurrently and colliding on sequence numbers,dispatchEvent()silently dropping five contract event topics, and every submitted transaction being hardcoded toBASE_FEEwith no way to bid higher — and implements Feature #44, a stream liquidity-risk/runway calculator.Type of change
Related issue
Closes #504
Closes #506
Closes #509
Closes #378
Changes
src/streams.tsbatchWithdraw()now submits sequentially (await each before the next) instead ofPromise.allSettled(...map), so each transaction observes a distinct account sequence number; wires the new configurable fee into every submitted operation; fixes a missingSignertype import that brokenpm run build/npm run typecheckonmainsrc/soroban.tsresolveFee()and an optionalfeeparameter (defaultBASE_FEE) onbuildContractCallTx()src/types/index.tsConduitConfig.fee/.feeMultiplier; addsCreatedEvent,ForceCancelEvent,RecipientTransferEvent,OperatorSetEvent,OperatorRevokedEvent, and theirStreamEventHandlerscallbackssrc/events.tscreated/force_cxl/xfer_rec/set_op/rm_optopic constants and their tuple/scalar decoders todispatchEvent()src/module44.ts(new)Module44— stream runway/liquidity-risk calculator for Feature #44, following the sharedLruMemoCachepattern already used byModule26/Module36/Module48src/index.tsModule44and its typessrc/tests/*docs/api.md,CHANGELOG.mdbatchWithdraw()(previously undocumented), the new fee config, the five new event handlers, andModule44Notes for reviewers
main) followed by one fix commit per issue, then a docs commit — plus two further commits (feature + docs) forModule44.#509's fix commit also carries a one-line, pre-existing, unrelated fix:src/streams.tswas missingimport type { Signer } from './signer.js', which madenpm run buildandnpm run typecheckfail unconditionally onmain— noted separately in that commit's body and inCHANGELOG.mdsince it isn't part of the assigned scope but blocks CI regardless.#378was a placeholder issue ("Investigate module 44 … improve performance by 20% … 90% coverage … update docs") with no concrete target beyond that template. Rather than fabricate a claim this repo has already established it won't make (see themeasuredSpeedupPercent-over-fixed-percentage rationale applied toModule26/Module36inCHANGELOG.md),Module44follows the same honest-measurement pattern as those modules, ships with 26 tests (98.5% statement / 100% function coverage onmodule44.ts), and is documented indocs/api.md.signer.test.ts—_signTx — Signer with async/sync sign()) is pre-existing and reproduces identically onmainwith none of this PR's changes applied; it's unrelated to this PR's scope and left untouched.Checklist
npm run typecheck— no errorsnpm run lint— no warningsnpm test— all tests pass (pre-existing, unrelatedsigner.test.tsflake noted above)npm run build— bundle compiles cleanlyanytypes introduceddocs/api.mdbigint— noNumber()conversion in arithmeticsrc/tests/CHANGELOG.mdupdated under[Unreleased]src/index.tsupdated —Module44and its types are now exportedBreaking changes?
BREAKING CHANGE:footer to relevant commit