Add timeout, retry-with-backoff to all Horizon calls in stellar.ts - #57
Open
mac-dubem wants to merge 1 commit into
Open
Add timeout, retry-with-backoff to all Horizon calls in stellar.ts#57mac-dubem wants to merge 1 commit into
mac-dubem wants to merge 1 commit into
Conversation
The Horizon.Server constructor now receives an explicit 10s timeout for every request. Idempotent reads (getAccount/getBalances) are wrapped in retryWithBackoff (up to 3 retries, 500ms base delay, exponential backoff). sendPayment's submitTransaction is NOT retried to avoid user-perceived double-processing risk, though the timeout still applies. Tests cover: timeout config, retry on transient failure, retry exhaustion, non-retry of submission, and balance parsing.
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.
Issue
Closes #36
Root cause
Every Horizon call in
stellar.ts(getAccount,getBalances,sendPayment) went throughnew StellarSdk.Horizon.Server(HORIZON_URL)with no timeout config and no retry handling. On mobile networks a single slow or dropped request causes the entire balance load or payment to hang indefinitely or fail outright.What changed and why
Explicit timeout on every Horizon call — the
Serverconstructor now receives{ timeout: 10_000 }(10s). The SDK passes this to the underlying axios client, so it applies uniformly toloadAccount,submitTransaction, and every other call the server instance makes.Retry-with-backoff for idempotent reads —
getAccount/getBalancesare wrapped inretryWithBackoff: up to 3 retries, 500ms base delay, exponential backoff (500ms → 1s → 2s).getBalancescalls the exportedgetAccount, so it inherits the behavior automatically.No retry for
sendPaymentsubmission —server.submitTransaction(tx)is called directly without retry. ThegetAccountstep insidesendPaymentstill benefits from retry (it calls the exportedgetAccount).submitTransactionis not retried because even though Horizon is idempotent per-transaction-hash, blind client-side retry risks the user perceiving a double charge (two "submitting" UI states). The timeout still applies via the server config.sendPaymentinternalgetAccountstill retries —sendPaymentcalls the exportedgetAccount(line 67), so the idempotent read step gains resilience while the non-idempotent submission step does not.Definition of done — addressed item by item
{ timeout: HORIZON_TIMEOUT }passed tonew Horizon.Server(...)(cast viaas anybecause the SDK typeOptionsis missing the property at the type level, though the runtime accepts it).getAccountwrapped inretryWithBackoff(3, 500ms).getBalancescallsgetAccount, so it inherits the retry.sendPaymentsubmission —submitTransactioncalled directly, no wrapper. Rationale above.Evidence this actually runs
Tests
New file
src/services/__tests__/stellar.test.ts(7 tests):Horizon timeout configuration › passes a timeouttimeoutgetAccount › retries on transient failure and succeedsgetAccount › throws after exhausting retriesgetBalances › retries via getAccount on underlying failuregetBalances › parses native and non-native balancessendPayment › retries getAccount but not submitTransactionsendPayment › happy path — returns submission resultRegression check
sendPaymentstill calls the exportedgetAccountfor account loading (unchanged contract)generateKeypairis unchanged (no Horizon calls) — no regression possibleType check