feat(client): clone overrides + lineage, dry-run simulation, invoice event streaming, RPC resilience - #900
Merged
Kingsman-99 merged 2 commits intoSep 28, 2026
Conversation
…reaming Implements the remaining acceptance criteria for four SDK issues. Stellar-split#850 — cloneInvoice now accepts a bigint (or string) source ID and InvoiceParamOverrides (title, deadline, targetAmount, recipients). Overrides are normalised and validated like createInvoice before submission and mapped onto the clone_invoice override map. Adds getLineage(invoiceId) returning the root -> leaf ancestor chain as bigint[]. Stellar-split#844 — adds generic client.simulate(method, params) returning SimulationResult { success, error?, fee, cpuInsns, memBytes, footprint } and supports { simulate: true } on createInvoice, pay, releaseGroup and refundInvoice. The RPC SimulationResult type is exported from the package; the sandbox result type is re-exported as SandboxSimulationResult to avoid a name collision. Stellar-split#842 — adds subscribeInvoice(invoiceId, callback, options) returning a Subscription backed by Soroban getEvents polling with ledger+topic deduplication and exponential-backoff reconnection. Stellar-split#843 — documents the existing retry (exponential backoff + jitter) and circuit breaker (CLOSED/OPEN/HALF-OPEN, circuit:open|close|half-open events) resilience layer in the README and wires its test suite into `npm test`. Tests: adds test/clientIssueFixes.test.ts (15 cases) and runs resilience.test.ts in CI. `npm test` passes (153 tests). No new TypeScript errors are introduced (verified against the pre-change baseline). closes Stellar-split#850 closes Stellar-split#844 closes Stellar-split#842 closes Stellar-split#843
|
@terngunan 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! 🚀 |
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 the issues were and how this PR resolves them
#850 — Add
cloneInvoicewith field override supportcloneInvoiceaccepted only string IDs and a narrowCloneOverridesshape, and there was no way to read an invoice's clone ancestry.cloneInvoice(sourceId: bigint | string, overrides?: CloneOverrides & InvoiceParamOverrides)—InvoiceParamOverrides = { title?, deadline?, targetAmount?, recipients? }is normalised onto the contract'sclone_invoiceoverride map and validated before submission (non-empty title, future deadline, positivetargetAmount, valid Stellar addresses, amount/recipient counts aligned) using the same rules ascreateInvoice.getLineage(invoiceId): Promise<bigint[]>returns the full ancestor chain in root → leaf order, ending withinvoiceIditself.InvoiceNotCloneableErrorvia the existing cloneability pre-flight.#842 — Implement
subscribeInvoicereal-time event streamingThere was a low-level
createInvoiceSubscriptionhelper but no client method.client.subscribeInvoice(invoiceId: bigint | string, callback, options?): Subscriptionpolls SorobangetEvents(pollIntervalMs, default 3000ms), deduplicates by ledger sequence + topic hash, and reconnects with exponential backoff (max 5 retries) before emitting anerrorlifecycle event.Subscriptioninterface withunsubscribe()/pause()/resume()/isActive();unsubscribe()clears all timers so no polling survives.InvoiceEventunion type was already exported and is unchanged.#843 — Retry logic with circuit breaker
The resilience layer (
src/resilientRpc.ts+src/circuitBreaker.ts) already existed inmainand satisfies the requirements:RetryConfig { maxRetries, baseDelayMs, maxDelayMs, jitter },CircuitBreakerConfig { failureThreshold, resetTimeoutMs }, CLOSED → OPEN → HALF-OPEN transitions,circuit:open/circuit:close/circuit:half-openevents forwarded on the client, and non-retryable errors (InvalidInput/Unauthorized) bypassing retries.test/resilience.test.ts(39 cases: retry exhaustion, threshold opening, reset after timeout, non-retryable bypass, event emission) intonpm testso it runs in CI.#844 — Transaction simulation layer via Soroban simulation RPC
Only
simulateCreateInvoice/simulatePayexisted, with bespoke result shapes.client.simulate(method, params): Promise<SimulationResult>builds the contract operation forcreateInvoice,pay,release,approveRelease,cloneInvoice(camelCase or raw entry point), submits it tosimulateTransactionand maps the response toSimulationResult { success, error?, fee, cpuInsns, memBytes, footprint }. Contract-level rejections resolve withsuccess: falseinstead of throwing.{ simulate: true }is supported oncreateInvoice,pay,releaseGroupandrefundInvoice; overloads keep the normal submission return types unchanged, and no sequence number is consumed.SimulationResult(andLedgerFootprint,SimulateMutationOptions,MaybeSimulated) are exported from the package. The sandbox's separate result type is now re-exported asSandboxSimulationResultto avoid the name collision.How it was tested
npm test→ 153 passed / 0 failed (client.test.ts,retryPolicy.test.ts,clientIssueFixes.test.ts,resilience.test.ts).test/clientIssueFixes.test.ts(15 cases):getLineagereturns[1n, 2n, 3n]for a 3-deep chain;simulate()success (fee/cpuInsns/memBytes/footprint), camelCase → entry-point mapping andsuccess: falseon contract error;createInvoice/paywithsimulate: truereturn aSimulationResultand never callsendTransaction;subscribeInvoicestreams events, deduplicates, accepts a bigint ID and stops polling afterunsubscribe().npx tsc --noEmitwas captured before and after the change — the error set is unchanged (the repository's pre-existing 282 errors are untouched; no new TypeScript errors are introduced).npm run buildwas run; the dts step fails on the same pre-existingsrc/index.tsmissing-export errors that exist onmainand is unrelated to this change.Notes
closes #843— the resilience implementation already present onmainis verified and now covered by CI; no behaviour change was required.closes #850
closes #844
closes #842
closes #843