From dc82197f78080a7b0a90b80f88270749e03968be Mon Sep 17 00:00:00 2001 From: Isaac <160560046+Isaacayomide28@users.noreply.github.com> Date: Sat, 17 Oct 2026 08:33:00 +0100 Subject: [PATCH] feat: `rpcErrorMapper` does not classify `tx_bad_seq` or `tx_bad_a (#742) --- examples/react-wallet-connect/src/App.tsx | 8 ++++---- src/shared/response.ts | 4 ++-- src/soroban/rpcErrorMapper.ts | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/react-wallet-connect/src/App.tsx b/examples/react-wallet-connect/src/App.tsx index b49f7bf3..ce9830f7 100644 --- a/examples/react-wallet-connect/src/App.tsx +++ b/examples/react-wallet-connect/src/App.tsx @@ -1,8 +1,8 @@ -/** - * React wallet-connect example — UI layer (#react-wallet-connect). +/* + * React wallet-connect example -- UI layer (#react-wallet-connect). * * Renders connect/disconnect, a balance list, and a payment form. - * All sorokit-core calls are in useSorokit.ts — this file only reads state + * All sorokit-core calls are in useSorokit.ts -- this file only reads state * and calls the callbacks the hook exposes. */ @@ -93,7 +93,7 @@ export default function App() { )} - {/* Payment form — only shown when connected */} + {/* Payment form -- only shown when connected */} {isConnected && (

Send XLM

diff --git a/src/shared/response.ts b/src/shared/response.ts index a46f18b7..d98c4425 100644 --- a/src/shared/response.ts +++ b/src/shared/response.ts @@ -1,4 +1,4 @@ -/** +/* * SorokitResult — the single response format for every public function. * * All public operations return a discriminated result. Error metadata is @@ -74,7 +74,7 @@ export enum SorokitErrorCode { ROUTER_INVALID_PATH = "ROUTER_INVALID_PATH", ROUTER_INSUFFICIENT_LIQUIDITY = "ROUTER_INSUFFICIENT_LIQUIDITY", ROUTER_SLIPPAGE_EXCEEDED = "ROUTER_SLIPPAGE_EXCEEDED", - ROUTER_SWAP_FAILED = "ROUTER_SWAP_FAILED", + ROUTER_SWAPc_FAILED = "ROUTER_SWAP_FAILED", CONTRACT_INVOKE_FAILED = "CONTRACT_INVOKE_FAILED", CONTRACT_READ_FAILED = "CONTRACT_READ_FAILED", CONTRACT_PREPARE_FAILED = "CONTRACT_PREPARE_FAILED", diff --git a/src/soroban/rpcErrorMapper.ts b/src/soroban/rpcErrorMapper.ts index 02fee9ea..a44ae7b0 100644 --- a/src/soroban/rpcErrorMapper.ts +++ b/src/soroban/rpcErrorMapper.ts @@ -44,6 +44,11 @@ export function classifySorobanRpcError(cause: unknown): SorokitErrorCode { const text = extractRpcErrorMessage(cause).toLowerCase(); if (status === 429 || /rate.?limit|too many requests|429/.test(text)) return SorokitErrorCode.RATE_LIMITED; if (/invalid auth|auth.*invalid|signature.*invalid|authorization/.test(text)) return SorokitErrorCode.INVALID_AUTH; + if (/tx_bad_seq|bad.?seq|sequence.*(mismatch|invalid|too (low|high))|txbadseq/.test(text)) return SorokitErrorCode.TX_BAD_SEQ; + if (/tx_bad_auth|bad.?auth|txbadauth|missing.*signature|wrong.*signer/.test(text)) return SorokitErrorCode.TX_BAD_AUTH; + if (/tx_no_account|no.?account|account.*(not found|does not exist)|txnoaccount/.test(text)) return SorokitErrorCode.TX_NO_ACCOUNT; + if (/tx_too_late|too.?late|time.?bound.*(expired|past)|txtoolate/.test(text)) return SorokitErrorCode.TX_TOO_LATE; + if (/tx_too_early|too.?early|time.?bound.*(not yet|future)|txtooearly/.test(text)) return SorokitErrorCode.TX_TOO_EARLY; if (/insufficient fee|fee.*insufficient|insufficient.*base fee|fee bid/.test(text)) return SorokitErrorCode.INSUFFICIENT_FEE; if (/resource exhausted|resource limit|budget exceeded|cpu.*limit|memory.*limit|limit exceeded/.test(text)) return SorokitErrorCode.RESOURCE_LIMIT_EXCEEDED; if (/xdr|invalid transaction|malformed.*transaction|decode/.test(text)) return SorokitErrorCode.XDR_INVALID; @@ -60,6 +65,16 @@ export function mapSorobanRpcError(cause: unknown): SorokitError { ? { retryable: true, action: "Increase the transaction fee using a fresh network estimate, then retry." } : code === SorokitErrorCode.RESOURCE_LIMIT_EXCEEDED ? { retryable: false, action: "Reduce contract work or split the operation before retrying." } + : code === SorokitErrorCode.TX_BAD_SEQ + ? { retryable: true, action: "Re-fetch the source account sequence number and rebuild the transaction before retrying." } + : code === SorokitErrorCode.TX_BAD_AUTH + ? { retryable: false, action: "Verify the signer, signing key, and signature coverage; rebuild and re-sign the transaction." } + : code === SorokitErrorCode.TX_NO_ACCOUNT + ? { retryable: false, action: "Fund or create the source account (or correct the account ID) before retrying." } + : code === SorokitErrorCode.TX_TOO_LATE + ? { retryable: true, action: "Rebuild the transaction with a fresh, later time bound and retry." } + : code === SorokitErrorCode.TX_TOO_EARLY + ? { retryable: true, action: "Wait until the transaction's time bound becomes valid, then retry." } : { retryable: false, action: "Inspect the Soroban RPC details and correct the transaction or contract inputs." }; const result = err(code, `Soroban RPC error: ${message}`, cause, undefined, { recovery }); if (result.status === "error") return result.error;