Skip to content

fix(streams,events,soroban): serialize batchWithdraw, decode missing events, configurable fee; add Module44 (#378, #504, #506, #509) - #556

Merged
Jaydbrown merged 8 commits into
conduit-protocol:mainfrom
SarahDoma:fix/504-506-509-batch-nonce-events-fee
Aug 31, 2026
Merged

fix(streams,events,soroban): serialize batchWithdraw, decode missing events, configurable fee; add Module44 (#378, #504, #506, #509)#556
Jaydbrown merged 8 commits into
conduit-protocol:mainfrom
SarahDoma:fix/504-506-509-batch-nonce-events-fee

Conversation

@SarahDoma

@SarahDoma SarahDoma commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

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 to BASE_FEE with no way to bid higher — and implements Feature #44, a stream liquidity-risk/runway calculator.

Type of change

  • Bug fix
  • New feature / method
  • Refactor
  • Test coverage
  • Documentation
  • Dependency update

Related issue

Closes #504
Closes #506
Closes #509
Closes #378

Changes

File Change
src/streams.ts batchWithdraw() now submits sequentially (await each before the next) instead of Promise.allSettled(...map), so each transaction observes a distinct account sequence number; wires the new configurable fee into every submitted operation; fixes a missing Signer type import that broke npm run build/npm run typecheck on main
src/soroban.ts Adds exported resolveFee() and an optional fee parameter (default BASE_FEE) on buildContractCallTx()
src/types/index.ts Adds ConduitConfig.fee / .feeMultiplier; adds CreatedEvent, ForceCancelEvent, RecipientTransferEvent, OperatorSetEvent, OperatorRevokedEvent, and their StreamEventHandlers callbacks
src/events.ts Adds created/force_cxl/xfer_rec/set_op/rm_op topic constants and their tuple/scalar decoders to dispatchEvent()
src/module44.ts (new) Module44 — stream runway/liquidity-risk calculator for Feature #44, following the shared LruMemoCache pattern already used by Module26/Module36/Module48
src/index.ts Exports Module44 and its types
src/tests/* New/updated unit tests for all four items (see below)
docs/api.md, CHANGELOG.md Documented batchWithdraw() (previously undocumented), the new fee config, the five new event handlers, and Module44

Notes for reviewers

  • Commit history follows this repo's 5-commit convention for the three bug fixes: a test commit (all new assertions fail against main) followed by one fix commit per issue, then a docs commit — plus two further commits (feature + docs) for Module44.
  • #509's fix commit also carries a one-line, pre-existing, unrelated fix: src/streams.ts was missing import type { Signer } from './signer.js', which made npm run build and npm run typecheck fail unconditionally on main — noted separately in that commit's body and in CHANGELOG.md since it isn't part of the assigned scope but blocks CI regardless.
  • #378 was 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 the measuredSpeedupPercent-over-fixed-percentage rationale applied to Module26/Module36 in CHANGELOG.md), Module44 follows the same honest-measurement pattern as those modules, ships with 26 tests (98.5% statement / 100% function coverage on module44.ts), and is documented in docs/api.md.
  • The only failing test locally (signer.test.ts_signTx — Signer with async/sync sign()) is pre-existing and reproduces identically on main with none of this PR's changes applied; it's unrelated to this PR's scope and left untouched.

Checklist

  • npm run typecheck — no errors
  • npm run lint — no warnings
  • npm test — all tests pass (pre-existing, unrelated signer.test.ts flake noted above)
  • npm run build — bundle compiles cleanly
  • No any types introduced
  • New public methods documented in docs/api.md
  • All on-chain amounts kept as bigint — no Number() conversion in arithmetic
  • New methods mock-tested in src/tests/
  • CHANGELOG.md updated under [Unreleased]
  • src/index.ts updated — Module44 and its types are now exported

Breaking changes?

  • No
  • Yes — describe below and add BREAKING CHANGE: footer to relevant commit

, 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.
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

…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.
@SarahDoma SarahDoma changed the title fix(streams,events,soroban): serialize batchWithdraw, decode missing events, configurable fee (#504, #506, #509) fix(streams,events,soroban): serialize batchWithdraw, decode missing events, configurable fee; add Module44 (#378, #504, #506, #509) Aug 30, 2026
@Jaydbrown
Jaydbrown merged commit 3bdb66d into conduit-protocol:main Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment