Skip to content

core: Soroban RPC client with retry, timeout, and circuit breaking - #70

Merged
joelpeace48-cell merged 1 commit into
FinesseStudioLab:mainfrom
Codeinx:issue-44-soroban-client
Aug 25, 2026
Merged

joelpeace48-cell merged 1 commit into
FinesseStudioLab:mainfrom
Codeinx:issue-44-soroban-client

Conversation

@Codeinx

@Codeinx Codeinx commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Problem

There was no Soroban integration at all — no client, no transaction handling — despite that being the service's stated purpose. Public RPC endpoints are rate-limited and intermittently unavailable, so a naive implementation would be flaky in exactly the way that erodes trust in a custody-adjacent service.

What this adds

New module: src/soroban/

  • Timeouts on every call, enforced by racing the call against a timer rather than relying on the callee to cooperate with an AbortSignal — so a hung transport still returns (with a timeout error) instead of blocking forever.
  • Retry with exponential backoff and full jitter, bounded by maxAttempts — applied only to idempotent operations (simulate, getTransactionStatus).
  • Circuit breaker (src/soroban/circuitBreaker.ts) that trips after consecutive failures and fails fast while open, instead of queuing requests behind a dead endpoint.
  • Correct submission flow: submit() is never retried by the client. The only recovery path after a submit error is pollUntilConfirmed(), which polls by transaction hash — blind-retrying a submit is how double-spends happen. submitAndConfirm() chains submit → poll for callers that want the full flow.
  • Typed errors: SorobanTransportError, SorobanTimeoutError, SorobanSimulationError, SorobanContractError, CircuitOpenError — so callers can branch on failure kind instead of parsing message strings.

Kept to a lightweight fetch-based JSON-RPC transport rather than pulling in the full @stellar/stellar-sdk, so this stays small and easy to mock, and the SDK's higher-level transaction builders can be layered on top later without touching the retry/timeout/circuit-breaking behavior here.

Testing

src/soroban/client.test.ts runs against a mocked RpcTransport, covering:

  • a transient transport failure that succeeds on retry
  • a call that hangs and is caught by the timeout
  • the circuit opening after repeated failures and failing fast on the next call
  • submit() never being retried internally, even on failure
  • the poll-after-429 recovery path

All existing tests continue to pass; npm run lint, npm run build, npm test, and npm audit --audit-level=high are all clean on this branch.

Scope note

This PR only adds src/soroban/** (new files) — it does not touch any file shared with the other two PRs open against this issue tracker (#54, #58), so it should apply independently of merge order.

Closes #44.

Adds src/soroban: a resilient wrapper around Soroban RPC, since public
RPC endpoints are rate-limited and intermittently unavailable.

- Every call is bounded by a timeout enforced by racing against a
  timer (not by relying on the callee to honor an AbortSignal), so a
  hung transport still returns rather than blocking forever.
- Idempotent operations (simulate, getTransactionStatus) get
  exponential backoff with full jitter, bounded by maxAttempts.
- A circuit breaker trips after consecutive failures and fails fast
  while open, rather than queuing requests behind a dead endpoint.
- Transaction submission is never retried by the client. The only
  recovery path after a submit error is pollUntilConfirmed, which
  polls by transaction hash — blind-retrying a submit is how
  double-spends happen.
- Errors are typed (SorobanTransportError / SorobanTimeoutError /
  SorobanSimulationError / SorobanContractError / CircuitOpenError)
  instead of opaque strings, so callers can branch on failure kind.
- Tests run against a mocked RPC transport, covering transient
  transport failures, a hung/timed-out call, circuit-open behavior
  after repeated failures, and the poll-after-429 recovery path.

Kept to a lightweight fetch-based JSON-RPC transport rather than
pulling in the full @stellar/stellar-sdk, so the higher-level
transaction builders can be layered on top later without touching the
retry/timeout/circuit-breaking behavior added here.

Closes FinesseStudioLab#44.
@joelpeace48-cell
joelpeace48-cell merged commit bc74867 into FinesseStudioLab:main Aug 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: build a Soroban RPC client with retry, timeout, and circuit breaking

2 participants