@lumenwipe/sdk 0.1.0 → 0.5.1
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 +237 -30
- package/dist/index.d.cts +565 -33
- package/dist/index.d.ts +565 -33
- package/dist/index.js +238 -30
- package/package.json +14 -10
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.5.1";
|
|
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,53 +196,131 @@ 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
|
+
);
|
|
291
|
+
}
|
|
292
|
+
/** Wraps a wind-down transaction (its own fee already zero) in a signed CAP-15 fee-bump
|
|
293
|
+
* envelope for an account that cannot pay its own fee (architecture.md §8.1). The caller
|
|
294
|
+
* submits the returned XDR through `submit()`, exactly like any other close transaction. */
|
|
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
|
+
);
|
|
304
|
+
}
|
|
305
|
+
/** Every live SEP-41 allowance the account has granted (architecture.md §12). Independent of
|
|
306
|
+
* closing an account - a standalone security utility. */
|
|
307
|
+
getAllowances(address, network, options) {
|
|
308
|
+
return this.http.request(
|
|
309
|
+
"GET",
|
|
310
|
+
`/${this.resolveNetwork(network)}/allowances/${encodeURIComponent(address)}`,
|
|
311
|
+
void 0,
|
|
312
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
/** Builds the unsigned `approve(owner, spender, 0, 0)` transaction that revokes one allowance.
|
|
316
|
+
* The caller signs and submits it through `submit()`, like any other transaction here. */
|
|
317
|
+
revokeAllowance(params, network, options) {
|
|
318
|
+
return this.http.request(
|
|
319
|
+
"POST",
|
|
320
|
+
`/${this.resolveNetwork(network)}/allowances/revoke`,
|
|
321
|
+
params,
|
|
322
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
323
|
+
);
|
|
118
324
|
}
|
|
119
325
|
};
|
|
120
326
|
|
|
@@ -171,8 +377,10 @@ async function signOrThrow(deps, tx, xdr, queue, requiresAnotherCall) {
|
|
|
171
377
|
}
|
|
172
378
|
export {
|
|
173
379
|
InsufficientSignatureWeightError,
|
|
380
|
+
LumenWipeAbortError,
|
|
174
381
|
LumenWipeApiError,
|
|
175
382
|
LumenWipeClient,
|
|
176
383
|
LumenWipeTimeoutError,
|
|
384
|
+
isApiErrorCode,
|
|
177
385
|
runClose
|
|
178
386
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenwipe/sdk",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -41,18 +41,22 @@
|
|
|
41
41
|
"account-merge"
|
|
42
42
|
],
|
|
43
43
|
"scripts": {
|
|
44
|
-
"build": "bun run
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
44
|
+
"build": "bun run build:types && api-extractor run --local && cp dist/index.d.ts dist/index.d.cts",
|
|
45
|
+
"build:types": "bun run --filter '@lumenwipe/types' build && tsup src/index.ts --format esm,cjs --clean && tsc -p tsconfig.build.json",
|
|
46
|
+
"check-api": "api-extractor run",
|
|
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"
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|
|
50
|
-
"@microsoft/api-extractor": "^7.
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
-
"@typescript-eslint/
|
|
53
|
+
"@microsoft/api-extractor": "^7.59.0",
|
|
54
|
+
"@stellar/stellar-sdk": "16.0.1",
|
|
55
|
+
"@types/bun": "^1.4.0",
|
|
56
|
+
"@typescript-eslint/eslint-plugin": "^8.69.0",
|
|
57
|
+
"@typescript-eslint/parser": "^8.69.0",
|
|
54
58
|
"eslint": "^8.57.1",
|
|
55
|
-
"tsup": "^8.
|
|
59
|
+
"tsup": "^8.5.1",
|
|
56
60
|
"typescript": "^5.9.3",
|
|
57
61
|
"@lumenwipe/types": "workspace:*"
|
|
58
62
|
}
|