Skip to content

Commit 5364fe3

Browse files
PocketPay Contributorrexx010
authored andcommitted
docs: add Network Resilience Layer guide, link NET_UNREACHABLE
- docs/network-resilience.md (new): consolidated guide to NetworkClient, the typed failure codes (REQUEST_TIMEOUT, NET_RATE_LIMITED, NET_UNREACHABLE, HTTP_ERROR_<status>, NETWORK_ERROR), why read-only calls are safe to retry but transaction submission is not, and the two endpoint-diagnostics tools (config snapshot vs. live reachability probe). Cross-links network-errors.md and retry-policy.md rather than duplicating their content. - docs/network-errors.md: adds a NET_UNREACHABLE / NET_RATE_LIMITED row and links to the new guide. - README.md: adds the new guide to the docs index. Refs #272 (Files: docs/network-resilience.md [new, 125 lines], docs/network-errors.md, +6 lines, README.md, +1 line)
1 parent afdb3db commit 5364fe3

3 files changed

Lines changed: 132 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ npm install @axionvera/pocketpay-sdk
6262
- [Local Mobile Consumption](./docs/local-mobile-consumption.md) - Safely test unpublished SDK changes in `pocketpay-mobile`with tarballs, links, local paths, or workspaces
6363
- [Transaction Date Formatting](./docs/transaction-timestamps.md) - Format of every `createdAt` timestamp returned by the SDK
6464
- [Network Error Handling](./docs/network-errors.md) - Retry guidance for Horizon, Friendbot, and Soroban RPC failures
65+
- [Network Resilience Layer](./docs/network-resilience.md) - The `NetworkClient` abstraction, typed timeout/rate-limit/unreachable errors, and endpoint reachability diagnostics
6566
- [Safe Retry Policy](./docs/retry-policy.md) - Classifying submission outcomes, safe retry rules, and the `withRetryPolicy` API
6667
- [Account Sequence & Concurrency Safety](./docs/sequence-safety.md) - Account sequence number handling, caching, stale sequence error classification, and in-process concurrency safety with SequenceProvider
6768
- [Meaningful Change Review Guide](./docs/meaningful-change-review.md) - what counts as real SDK work: behaviour, modules, tests, acceptance criteria + reviewer checks

docs/network-errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This guide helps PocketPay SDK consumers handle transient failures from Stellar network services correctly.
44

5+
> See [Network Resilience Layer](./network-resilience.md) for the `NetworkClient`
6+
> abstraction, endpoint diagnostics, and how read-only retries differ from
7+
> transaction submission.
8+
59
## Overview
610

