@lumenwipe/sdk 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,14 @@ transaction happens server-side; this client just relays typed JSON and XDR stri
14
14
  npm install @lumenwipe/sdk
15
15
  ```
16
16
 
17
+ ## Requirements
18
+
19
+ - Node.js 22 or later (`engines.node` is `>=22`; Node 18 and 20 are end-of-life and not tested). No
20
+ runtime dependencies.
21
+ - TypeScript 4.7 or later, with `moduleResolution` set to `node16`, `nodenext` or `bundler`
22
+ (`bundler` needs TypeScript 5.0). The `import` and `require` entry points each ship their own
23
+ declarations.
24
+
17
25
  ## Quick start
18
26
 
19
27
  ```ts
@@ -27,15 +35,19 @@ const client = new LumenWipeClient({
27
35
 
28
36
  const account = await client.getAccount(source);
29
37
 
30
- const plan = await client.closePlan({
31
- source,
32
- destination,
33
- // ...see @lumenwipe/types for the full request shape
34
- });
38
+ const plan = await client.closePlan({ source, destination });
39
+ if (plan.blockers.length > 0) throw new Error(plan.blockers.map((b) => b.message).join("; "));
40
+
41
+ // Every decision point needs an answer (convert or keep a token, acknowledge an exchange, ...).
42
+ // Show them to the user, plan again with the answers, and close with those same answers.
43
+ const decisions = await askUser(plan.decisionPoints);
44
+ const reviewed = await client.closePlan({ source, destination, decisions });
35
45
 
36
46
  const { transactions, remaining } = await client.closeTransactions({
37
47
  source,
38
48
  destination,
49
+ decisions,
50
+ planHash: reviewed.planHash,
39
51
  });
40
52
 
41
53
  // Sign and submit each transaction, then repeat while remaining.requiresAnotherCall is true.
@@ -57,13 +69,45 @@ await runClose({
57
69
  });
58
70
  ```
59
71
 
72
+ ### Progress
73
+
74
+ Pass `onProgressEvent` to follow the close, for example to show "Transaction 2 of 3: approve in
75
+ your wallet":
76
+
77
+ ```ts
78
+ await runClose({
79
+ ...deps,
80
+ onProgressEvent: (event) => {
81
+ if (event.phase === "preparing") return show(`Preparing round ${event.round + 1}…`);
82
+ const of = event.moreRoundsExpected ? `at least ${event.txCount}` : `${event.txCount}`;
83
+ show(`Transaction ${event.txIndex + 1} of ${of}: ${event.phase}`);
84
+ },
85
+ });
86
+ ```
87
+
88
+ - `phase`: `preparing` before each round is fetched; then, for each transaction, `verifying`,
89
+ `awaiting-signature` (the signer, for example a wallet, has been asked), `submitting` (signed;
90
+ the network is waited on) and `confirmed`. A resumed transaction starts at
91
+ `awaiting-signature`.
92
+ - `round`: zero-based round of the close. A resumed close keeps the numbering it stopped at.
93
+ - `txIndex`: zero-based position of the transaction in its round (not on `preparing`).
94
+ - `txCount`: how many transactions the round holds (not on `preparing`).
95
+ - `covers`: the plan steps the transaction covers. API metadata until `confirmed`, which carries
96
+ what `onConfirmed` is given: with `reconcile`, a merge only when the client parsed one.
97
+ - `moreRoundsExpected`: the API asked for another round after this one. Rounds are planned from
98
+ live state, so the close then holds at least `txCount` transactions and its total is not
99
+ known; never show `txCount` as the total while this is `true`.
100
+
101
+ The callback only observes: each event is a fresh object, nothing it returns is awaited, and
102
+ anything it throws is ignored. `onProgress(message)` still gets its plain messages.
103
+
60
104
  ## Headless example
61
105
 
62
106
  [`examples/headless-close.ts`](https://github.com/LumenWipe/lumenwipe/blob/main/packages/sdk/examples/headless-close.ts)
63
107
  closes a throwaway testnet account end to end from a plain Node/Bun process, with no browser or
64
108
  web UI: it funds two accounts, then runs `getAccount`, `closePlan`, `closeTransactions` and
65
- `submit` through `runClose`, signing with an in-process keypair. It decodes and verifies every
66
- transaction itself before signing (`examples/verify.ts`), which is the pattern to copy.
109
+ `submit` through `runClose`, signing with an in-process keypair. It verifies every transaction
110
+ before signing with `@lumenwipe/verify` (`examples/verify.ts`), which is the pattern to copy.
67
111
 
68
112
  ```bash
69
113
  LUMENWIPE_API_KEY=... bun run --filter '@lumenwipe/sdk' example:headless
@@ -71,30 +115,71 @@ LUMENWIPE_API_KEY=... bun run --filter '@lumenwipe/sdk' example:headless
71
115
 
72
116
  `LUMENWIPE_API_URL` overrides the default `https://api.lumenwipe.com`. Testnet only.
73
117
 
118
+ ## Recording what arrived
119
+
120
+ After a close, read the XLM that reached the destination from the ledger rather than from the
121
+ plan. Collect the hashes `runClose` confirmed, then ask once:
122
+
123
+ ```ts
124
+ const hashes: string[] = [];
125
+ await runClose({ ...deps, onConfirmed: (_tx, hash) => hashes.push(hash) });
126
+
127
+ const delivered = await client.closeDelivered(destination, hashes);
128
+ for (const entry of delivered.entries) {
129
+ if (entry.status === "delivered") record(entry.hash, entry.amountStroops);
130
+ else record(entry.hash, `unavailable: ${entry.reason}`); // not_found, failed or no_delivery
131
+ }
132
+ // Set only when every entry was delivered: never treat its absence as zero.
133
+ if (delivered.totalStroops !== undefined) record("total", delivered.totalStroops);
134
+ ```
135
+
136
+ Only a merge into the destination, or the relay account's forward of a merged balance, counts.
137
+ A transaction that delivered nothing this way, such as a cleanup round, is `no_delivery`, so pass
138
+ the hashes whose amount you want: up to 20 per call.
139
+
74
140
  ## Verification is the caller's responsibility
75
141
 
76
142
  This SDK does not verify or sign anything. Every transaction it returns is unsigned XDR built by
77
143
  the API from data you provided - before signing, your own code must independently confirm the
78
144
  transaction does exactly what the user asked (correct destination, no unexpected operations, no
79
145
  added signers, matching amounts) using values from your own inputs, never from the API response
80
- alone. LumenWipe's own web client's verification logic (`assertCloseIntent` in
81
- [`apps/web/lib/stellar/verify.ts`](https://github.com/LumenWipe/lumenwipe/blob/main/apps/web/lib/stellar/verify.ts))
82
- is the reference implementation - read it before wiring up signing in a production integration.
83
- Never sign a transaction from this SDK without an equivalent check.
146
+ alone. [`@lumenwipe/verify`](https://github.com/LumenWipe/lumenwipe/tree/main/packages/verify)
147
+ is that check: the verifier LumenWipe's own web client signs with, compiled from
148
+ [`apps/web/lib/stellar/verify.ts`](https://github.com/LumenWipe/lumenwipe/blob/main/apps/web/lib/stellar/verify.ts),
149
+ with a conformance vector set. Pass it as `runClose`'s `verify` (see the docs' Verification page).
150
+ Never sign a transaction from this SDK without it or an equivalent check.
84
151
 
85
152
  ## API surface
86
153
 
87
- - `health()` - service health check.
154
+ - `health()` / `healthDeep()` - service health, and whether Stellar RPC is reachable on every
155
+ network (a degraded service throws its 503, never retried).
156
+ - `getExchangeRegistry()` - the exchange registry the API closes against; do not rely on it past
157
+ `validUntil`.
88
158
  - `getAccount(address, network?)` - current on-chain state for an account.
89
159
  - `getPaths(params, network?)` - conversion path quotes for a source asset.
90
160
  - `closePlan(body, network?)` - a preview of the full close plan, with blockers if any step
91
161
  cannot be closed safely.
162
+ - `batchPlan(body, network?)` - plans up to 20 accounts into one destination, read-only.
92
163
  - `closeTransactions(body, network?)` - builds the next round of unsigned transactions.
93
164
  - `submit(signedXdr, network?)` - submits a signed transaction.
94
165
  - `mediatorCheck(address, network?)` / `mediatorSign(transaction, network?)` - the exchange
95
166
  mediator flow (see the architecture doc).
167
+ - `getTransaction(hash, network?)` / `closeStatus(source, network?)` - look a transaction up by
168
+ hash, and read a close's phase from live state.
169
+ - `closeDelivered(destination, hashes, network?)` - the XLM confirmed close transactions
170
+ delivered to the destination, read from the ledger's result (see below).
171
+ - `feeBumpSponsor(transaction, network?)`, `getAllowances(address, network?)` and
172
+ `revokeAllowance(params, network?)` - fee sponsorship and the allowance inspector.
173
+ - `getStats(network?)` / `getStatsFeed(network?)` / `recordMerge(txHash, network?)` - close
174
+ totals, and counting a close submitted outside the API.
96
175
  - `runClose(deps)` - drives the full fetch/verify/sign/submit loop to completion.
97
176
 
177
+ Every call uses the API's `/v1` paths, except `health()` and `healthDeep()`.
178
+
179
+ `LumenWipeIntegratorClient` is a separate entry point for a wallet's own API keys: `challenge`,
180
+ `session`, `listKeys`, `createKey`, `revokeKey` and `rotateKey`. It takes no API key and never
181
+ sends one; the key calls carry only the wallet's session token. See the SDK docs.
182
+
98
183
  Request and response types come from `@lumenwipe/types` and are re-exported from this package, so
99
184
  no separate install is needed.
100
185
 
@@ -103,6 +188,8 @@ no separate install is needed.
103
188
  - `LumenWipeApiError` - thrown on any non-2xx API response; carries `status`, `code`, `details`,
104
189
  `retryAfterMs`, `requestId` and the parsed error `body`. Narrow with `isApiErrorCode`.
105
190
  - `LumenWipeAbortError` - thrown when the `signal` you passed aborts the request.
191
+ - `LumenWipeInvalidNetworkError` - raised before any request when `network` is not `"mainnet"` or
192
+ `"testnet"`.
106
193
  - `LumenWipeTimeoutError` - thrown when a request exceeds the configured `timeout`
107
194
  (default 30s).
108
195