Skip to content

fix(batch): only fall back to direct Horizon on network-layer errors (#14) - #101

Open
ghzhost wants to merge 1 commit into
StellarSend:mainfrom
ghzhost:fix/batch-fallback-discrimination-14
Open

fix(batch): only fall back to direct Horizon on network-layer errors (#14)#101
ghzhost wants to merge 1 commit into
StellarSend:mainfrom
ghzhost:fix/batch-fallback-discrimination-14

Conversation

@ghzhost

@ghzhost ghzhost commented Sep 2, 2026

Copy link
Copy Markdown

Problem (fixes #14)

useBatchPayment's batchPaymentApi.send catch block previously swallowed every error and blindly resubmitted the already-signed XDR straight to Horizon, regardless of error type.

This caused two concrete failure modes:

  1. 4xx validation error (e.g. sanctioned recipient, insufficient trustline): the backend explicitly rejected the payload, but the code pushed it straight to the public ledger, bypassing the validation.
  2. 5xx / 502 after successful backend broadcast: the backend may have already submitted the transaction on-chain; the fallback resubmission gets tx_bad_seq and the user sees a confusing error at exactly the point where the payment actually worked.

Fix

src/lib/api.ts

  • Added optional httpStatus?: number field to ApiRequestError, populated by normalizeApiError from the Axios response status. A missing httpStatus unambiguously identifies a pure network-layer failure (no server response arrived).
  • New exported helper isNetworkLayerError(error) encapsulates the check in one unit-testable place.

src/hooks/useBatchPayment.ts

  • The catch block now calls isNetworkLayerError and only falls back to submitTransaction when it returns true (no HTTP response = backend never received the request).
  • 4xx, 5xx, and 502 errors are re-thrown so the caller sees a real, actionable error.

Tests added

src/hooks/useBatchPayment.test.tsx — 10 tests:

  • Normal backend-success path (no Horizon call)
  • Pure network error → fallback triggers, Horizon called once
  • 400 validation → error surfaced, Horizon not called
  • 500 server → error surfaced, Horizon not called
  • 422 unprocessable → error surfaced, Horizon not called
  • 502 bad gateway → error surfaced, Horizon not called
  • isNetworkLayerError unit tests (with/without status, non-ApiRequestError)

All 123 existing tests still pass.

…tellarSend#14)

batchPaymentApi.send's catch block previously swallowed every error and
blindly resubmitted the signed XDR straight to Horizon, bypassing
backend validation and risking double-spend when the backend had already
broadcast the transaction (the HTTP response was lost).

The fix:

1. ApiRequestError gains an optional httpStatus field populated by
   normalizeApiError from the Axios response status.  A missing httpStatus
   unambiguously identifies a pure network-layer failure (no server
   response arrived at all).

2. New exported helper isNetworkLayerError(error) encapsulates that check
   in a single, unit-testable place shared across hooks.

3. useBatchPayment's catch block now calls isNetworkLayerError and only
   falls back to submitTransaction when it returns true.  4xx (validation/
   client), 5xx (server), and 502 (gateway) errors are re-thrown so the
   caller sees a real error rather than a spurious success.

Tests added in useBatchPayment.test.tsx covering:
- normal backend-success path (no Horizon call)
- pure network error → fallback triggers
- 400 validation error → error surfaced, no fallback
- 500 server error → error surfaced, no fallback
- 422 unprocessable → error surfaced, no fallback
- 502 bad gateway → error surfaced, no fallback
- isNetworkLayerError unit tests for all variants
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.

useBatchPayment silently swallows batch API errors and blindly resubmits the already-signed XDR straight to Horizon

1 participant