711
Stellar network calls can fail for different reasons. Some failures are temporary and should be retried. Others indicate a problem with the request or account state and should be shown to the user.
@@ -20,6 +24,8 @@ These errors are temporary. Retry with exponential backoff (start at 1s, double
2024
| ECONNRESET / ETIMEDOUT | Network interruption | Retry with backoff |
2125
| REQUEST_TIMEOUT | SDK timeout during preparation or a plain read | Retry with backoff or increase `timeout` |
2226
| 500 Internal Server Error | Transient Horizon issue | Retry once, then fail |
27+
| NET_UNREACHABLE | Endpoint unreachable (5xx, ECONNREFUSED, DNS failure) — thrown by `NetworkClient`, `checkEndpointReachability` | Retry with backoff |
28+
| NET_RATE_LIMITED | 429 — thrown by `NetworkClient` with the typed code instead of a raw HTTP status | Retry after Retry-After |
2329

2430
### Friendbot
2531

docs/network-resilience.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Network Resilience Layer
2+
3+
This is the top-level guide to how the SDK talks to the network: the client
4+
abstraction, the typed errors it produces, how read-only calls differ from
5+
state-changing ones, and how to check endpoint health. For the full status
6+
code and result-code reference, see [Network Error Handling](./network-errors.md);
7+
for the transaction retry state machine, see [Safe Retry Policy](./retry-policy.md).
8+
9+
## `NetworkClient`
10+
11+
All raw `fetch` calls in the SDK are expected to go through `NetworkClient` (or
12+
`fetchWithTimeout`, which it's built on) rather than being made ad hoc from
13+
individual modules. It centralises three things every call needs: a timeout
14+
budget, JSON parsing, and typed error classification.
15+
16+
```ts
17+
import { NetworkClient } from 'stellar-pocketpay-sdk';
18+
19+
const client = new NetworkClient({
20+
baseUrl: 'https://friendbot.stellar.org',
21+
defaultTimeoutMs: 10_000,
22+
});
23+
24+
const data = await client.get('?addr=GABC...', { operation: 'Friendbot funding request' });
25+
```
26+
27+
`get()` and `post()` both accept a per-call `timeoutMs` and `operation` label
28+
(used in error messages and timeout-stage inference — see
29+
[Timeout Classification](./timeout-classification.md)).
30+
31+
## Typed failure codes
32+
33+
`NetworkClient` classifies every failure into one of these codes before
34+
throwing, so callers can branch on `error.code` / `error.retryable` instead of
35+
inspecting HTTP status numbers or `error.message`:
36+
37+
| Code | When | Retryable |
38+
|---|---|---|
39+
| `REQUEST_TIMEOUT` | The SDK's own timeout budget elapsed (see `withTimeout`) | Yes (unless the stage is `submission`/`confirmation` — see below) |
40+
| `NET_RATE_LIMITED` | HTTP 429 | Yes |
41+
| `NET_UNREACHABLE` | HTTP 5xx, or the request never reached the endpoint (`ECONNREFUSED`, `ENOTFOUND`, `EAI_AGAIN`, `ENETUNREACH`, `EHOSTUNREACH`) | Yes |
42+
| `HTTP_ERROR_<status>` | Any other non-2xx response (400, 401, 403, 404, …) | No |
43+
| `NETWORK_ERROR` | An unrecognized fetch rejection | Depends — inspect `error.cause` |
44+
45+
All of the above are published, stable codes in `ERROR_CODES` (see
46+
`src/errors/codes.ts`); use `describeError(code)` to get a safe user-facing
47+
message and developer hint for any of them.
48+
49+
```ts
50+
import { NetworkClient, describeError } from 'stellar-pocketpay-sdk';
51+
52+
try {
53+
await client.get('/accounts/GABC...');
54+
} catch (error) {
55+
if (error instanceof PocketPayError) {
56+
const { safeMessage, retryable } = describeError(error.code);
57+
if (retryable) {
58+
// back off and retry the same read-only request
59+
}
60+
}
61+
}
62+
```
63+
64+
## Read-only calls vs. state-changing submission
65+
66+
**Read-only requests** (account lookups, fee stats, transaction history,
67+
Friendbot funding) are safe to retry whenever `error.retryable` is `true`.
68+
Nothing on the server changes state just because a GET was retried.
69+
70+
**Transaction submission is different.** A submission that times out or hits
71+
`NET_UNREACHABLE` may or may not have reached the network — resubmitting
72+
blindly risks a duplicate payment. The SDK never does this automatically:
73+
74+
- `submitTransactionIdempotently()` resolves this by polling Horizon for the
75+
transaction hash before deciding anything.
76+
- `withRetryPolicy()` builds on that: it only resubmits the same envelope for
77+
`retryable_failure` outcomes, and always requires a status check
78+
(`requiresStatusCheck`) before any further action on `unknown_status`.
79+
80+
See [Safe Retry Policy](./retry-policy.md) for the full state machine. The
81+
short version: **retry reads freely; never retry a submission without going
82+
through `submitTransactionIdempotently` or `withRetryPolicy`.**
83+
84+
## Endpoint diagnostics
85+
86+
Two complementary tools are available, both safe to share with support —
87+
neither ever includes secret keys, signed XDR, or response bodies.
88+
89+
### Config snapshot (no network calls)
90+
91+
`buildDiagnosticsReport()` returns a redacted snapshot of the resolved
92+
configuration — network, Horizon/Soroban URLs, timeout, capability status —
93+
without making any request:
94+
95+
```ts
96+
import { buildDiagnosticsReport } from 'stellar-pocketpay-sdk';
97+
98+
const report = buildDiagnosticsReport({ network: 'testnet' });
99+
```
100+
101+
### Live reachability probe (opt-in network calls)
102+
103+
`checkEndpointReachability(url)` and `probeConfiguredEndpoints(config)` make a
104+
lightweight GET against an endpoint and report only whether it responded, how
105+
long it took, and a typed error code if it didn't — never the response body:
106+
107+
```ts
108+
import { probeConfiguredEndpoints } from 'stellar-pocketpay-sdk';
109+
110+
const diagnostics = await probeConfiguredEndpoints({ network: 'testnet' });
111+
// { generatedAt, horizon: { url, reachable, latencyMs }, sorobanRpc: { ... } }
112+
```
113+
114+
Because this makes real network calls, it is never invoked automatically by
115+
`buildDiagnosticsReport()` — call it explicitly when you need to check live
116+
connectivity (e.g. a support "test connection" button).
117+
118+
## Malformed configuration
119+
120+
Every network call resolves configuration through `resolveConfig()` /
121+
`validatePocketPayConfig()` first, which validates the network name, Horizon
122+
and Soroban RPC URLs, timeout, and contract ID before any request is made. An
123+
invalid config throws (or, for `validatePocketPayConfig`, reports structured
124+
issues) instead of silently falling back to a default — see
125+
[SDK Configuration](./configuration.md) for the full validation rules.

0 commit comments

Comments
 (0)