@lumenwipe/sdk 0.2.0 → 0.7.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 +22 -7
- package/dist/index.cjs +221 -39
- package/dist/index.d.cts +148 -28
- package/dist/index.d.ts +148 -28
- package/dist/index.js +222 -39
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -1,12 +1,58 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
1
21
|
// src/errors.ts
|
|
22
|
+
function parseRetryAfterMs(value) {
|
|
23
|
+
if (!value) return void 0;
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
26
|
+
const at = Date.parse(trimmed);
|
|
27
|
+
return Number.isNaN(at) ? void 0 : Math.max(0, at - Date.now());
|
|
28
|
+
}
|
|
29
|
+
function readEnvelope(body) {
|
|
30
|
+
if (typeof body !== "object" || body === null || !("error" in body)) return void 0;
|
|
31
|
+
const error = body.error;
|
|
32
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
33
|
+
const { code, message, details } = error;
|
|
34
|
+
if (typeof code !== "string" || typeof message !== "string") return void 0;
|
|
35
|
+
return { code, message, details };
|
|
36
|
+
}
|
|
2
37
|
var LumenWipeApiError = class extends Error {
|
|
3
|
-
constructor(status, body) {
|
|
4
|
-
|
|
38
|
+
constructor(status, body, headers) {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
const envelope = readEnvelope(body);
|
|
41
|
+
super(
|
|
42
|
+
envelope ? `LumenWipe API error ${status} ${envelope.code}: ${envelope.message}` : `LumenWipe API error ${status}`
|
|
43
|
+
);
|
|
5
44
|
this.status = status;
|
|
6
45
|
this.body = body;
|
|
7
46
|
this.name = "LumenWipeApiError";
|
|
47
|
+
this.code = (_a = envelope == null ? void 0 : envelope.code) != null ? _a : "upstream_error";
|
|
48
|
+
this.details = envelope == null ? void 0 : envelope.details;
|
|
49
|
+
this.retryAfterMs = parseRetryAfterMs(headers == null ? void 0 : headers.get("Retry-After"));
|
|
50
|
+
this.requestId = (_b = headers == null ? void 0 : headers.get("X-Request-Id")) != null ? _b : void 0;
|
|
8
51
|
}
|
|
9
52
|
};
|
|
53
|
+
function isApiErrorCode(error, code) {
|
|
54
|
+
return error instanceof LumenWipeApiError && error.code === code;
|
|
55
|
+
}
|
|
10
56
|
var LumenWipeTimeoutError = class extends Error {
|
|
11
57
|
constructor(timeoutMs) {
|
|
12
58
|
super(`LumenWipe API request timed out after ${timeoutMs}ms`);
|
|
@@ -14,21 +60,90 @@ var LumenWipeTimeoutError = class extends Error {
|
|
|
14
60
|
this.name = "LumenWipeTimeoutError";
|
|
15
61
|
}
|
|
16
62
|
};
|
|
63
|
+
var LumenWipeAbortError = class extends Error {
|
|
64
|
+
constructor(reason) {
|
|
65
|
+
super("LumenWipe API request was aborted");
|
|
66
|
+
this.reason = reason;
|
|
67
|
+
this.name = "LumenWipeAbortError";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// src/version.ts
|
|
72
|
+
var SDK_VERSION = "0.7.0";
|
|
17
73
|
|
|
18
74
|
// src/http.ts
|
|
75
|
+
var MAX_DELAY_MS = 3e4;
|
|
76
|
+
var TRANSIENT_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
77
|
+
var THROTTLE_STATUSES = /* @__PURE__ */ new Set([429, 503]);
|
|
19
78
|
var HttpTransport = class {
|
|
20
|
-
constructor(baseUrl, apiKey, timeout, doFetch) {
|
|
79
|
+
constructor(baseUrl, apiKey, timeout, doFetch, retry) {
|
|
21
80
|
this.baseUrl = baseUrl;
|
|
22
81
|
this.apiKey = apiKey;
|
|
23
82
|
this.timeout = timeout;
|
|
24
83
|
this.doFetch = doFetch;
|
|
84
|
+
this.retry = retry;
|
|
25
85
|
}
|
|
26
|
-
async request(method, path, body) {
|
|
27
|
-
|
|
86
|
+
async request(method, path, body, opts) {
|
|
87
|
+
var _a, _b;
|
|
88
|
+
const attempts = opts.retry === "never" ? 1 : Math.max(1, Math.floor((_b = (_a = this.retry) == null ? void 0 : _a.attempts) != null ? _b : 1));
|
|
89
|
+
for (let attempt = 1; ; attempt++) {
|
|
90
|
+
try {
|
|
91
|
+
return await this.once(method, path, body, opts.signal);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (attempt >= attempts || !this.shouldRetry(opts.retry, e)) throw e;
|
|
94
|
+
await this.sleep(this.delayMs(e, attempt), opts.signal);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
shouldRetry(policy, e) {
|
|
99
|
+
if (e instanceof LumenWipeApiError) {
|
|
100
|
+
const statuses = policy === "transient" ? TRANSIENT_STATUSES : THROTTLE_STATUSES;
|
|
101
|
+
return statuses.has(e.status);
|
|
102
|
+
}
|
|
103
|
+
return policy === "transient" && !(e instanceof LumenWipeTimeoutError) && !(e instanceof LumenWipeAbortError);
|
|
104
|
+
}
|
|
105
|
+
delayMs(e, attempt) {
|
|
106
|
+
var _a, _b;
|
|
107
|
+
if (e instanceof LumenWipeApiError && e.retryAfterMs !== void 0) {
|
|
108
|
+
return Math.min(e.retryAfterMs, MAX_DELAY_MS);
|
|
109
|
+
}
|
|
110
|
+
const base = (_b = (_a = this.retry) == null ? void 0 : _a.baseDelayMs) != null ? _b : 0;
|
|
111
|
+
return Math.min(base * 2 ** (attempt - 1) * (0.5 + Math.random() / 2), MAX_DELAY_MS);
|
|
112
|
+
}
|
|
113
|
+
sleep(ms, signal) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
if (signal == null ? void 0 : signal.aborted) return reject(new LumenWipeAbortError(signal.reason));
|
|
116
|
+
const onAbort = () => {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
reject(new LumenWipeAbortError(signal == null ? void 0 : signal.reason));
|
|
119
|
+
};
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
122
|
+
resolve();
|
|
123
|
+
}, ms);
|
|
124
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async once(method, path, body, callerSignal) {
|
|
128
|
+
if (callerSignal == null ? void 0 : callerSignal.aborted) throw new LumenWipeAbortError(callerSignal.reason);
|
|
129
|
+
const headers = {
|
|
130
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
131
|
+
"X-LumenWipe-SDK": `sdk/${SDK_VERSION}`
|
|
132
|
+
};
|
|
28
133
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
29
134
|
const controller = new AbortController();
|
|
135
|
+
let timedOut = false;
|
|
136
|
+
let callerAborted = false;
|
|
137
|
+
const onCallerAbort = () => {
|
|
138
|
+
callerAborted = true;
|
|
139
|
+
controller.abort();
|
|
140
|
+
};
|
|
141
|
+
callerSignal == null ? void 0 : callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
30
142
|
const bounded = Number.isFinite(this.timeout) && this.timeout > 0;
|
|
31
|
-
const timer = bounded ? setTimeout(() =>
|
|
143
|
+
const timer = bounded ? setTimeout(() => {
|
|
144
|
+
timedOut = true;
|
|
145
|
+
controller.abort();
|
|
146
|
+
}, this.timeout) : void 0;
|
|
32
147
|
try {
|
|
33
148
|
const res = await this.doFetch(`${this.baseUrl}${path}`, {
|
|
34
149
|
method,
|
|
@@ -45,20 +160,33 @@ var HttpTransport = class {
|
|
|
45
160
|
parsed = text;
|
|
46
161
|
}
|
|
47
162
|
}
|
|
48
|
-
if (!res.ok) throw new LumenWipeApiError(res.status, parsed);
|
|
163
|
+
if (!res.ok) throw new LumenWipeApiError(res.status, parsed, res.headers);
|
|
49
164
|
return parsed;
|
|
50
165
|
} catch (e) {
|
|
51
|
-
if (
|
|
166
|
+
if (callerAborted) throw new LumenWipeAbortError(callerSignal == null ? void 0 : callerSignal.reason);
|
|
167
|
+
if (timedOut) throw new LumenWipeTimeoutError(this.timeout);
|
|
52
168
|
throw e;
|
|
53
169
|
} finally {
|
|
54
170
|
if (timer) clearTimeout(timer);
|
|
171
|
+
callerSignal == null ? void 0 : callerSignal.removeEventListener("abort", onCallerAbort);
|
|
55
172
|
}
|
|
56
173
|
}
|
|
57
174
|
};
|
|
58
175
|
|
|
59
176
|
// src/client.ts
|
|
177
|
+
function isPlaintextRemote(baseUrl) {
|
|
178
|
+
try {
|
|
179
|
+
const url = new URL(baseUrl);
|
|
180
|
+
if (url.protocol !== "http:") return false;
|
|
181
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
182
|
+
return !(host === "localhost" || host === "::1" || /^127\./.test(host));
|
|
183
|
+
} catch (e) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
60
187
|
var LumenWipeClient = class {
|
|
61
188
|
constructor(options) {
|
|
189
|
+
this.warnedDefaultNetwork = false;
|
|
62
190
|
var _a, _b, _c;
|
|
63
191
|
const resolved = (_a = options.fetch) != null ? _a : globalThis.fetch;
|
|
64
192
|
if (!resolved) {
|
|
@@ -68,77 +196,130 @@ var LumenWipeClient = class {
|
|
|
68
196
|
options.baseUrl.replace(/\/+$/, ""),
|
|
69
197
|
options.apiKey,
|
|
70
198
|
(_b = options.timeout) != null ? _b : 3e4,
|
|
71
|
-
resolved
|
|
199
|
+
resolved,
|
|
200
|
+
options.retry
|
|
72
201
|
);
|
|
73
|
-
this.defaultNetwork =
|
|
202
|
+
this.defaultNetwork = options.network;
|
|
203
|
+
this.log = (_c = options.logger) != null ? _c : ((message) => console.warn(message));
|
|
204
|
+
if (isPlaintextRemote(options.baseUrl)) {
|
|
205
|
+
this.log(
|
|
206
|
+
"LumenWipe: baseUrl uses http:// for a non-local host. The API key is sent in clear text; use https://."
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
resolveNetwork(network) {
|
|
211
|
+
if (network) return network;
|
|
212
|
+
if (this.defaultNetwork) return this.defaultNetwork;
|
|
213
|
+
if (!this.warnedDefaultNetwork) {
|
|
214
|
+
this.warnedDefaultNetwork = true;
|
|
215
|
+
this.log(
|
|
216
|
+
'LumenWipe: no network given, defaulting to "testnet". Pass `network` to the client or the call to silence this.'
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return "testnet";
|
|
74
220
|
}
|
|
75
|
-
health() {
|
|
76
|
-
return this.http.request("GET", "/health")
|
|
221
|
+
health(options) {
|
|
222
|
+
return this.http.request("GET", "/health", void 0, __spreadProps(__spreadValues({}, options), {
|
|
223
|
+
retry: "transient"
|
|
224
|
+
}));
|
|
77
225
|
}
|
|
78
|
-
getAccount(address, network
|
|
226
|
+
getAccount(address, network, options) {
|
|
79
227
|
return this.http.request(
|
|
80
228
|
"GET",
|
|
81
|
-
`/${network}/account/${encodeURIComponent(address)}
|
|
229
|
+
`/${this.resolveNetwork(network)}/account/${encodeURIComponent(address)}`,
|
|
230
|
+
void 0,
|
|
231
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
82
232
|
);
|
|
83
233
|
}
|
|
84
|
-
getPaths(params, network
|
|
234
|
+
getPaths(params, network, options) {
|
|
85
235
|
const query = new URLSearchParams({ fromAsset: params.fromAsset, amount: params.amount });
|
|
86
|
-
return this.http.request(
|
|
236
|
+
return this.http.request(
|
|
237
|
+
"GET",
|
|
238
|
+
`/${this.resolveNetwork(network)}/paths?${query.toString()}`,
|
|
239
|
+
void 0,
|
|
240
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
241
|
+
);
|
|
87
242
|
}
|
|
88
|
-
closePlan(body, network
|
|
89
|
-
return this.http.request(
|
|
243
|
+
closePlan(body, network, options) {
|
|
244
|
+
return this.http.request(
|
|
245
|
+
"POST",
|
|
246
|
+
`/v1/${this.resolveNetwork(network)}/close/plan`,
|
|
247
|
+
body,
|
|
248
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
249
|
+
);
|
|
90
250
|
}
|
|
91
251
|
/**
|
|
92
252
|
* Builds the next unsigned transaction(s) for a close. A close can span several
|
|
93
|
-
* transactions (a
|
|
253
|
+
* transactions (a single transaction, or separate claim / cleanup / mediator-merge steps),
|
|
94
254
|
* so the response's `remaining.requiresAnotherCall` says whether more follow: sign and
|
|
95
255
|
* submit the returned transactions in `order`, wait for confirmation, then call this
|
|
96
256
|
* again until `requiresAnotherCall` is false.
|
|
97
257
|
*/
|
|
98
|
-
closeTransactions(body, network
|
|
258
|
+
closeTransactions(body, network, options) {
|
|
99
259
|
return this.http.request(
|
|
100
260
|
"POST",
|
|
101
|
-
`/v1/${network}/close/transactions`,
|
|
102
|
-
body
|
|
261
|
+
`/v1/${this.resolveNetwork(network)}/close/transactions`,
|
|
262
|
+
body,
|
|
263
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
103
264
|
);
|
|
104
265
|
}
|
|
105
|
-
submit(signedXdr, network
|
|
106
|
-
return this.http.request(
|
|
266
|
+
submit(signedXdr, network, options) {
|
|
267
|
+
return this.http.request(
|
|
268
|
+
"POST",
|
|
269
|
+
`/v1/${this.resolveNetwork(network)}/submit`,
|
|
270
|
+
{ signedXdr },
|
|
271
|
+
__spreadProps(__spreadValues({}, options), { retry: "never" })
|
|
272
|
+
);
|
|
107
273
|
}
|
|
108
|
-
mediatorCheck(address, network
|
|
274
|
+
mediatorCheck(address, network, options) {
|
|
109
275
|
return this.http.request(
|
|
110
276
|
"GET",
|
|
111
|
-
`/${network}/mediator/check/${encodeURIComponent(address)}
|
|
277
|
+
`/${this.resolveNetwork(network)}/mediator/check/${encodeURIComponent(address)}`,
|
|
278
|
+
void 0,
|
|
279
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
112
280
|
);
|
|
113
281
|
}
|
|
114
|
-
mediatorSign(transaction, network
|
|
115
|
-
return this.http.request(
|
|
116
|
-
|
|
117
|
-
|
|
282
|
+
mediatorSign(transaction, network, options) {
|
|
283
|
+
return this.http.request(
|
|
284
|
+
"POST",
|
|
285
|
+
`/${this.resolveNetwork(network)}/mediator/sign`,
|
|
286
|
+
{
|
|
287
|
+
transaction
|
|
288
|
+
},
|
|
289
|
+
__spreadProps(__spreadValues({}, options), { retry: "never" })
|
|
290
|
+
);
|
|
118
291
|
}
|
|
119
292
|
/** Wraps a wind-down transaction (its own fee already zero) in a signed CAP-15 fee-bump
|
|
120
293
|
* envelope for an account that cannot pay its own fee (architecture.md §8.1). The caller
|
|
121
294
|
* submits the returned XDR through `submit()`, exactly like any other close transaction. */
|
|
122
|
-
feeBumpSponsor(transaction, network
|
|
123
|
-
return this.http.request(
|
|
124
|
-
|
|
125
|
-
|
|
295
|
+
feeBumpSponsor(transaction, network, options) {
|
|
296
|
+
return this.http.request(
|
|
297
|
+
"POST",
|
|
298
|
+
`/${this.resolveNetwork(network)}/fee-bump/sponsor`,
|
|
299
|
+
{
|
|
300
|
+
transaction
|
|
301
|
+
},
|
|
302
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
303
|
+
);
|
|
126
304
|
}
|
|
127
305
|
/** Every live SEP-41 allowance the account has granted (architecture.md §12). Independent of
|
|
128
306
|
* closing an account - a standalone security utility. */
|
|
129
|
-
getAllowances(address, network
|
|
307
|
+
getAllowances(address, network, options) {
|
|
130
308
|
return this.http.request(
|
|
131
309
|
"GET",
|
|
132
|
-
`/${network}/allowances/${encodeURIComponent(address)}
|
|
310
|
+
`/${this.resolveNetwork(network)}/allowances/${encodeURIComponent(address)}`,
|
|
311
|
+
void 0,
|
|
312
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
133
313
|
);
|
|
134
314
|
}
|
|
135
315
|
/** Builds the unsigned `approve(owner, spender, 0, 0)` transaction that revokes one allowance.
|
|
136
316
|
* The caller signs and submits it through `submit()`, like any other transaction here. */
|
|
137
|
-
revokeAllowance(params, network
|
|
317
|
+
revokeAllowance(params, network, options) {
|
|
138
318
|
return this.http.request(
|
|
139
319
|
"POST",
|
|
140
|
-
`/${network}/allowances/revoke`,
|
|
141
|
-
params
|
|
320
|
+
`/${this.resolveNetwork(network)}/allowances/revoke`,
|
|
321
|
+
params,
|
|
322
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
142
323
|
);
|
|
143
324
|
}
|
|
144
325
|
};
|
|
@@ -196,8 +377,10 @@ async function signOrThrow(deps, tx, xdr, queue, requiresAnotherCall) {
|
|
|
196
377
|
}
|
|
197
378
|
export {
|
|
198
379
|
InsufficientSignatureWeightError,
|
|
380
|
+
LumenWipeAbortError,
|
|
199
381
|
LumenWipeApiError,
|
|
200
382
|
LumenWipeClient,
|
|
201
383
|
LumenWipeTimeoutError,
|
|
384
|
+
isApiErrorCode,
|
|
202
385
|
runClose
|
|
203
386
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenwipe/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Typed fetch client for the LumenWipe API - non-custodial Stellar account closing.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -44,12 +44,14 @@
|
|
|
44
44
|
"build": "bun run build:types && api-extractor run --local && cp dist/index.d.ts dist/index.d.cts",
|
|
45
45
|
"build:types": "bun run --filter '@lumenwipe/types' build && tsup src/index.ts --format esm,cjs --clean && tsc -p tsconfig.build.json",
|
|
46
46
|
"check-api": "api-extractor run",
|
|
47
|
-
"type-check": "tsc --noEmit && tsc -p tests/tsconfig.json --noEmit",
|
|
48
|
-
"lint": "eslint src",
|
|
49
|
-
"test": "bun test tests"
|
|
47
|
+
"type-check": "tsc --noEmit && tsc -p tests/tsconfig.json --noEmit && tsc -p examples/tsconfig.json --noEmit",
|
|
48
|
+
"lint": "eslint src examples",
|
|
49
|
+
"test": "bun test tests",
|
|
50
|
+
"example:headless": "bun run examples/headless-close.ts"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@microsoft/api-extractor": "^7.59.0",
|
|
54
|
+
"@stellar/stellar-sdk": "16.0.1",
|
|
53
55
|
"@types/bun": "^1.4.0",
|
|
54
56
|
"@typescript-eslint/eslint-plugin": "^8.69.0",
|
|
55
57
|
"@typescript-eslint/parser": "^8.69.0",
|