Skip to content

feat: add SDK transaction timeout classification (#208) - #327

Merged
El-swaggerito merged 1 commit into
Axionvera:mainfrom
XxHugheadxX:feat/timeout-classification
Jul 27, 2026
Merged

feat: add SDK transaction timeout classification (#208)#327
El-swaggerito merged 1 commit into
Axionvera:mainfrom
XxHugheadxX:feat/timeout-classification

Conversation

@XxHugheadxX

Copy link
Copy Markdown
Contributor

Closes #208

Summary

Classifies SDK timeouts by the stage they interrupt, and routes the two stages
whose outcome is undetermined to TX_STATUS_UNKNOWN so consumers know when
retrying is unsafe.

The gap

Every timeout in the SDK came from a single timeoutError() in
src/network/index.ts, which threw the code REQUEST_TIMEOUT — absent from the
ERROR_CODES registry — and fed 33 withTimeout call sites spanning account
loads, simulation, submission and confirmation polling.

The stage was already available: it arrives as withTimeout's first argument.
It was only interpolated into the message and then discarded.

Why it mattered

payments/index.ts wraps server.submitTransaction in withTimeout, and its
recovery-hint block matched REQUEST_TIMEOUT to push action: 'retry',
retryable: true, suggestedDelayMs: 3000 — for a payment whose outcome was
unknown. Advising a retry there risks paying twice.

TX_STATUS_UNKNOWN and isUnknownStatusError() exist for exactly this case,
but the helper matches only that code, so it returned false for every timeout
the SDK threw.

Mapping timeouts to NET_TIMEOUT would have been worse: it is retryable: true,
so any automation reading retryable would resend the payment.

Changes

  • src/types/index.tsTimeoutStage (preparation | submission |
    confirmation | unknown) and TimeoutMetadata; PocketPayError.timeout
    carries stage, operation and budget.
  • src/network/index.tsinferTimeoutStage() derives the stage from the
    operation label already passed; withTimeout takes an optional explicit
    stage that wins over inference. Submission and confirmation timeouts report
    TX_STATUS_UNKNOWN; every other stage keeps REQUEST_TIMEOUT.
  • src/errors/codes.tsREQUEST_TIMEOUT added to the registry, with a
    developer hint pointing at error.timeout.stage.
  • src/payments/index.ts — a TX_STATUS_UNKNOWN failure now gets a
    check_status hint with retryable: false, instead of the plain retry hint.
  • docs/timeout-classification.md (new) — stages, recovery per stage, and
    why NET_TIMEOUT was not used.

Existing consumers remain compatible

REQUEST_TIMEOUT keeps its exact code string and message format, and is now a
registered code rather than an unknown one, so describeError() returns real
guidance instead of the generic fallback.

The four existing assertions on REQUEST_TIMEOUT — in tests/fund.test.ts,
tests/mockHorizon.test.ts and tests/transactions.test.ts (×2) — all stay
green, because none of them is a submission or confirmation timeout: they cover
account lookup, Friendbot and plain read requests. Only those two stages change
code.

Behaviour change: submission and confirmation timeouts report
TX_STATUS_UNKNOWN instead of REQUEST_TIMEOUT. Consumers branching on
REQUEST_TIMEOUT for those stages should use isUnknownStatusError().

Tests

17 new tests in tests/timeout-classification.test.ts: stage inference across
the real operation labels used in the codebase; explicit stage overriding
inference; metadata attached to every timeout; each of the four stages covered;
isUnknownStatusError() true after submission and confirmation timeouts and
false after preparation; the unknown-outcome timeout never reported as retryable
and never mapped to NET_TIMEOUT; REQUEST_TIMEOUT now registry-known; and
successful work plus non-timeout rejections passing through unchanged.

Verification

lint, check:circular (46 modules, no cycles) and build all pass.

Full suite: 46 failed · 765 passed · 1 skipped. The 46 failures are
pre-existing on the base commit — verified against a clean checkout of
ef46a9a, which gives 46 failed · 748 passed · 1 skipped: same failures in
the same five files. This branch adds exactly the 17 new passing tests and
introduces no regressions.

@El-swaggerito
El-swaggerito merged commit 8799f9d into Axionvera:main Jul 27, 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.

Add SDK transaction timeout classification

2 participants