Skip to content

feat(client): clone overrides + lineage, dry-run simulation, invoice event streaming, RPC resilience - #900

Merged
Kingsman-99 merged 2 commits into
Stellar-split:mainfrom
terngunan:feat/issues-850-844-843-842-client-features
Sep 28, 2026
Merged

Kingsman-99 merged 2 commits into
Stellar-split:mainfrom
terngunan:feat/issues-850-844-843-842-client-features

Conversation

@terngunan

Copy link
Copy Markdown

What the issues were and how this PR resolves them

#850 — Add cloneInvoice with field override support

cloneInvoice accepted only string IDs and a narrow CloneOverrides shape, 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's clone_invoice override map and validated before submission (non-empty title, future deadline, positive targetAmount, valid Stellar addresses, amount/recipient counts aligned) using the same rules as createInvoice.
  • getLineage(invoiceId): Promise<bigint[]> returns the full ancestor chain in root → leaf order, ending with invoiceId itself.
  • Terminal/non-cloneable source invoices still throw InvoiceNotCloneableError via the existing cloneability pre-flight.

#842 — Implement subscribeInvoice real-time event streaming

There was a low-level createInvoiceSubscription helper but no client method.

  • client.subscribeInvoice(invoiceId: bigint | string, callback, options?): Subscription polls Soroban getEvents (pollIntervalMs, default 3000ms), deduplicates by ledger sequence + topic hash, and reconnects with exponential backoff (max 5 retries) before emitting an error lifecycle event.
  • Returns the existing Subscription interface with unsubscribe() / pause() / resume() / isActive(); unsubscribe() clears all timers so no polling survives.
  • InvoiceEvent union type was already exported and is unchanged.

#843 — Retry logic with circuit breaker

The resilience layer (src/resilientRpc.ts + src/circuitBreaker.ts) already existed in main and satisfies the requirements: RetryConfig { maxRetries, baseDelayMs, maxDelayMs, jitter }, CircuitBreakerConfig { failureThreshold, resetTimeoutMs }, CLOSED → OPEN → HALF-OPEN transitions, circuit:open / circuit:close / circuit:half-open events forwarded on the client, and non-retryable errors (InvalidInput/Unauthorized) bypassing retries.

  • This PR documents the feature and behaviour in the README and wires test/resilience.test.ts (39 cases: retry exhaustion, threshold opening, reset after timeout, non-retryable bypass, event emission) into npm test so it runs in CI.

#844 — Transaction simulation layer via Soroban simulation RPC

Only simulateCreateInvoice / simulatePay existed, with bespoke result shapes.

  • client.simulate(method, params): Promise<SimulationResult> builds the contract operation for createInvoice, pay, release, approveRelease, cloneInvoice (camelCase or raw entry point), submits it to simulateTransaction and maps the response to SimulationResult { success, error?, fee, cpuInsns, memBytes, footprint }. Contract-level rejections resolve with success: false instead of throwing.
  • { simulate: true } is supported on createInvoice, pay, releaseGroup and refundInvoice; overloads keep the normal submission return types unchanged, and no sequence number is consumed.
  • SimulationResult (and LedgerFootprint, SimulateMutationOptions, MaybeSimulated) are exported from the package. The sandbox's separate result type is now re-exported as SandboxSimulationResult to 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).
  • New test/clientIssueFixes.test.ts (15 cases):
    • getLineage returns [1n, 2n, 3n] for a 3-deep chain;
    • clone with no overrides, clone with partial overrides, override validation failures (title/deadline/targetAmount/recipients) and terminal-invoice clone throwing;
    • simulate() success (fee/cpuInsns/memBytes/footprint), camelCase → entry-point mapping and success: false on contract error;
    • createInvoice/pay with simulate: true return a SimulationResult and never call sendTransaction;
    • subscribeInvoice streams events, deduplicates, accepts a bigint ID and stops polling after unsubscribe().
  • Type check: npx tsc --noEmit was 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 build was run; the dts step fails on the same pre-existing src/index.ts missing-export errors that exist on main and is unrelated to this change.

Notes

  • README documents dry-run simulation, cloning/lineage, real-time streaming and the retry/circuit-breaker configuration.
  • closes #843 — the resilience implementation already present on main is verified and now covered by CI; no behaviour change was required.

closes #850
closes #844
closes #842
closes #843

…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
@drips-wave

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

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

Learn more about application limits

@Kingsman-99
Kingsman-99 merged commit b4abc22 into Stellar-split:main Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants