@lumenwipe/sdk 0.8.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/CHANGELOG.md +382 -0
- package/LICENSE +201 -0
- package/README.md +99 -12
- package/dist/index.cjs +485 -58
- package/dist/index.d.cts +606 -22
- package/dist/index.d.ts +606 -22
- package/dist/index.js +470 -57
- package/package.json +34 -16
package/dist/index.js
CHANGED
|
@@ -60,6 +60,12 @@ var LumenWipeTimeoutError = class extends Error {
|
|
|
60
60
|
this.name = "LumenWipeTimeoutError";
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
+
var LumenWipeInvalidNetworkError = class extends Error {
|
|
64
|
+
constructor() {
|
|
65
|
+
super('LumenWipe network must be "mainnet" or "testnet"');
|
|
66
|
+
this.name = "LumenWipeInvalidNetworkError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
63
69
|
var LumenWipeAbortError = class extends Error {
|
|
64
70
|
constructor(reason) {
|
|
65
71
|
super("LumenWipe API request was aborted");
|
|
@@ -69,7 +75,7 @@ var LumenWipeAbortError = class extends Error {
|
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
// src/version.ts
|
|
72
|
-
var SDK_VERSION = "0.
|
|
78
|
+
var SDK_VERSION = "0.9.0";
|
|
73
79
|
|
|
74
80
|
// src/http.ts
|
|
75
81
|
var MAX_DELAY_MS = 3e4;
|
|
@@ -84,11 +90,11 @@ var HttpTransport = class {
|
|
|
84
90
|
this.retry = retry;
|
|
85
91
|
}
|
|
86
92
|
async request(method, path, body, opts) {
|
|
87
|
-
var _a, _b;
|
|
93
|
+
var _a, _b, _c;
|
|
88
94
|
const attempts = opts.retry === "never" ? 1 : Math.max(1, Math.floor((_b = (_a = this.retry) == null ? void 0 : _a.attempts) != null ? _b : 1));
|
|
89
95
|
for (let attempt = 1; ; attempt++) {
|
|
90
96
|
try {
|
|
91
|
-
return await this.once(method, path, body, opts.signal);
|
|
97
|
+
return await this.once(method, path, body, (_c = opts.bearer) != null ? _c : this.apiKey, opts.signal);
|
|
92
98
|
} catch (e) {
|
|
93
99
|
if (attempt >= attempts || !this.shouldRetry(opts.retry, e)) throw e;
|
|
94
100
|
await this.sleep(this.delayMs(e, attempt), opts.signal);
|
|
@@ -124,12 +130,11 @@ var HttpTransport = class {
|
|
|
124
130
|
signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
125
131
|
});
|
|
126
132
|
}
|
|
127
|
-
async once(method, path, body, callerSignal) {
|
|
133
|
+
async once(method, path, body, bearer, callerSignal) {
|
|
128
134
|
if (callerSignal == null ? void 0 : callerSignal.aborted) throw new LumenWipeAbortError(callerSignal.reason);
|
|
129
|
-
const headers = {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
135
|
+
const headers = {};
|
|
136
|
+
if (bearer !== void 0) headers.Authorization = `Bearer ${bearer}`;
|
|
137
|
+
headers["X-LumenWipe-SDK"] = `sdk/${SDK_VERSION}`;
|
|
133
138
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
134
139
|
const controller = new AbortController();
|
|
135
140
|
let timedOut = false;
|
|
@@ -173,7 +178,16 @@ var HttpTransport = class {
|
|
|
173
178
|
}
|
|
174
179
|
};
|
|
175
180
|
|
|
181
|
+
// src/url.ts
|
|
182
|
+
var SLASH = 47;
|
|
183
|
+
function withoutTrailingSlashes(url) {
|
|
184
|
+
let end = url.length;
|
|
185
|
+
while (end > 0 && url.charCodeAt(end - 1) === SLASH) end--;
|
|
186
|
+
return url.slice(0, end);
|
|
187
|
+
}
|
|
188
|
+
|
|
176
189
|
// src/client.ts
|
|
190
|
+
var NETWORKS = ["mainnet", "testnet"];
|
|
177
191
|
function isPlaintextRemote(baseUrl) {
|
|
178
192
|
try {
|
|
179
193
|
const url = new URL(baseUrl);
|
|
@@ -193,7 +207,7 @@ var LumenWipeClient = class {
|
|
|
193
207
|
throw new Error("No fetch implementation available; pass one via options.fetch.");
|
|
194
208
|
}
|
|
195
209
|
this.http = new HttpTransport(
|
|
196
|
-
options.baseUrl
|
|
210
|
+
withoutTrailingSlashes(options.baseUrl),
|
|
197
211
|
options.apiKey,
|
|
198
212
|
(_b = options.timeout) != null ? _b : 3e4,
|
|
199
213
|
resolved,
|
|
@@ -208,44 +222,78 @@ var LumenWipeClient = class {
|
|
|
208
222
|
}
|
|
209
223
|
}
|
|
210
224
|
resolveNetwork(network) {
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
const resolved = network != null ? network : this.defaultNetwork;
|
|
226
|
+
if (resolved === void 0) {
|
|
227
|
+
if (!this.warnedDefaultNetwork) {
|
|
228
|
+
this.warnedDefaultNetwork = true;
|
|
229
|
+
this.log(
|
|
230
|
+
'LumenWipe: no network given, defaulting to "testnet". Pass `network` to the client or the call to silence this.'
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return "testnet";
|
|
218
234
|
}
|
|
219
|
-
|
|
235
|
+
if (!NETWORKS.includes(resolved)) throw new LumenWipeInvalidNetworkError();
|
|
236
|
+
return resolved;
|
|
237
|
+
}
|
|
238
|
+
/** The `/v1/{network}` path for `rest`. The network is checked before any request is built. */
|
|
239
|
+
v1(network, rest) {
|
|
240
|
+
return `/v1/${encodeURIComponent(this.resolveNetwork(network))}${rest}`;
|
|
220
241
|
}
|
|
221
242
|
health(options) {
|
|
222
243
|
return this.http.request("GET", "/health", void 0, __spreadProps(__spreadValues({}, options), {
|
|
223
244
|
retry: "transient"
|
|
224
245
|
}));
|
|
225
246
|
}
|
|
226
|
-
|
|
247
|
+
/** Whether Stellar RPC is reachable on every network, and the days left on the exchange registry.
|
|
248
|
+
* A degraded service answers 503, thrown as a `LumenWipeApiError` whose `body` is a
|
|
249
|
+
* `HealthDeepResponse` with `status: "error"`. Never retried: the 503 is the answer. */
|
|
250
|
+
healthDeep(options) {
|
|
251
|
+
return this.http.request("GET", "/health/deep", void 0, __spreadProps(__spreadValues({}, options), {
|
|
252
|
+
retry: "never"
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
/** The exchange registry the API closes against. Do not rely on it past `validUntil`. */
|
|
256
|
+
getExchangeRegistry(options) {
|
|
257
|
+
return this.http.request(
|
|
258
|
+
"GET",
|
|
259
|
+
"/v1/config/exchange-registry",
|
|
260
|
+
void 0,
|
|
261
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
async getAccount(address, network, options) {
|
|
227
265
|
return this.http.request(
|
|
228
266
|
"GET",
|
|
229
|
-
|
|
267
|
+
this.v1(network, `/account/${encodeURIComponent(address)}`),
|
|
230
268
|
void 0,
|
|
231
269
|
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
232
270
|
);
|
|
233
271
|
}
|
|
234
|
-
getPaths(params, network, options) {
|
|
272
|
+
async getPaths(params, network, options) {
|
|
235
273
|
const query = new URLSearchParams({ fromAsset: params.fromAsset, amount: params.amount });
|
|
236
274
|
return this.http.request(
|
|
237
275
|
"GET",
|
|
238
|
-
|
|
276
|
+
this.v1(network, `/paths?${query.toString()}`),
|
|
239
277
|
void 0,
|
|
240
278
|
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
241
279
|
);
|
|
242
280
|
}
|
|
243
|
-
closePlan(body, network, options) {
|
|
281
|
+
async closePlan(body, network, options) {
|
|
282
|
+
return this.http.request("POST", this.v1(network, "/close/plan"), body, __spreadProps(__spreadValues({}, options), {
|
|
283
|
+
retry: "throttle"
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
/** Plans up to 20 accounts into one shared destination in one call. Read-only: no decisions are
|
|
287
|
+
* applied, so plan each account with `closePlan` before closing it. An address that cannot be
|
|
288
|
+
* read or planned comes back as a `blocked` plan, never as a missing entry. */
|
|
289
|
+
async batchPlan(body, network, options) {
|
|
244
290
|
return this.http.request(
|
|
245
291
|
"POST",
|
|
246
|
-
|
|
292
|
+
this.v1(network, "/close/batch-plan"),
|
|
247
293
|
body,
|
|
248
|
-
__spreadProps(__spreadValues({}, options), {
|
|
294
|
+
__spreadProps(__spreadValues({}, options), {
|
|
295
|
+
retry: "throttle"
|
|
296
|
+
})
|
|
249
297
|
);
|
|
250
298
|
}
|
|
251
299
|
/**
|
|
@@ -255,34 +303,68 @@ var LumenWipeClient = class {
|
|
|
255
303
|
* submit the returned transactions in `order`, wait for confirmation, then call this
|
|
256
304
|
* again until `requiresAnotherCall` is false.
|
|
257
305
|
*/
|
|
258
|
-
closeTransactions(body, network, options) {
|
|
306
|
+
async closeTransactions(body, network, options) {
|
|
259
307
|
return this.http.request(
|
|
260
308
|
"POST",
|
|
261
|
-
|
|
309
|
+
this.v1(network, "/close/transactions"),
|
|
262
310
|
body,
|
|
263
311
|
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
264
312
|
);
|
|
265
313
|
}
|
|
266
|
-
submit(signedXdr, network, options) {
|
|
314
|
+
async submit(signedXdr, network, options) {
|
|
267
315
|
return this.http.request(
|
|
268
316
|
"POST",
|
|
269
|
-
|
|
317
|
+
this.v1(network, "/submit"),
|
|
270
318
|
{ signedXdr },
|
|
271
319
|
__spreadProps(__spreadValues({}, options), { retry: "never" })
|
|
272
320
|
);
|
|
273
321
|
}
|
|
274
|
-
|
|
322
|
+
/** Looks a transaction up by hash and sends nothing. `not_found` is not "did not land": the
|
|
323
|
+
* transaction may be queued, or older than the window the response names. */
|
|
324
|
+
async getTransaction(hash, network, options) {
|
|
325
|
+
return this.http.request(
|
|
326
|
+
"GET",
|
|
327
|
+
this.v1(network, `/transactions/${encodeURIComponent(hash)}`),
|
|
328
|
+
void 0,
|
|
329
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
/** A close's phase and remaining work, from live state. Informational: a close is finished only
|
|
333
|
+
* when the hash of the merge you signed is confirmed by `getTransaction`. */
|
|
334
|
+
async closeStatus(source, network, options) {
|
|
335
|
+
const query = new URLSearchParams({ source });
|
|
275
336
|
return this.http.request(
|
|
276
337
|
"GET",
|
|
277
|
-
|
|
338
|
+
this.v1(network, `/close/status?${query.toString()}`),
|
|
278
339
|
void 0,
|
|
279
340
|
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
280
341
|
);
|
|
281
342
|
}
|
|
282
|
-
|
|
343
|
+
/** The XLM confirmed close transactions delivered to `destination`, read from each one's ledger
|
|
344
|
+
* result: a merge into it, or the relay account's forward of a merged balance. A hash that
|
|
345
|
+
* cannot be read as delivered is an `unavailable` entry, not an error, and `totalStroops` is
|
|
346
|
+
* set only when every entry is `delivered`. Pass 1 to 20 distinct hashes. */
|
|
347
|
+
async closeDelivered(destination, hashes, network, options) {
|
|
348
|
+
const query = new URLSearchParams({ destination, hashes: hashes.join(",") });
|
|
349
|
+
return this.http.request(
|
|
350
|
+
"GET",
|
|
351
|
+
this.v1(network, `/close/delivered?${query.toString()}`),
|
|
352
|
+
void 0,
|
|
353
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
async mediatorCheck(address, network, options) {
|
|
357
|
+
return this.http.request(
|
|
358
|
+
"GET",
|
|
359
|
+
this.v1(network, `/mediator/check/${encodeURIComponent(address)}`),
|
|
360
|
+
void 0,
|
|
361
|
+
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
async mediatorSign(transaction, network, options) {
|
|
283
365
|
return this.http.request(
|
|
284
366
|
"POST",
|
|
285
|
-
|
|
367
|
+
this.v1(network, "/mediator/sign"),
|
|
286
368
|
{
|
|
287
369
|
transaction
|
|
288
370
|
},
|
|
@@ -292,10 +374,10 @@ var LumenWipeClient = class {
|
|
|
292
374
|
/** Wraps a wind-down transaction (its own fee already zero) in a signed CAP-15 fee-bump
|
|
293
375
|
* envelope for an account that cannot pay its own fee (architecture.md §8.1). The caller
|
|
294
376
|
* submits the returned XDR through `submit()`, exactly like any other close transaction. */
|
|
295
|
-
feeBumpSponsor(transaction, network, options) {
|
|
377
|
+
async feeBumpSponsor(transaction, network, options) {
|
|
296
378
|
return this.http.request(
|
|
297
379
|
"POST",
|
|
298
|
-
|
|
380
|
+
this.v1(network, "/fee-bump/sponsor"),
|
|
299
381
|
{
|
|
300
382
|
transaction
|
|
301
383
|
},
|
|
@@ -304,24 +386,125 @@ var LumenWipeClient = class {
|
|
|
304
386
|
}
|
|
305
387
|
/** Every live SEP-41 allowance the account has granted (architecture.md §12). Independent of
|
|
306
388
|
* closing an account - a standalone security utility. */
|
|
307
|
-
getAllowances(address, network, options) {
|
|
389
|
+
async getAllowances(address, network, options) {
|
|
308
390
|
return this.http.request(
|
|
309
391
|
"GET",
|
|
310
|
-
|
|
392
|
+
this.v1(network, `/allowances/${encodeURIComponent(address)}`),
|
|
311
393
|
void 0,
|
|
312
394
|
__spreadProps(__spreadValues({}, options), { retry: "transient" })
|
|
313
395
|
);
|
|
314
396
|
}
|
|
315
397
|
/** Builds the unsigned `approve(owner, spender, 0, 0)` transaction that revokes one allowance.
|
|
316
398
|
* The caller signs and submits it through `submit()`, like any other transaction here. */
|
|
317
|
-
revokeAllowance(params, network, options) {
|
|
399
|
+
async revokeAllowance(params, network, options) {
|
|
318
400
|
return this.http.request(
|
|
319
401
|
"POST",
|
|
320
|
-
|
|
402
|
+
this.v1(network, "/allowances/revoke"),
|
|
321
403
|
params,
|
|
322
404
|
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
323
405
|
);
|
|
324
406
|
}
|
|
407
|
+
/** Accounts closed and XLM recovered through LumenWipe on the network. */
|
|
408
|
+
async getStats(network, options) {
|
|
409
|
+
return this.http.request("GET", this.v1(network, "/stats"), void 0, __spreadProps(__spreadValues({}, options), {
|
|
410
|
+
retry: "transient"
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
/** The totals, the most recent closes and the daily activity on the network. */
|
|
414
|
+
async getStatsFeed(network, options) {
|
|
415
|
+
return this.http.request("GET", this.v1(network, "/stats/feed"), void 0, __spreadProps(__spreadValues({}, options), {
|
|
416
|
+
retry: "transient"
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
/** Counts a confirmed close that was submitted outside this API. The API re-reads the
|
|
420
|
+
* transaction and counts it only if it built it, it confirmed and it merged an account;
|
|
421
|
+
* `counted` is false for a hash already counted. Closes sent through `submit()` are counted
|
|
422
|
+
* without this. */
|
|
423
|
+
async recordMerge(txHash, network, options) {
|
|
424
|
+
return this.http.request(
|
|
425
|
+
"POST",
|
|
426
|
+
this.v1(network, "/stats/merges"),
|
|
427
|
+
{ txHash },
|
|
428
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// src/integrator-client.ts
|
|
434
|
+
var LumenWipeIntegratorClient = class {
|
|
435
|
+
constructor(options) {
|
|
436
|
+
var _a, _b, _c;
|
|
437
|
+
const resolved = (_a = options.fetch) != null ? _a : globalThis.fetch;
|
|
438
|
+
if (!resolved) {
|
|
439
|
+
throw new Error("No fetch implementation available; pass one via options.fetch.");
|
|
440
|
+
}
|
|
441
|
+
this.http = new HttpTransport(
|
|
442
|
+
withoutTrailingSlashes(options.baseUrl),
|
|
443
|
+
void 0,
|
|
444
|
+
(_b = options.timeout) != null ? _b : 3e4,
|
|
445
|
+
resolved,
|
|
446
|
+
options.retry
|
|
447
|
+
);
|
|
448
|
+
if (isPlaintextRemote(options.baseUrl)) {
|
|
449
|
+
((_c = options.logger) != null ? _c : ((message) => console.warn(message)))(
|
|
450
|
+
"LumenWipe: baseUrl uses http:// for a non-local host. Session tokens and new keys travel in clear text; use https://."
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/** A SEP-53 message for `address` (G...) to sign, valid until `expiresAt`. */
|
|
455
|
+
challenge(address, options) {
|
|
456
|
+
return this.http.request(
|
|
457
|
+
"POST",
|
|
458
|
+
"/v1/integrator/auth/challenge",
|
|
459
|
+
{ address },
|
|
460
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
/** Exchanges the signed challenge for a session token. */
|
|
464
|
+
session(request, options) {
|
|
465
|
+
return this.http.request(
|
|
466
|
+
"POST",
|
|
467
|
+
"/v1/integrator/auth/session",
|
|
468
|
+
{ address: request.address, message: request.message, signature: request.signature },
|
|
469
|
+
__spreadProps(__spreadValues({}, options), { retry: "throttle" })
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
/** The wallet's keys, each with its own request counts, and the wallet's total counts. */
|
|
473
|
+
listKeys(token, options) {
|
|
474
|
+
return this.http.request("GET", "/v1/integrator/keys", void 0, __spreadProps(__spreadValues({}, options), {
|
|
475
|
+
retry: "transient",
|
|
476
|
+
bearer: token
|
|
477
|
+
}));
|
|
478
|
+
}
|
|
479
|
+
/** Creates a key, optionally named and limited to some networks. The raw `key` is in this
|
|
480
|
+
* response only and can never be read again. */
|
|
481
|
+
createKey(token, request = {}, options) {
|
|
482
|
+
return this.http.request(
|
|
483
|
+
"POST",
|
|
484
|
+
"/v1/integrator/keys",
|
|
485
|
+
{ label: request.label, networks: request.networks },
|
|
486
|
+
__spreadProps(__spreadValues({}, options), { retry: "never", bearer: token })
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
/** Revokes one of the wallet's keys by its `id`. */
|
|
490
|
+
revokeKey(token, id, options) {
|
|
491
|
+
return this.http.request(
|
|
492
|
+
"POST",
|
|
493
|
+
`/v1/integrator/keys/${encodeURIComponent(id)}/revoke`,
|
|
494
|
+
void 0,
|
|
495
|
+
__spreadProps(__spreadValues({}, options), { retry: "never", bearer: token })
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
/** Revokes a key and issues its replacement, keeping its label and networks. The new raw `key`
|
|
499
|
+
* is in this response only. */
|
|
500
|
+
rotateKey(token, id, options) {
|
|
501
|
+
return this.http.request(
|
|
502
|
+
"POST",
|
|
503
|
+
`/v1/integrator/keys/${encodeURIComponent(id)}/rotate`,
|
|
504
|
+
void 0,
|
|
505
|
+
__spreadProps(__spreadValues({}, options), { retry: "never", bearer: token })
|
|
506
|
+
);
|
|
507
|
+
}
|
|
325
508
|
};
|
|
326
509
|
|
|
327
510
|
// src/close-engine.ts
|
|
@@ -334,33 +517,200 @@ var InsufficientSignatureWeightError = class extends Error {
|
|
|
334
517
|
this.name = "InsufficientSignatureWeightError";
|
|
335
518
|
}
|
|
336
519
|
};
|
|
520
|
+
var STATE_CHANGED_REFETCHES = 2;
|
|
521
|
+
var STATE_CHANGED_REFETCH_DELAY_MS = 3e3;
|
|
522
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
523
|
+
var CloseStateChangedError = class extends Error {
|
|
524
|
+
constructor(apiError) {
|
|
525
|
+
super(
|
|
526
|
+
"Your account changed while the close was being prepared, so nothing was submitted. Plan the close again and review it before continuing."
|
|
527
|
+
);
|
|
528
|
+
this.apiError = apiError;
|
|
529
|
+
this.name = "CloseStateChangedError";
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
function hasCode(error, code) {
|
|
533
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
534
|
+
}
|
|
535
|
+
var SUBMIT_RESEND_ATTEMPTS = 2;
|
|
536
|
+
var SUBMIT_RESEND_DELAY_MS = 5e3;
|
|
537
|
+
var SUBMISSION_GIVE_UP_MARGIN_SECONDS = 120;
|
|
538
|
+
var CloseOutcomeUnknownError = class extends Error {
|
|
539
|
+
constructor(submission) {
|
|
540
|
+
super(
|
|
541
|
+
"This transaction is not confirmed yet. Your account may already be closed, so check again before doing anything else."
|
|
542
|
+
);
|
|
543
|
+
this.submission = submission;
|
|
544
|
+
this.name = "CloseOutcomeUnknownError";
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
var CloseTransactionFailedError = class extends Error {
|
|
548
|
+
constructor(submission, message) {
|
|
549
|
+
super(message);
|
|
550
|
+
this.submission = submission;
|
|
551
|
+
this.name = "CloseTransactionFailedError";
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
function isSubmitRefused(error) {
|
|
555
|
+
const status = error == null ? void 0 : error.status;
|
|
556
|
+
return typeof status === "number" && status >= 400 && status < 500;
|
|
557
|
+
}
|
|
558
|
+
async function resolveSubmission(submission, lookup, now = Date.now) {
|
|
559
|
+
let found;
|
|
560
|
+
try {
|
|
561
|
+
found = await lookup(submission.hash);
|
|
562
|
+
} catch (e) {
|
|
563
|
+
return { outcome: "unknown" };
|
|
564
|
+
}
|
|
565
|
+
if (found.hash !== submission.hash) return { outcome: "unknown" };
|
|
566
|
+
if (found.status === "confirmed") return { outcome: "confirmed" };
|
|
567
|
+
if (found.status === "failed") return { outcome: "failed", message: found.message };
|
|
568
|
+
if (submission.validUntil === null) return { outcome: "unknown" };
|
|
569
|
+
const elapsed = Math.min(now() / 1e3, found.searched.latestLedgerCloseTime);
|
|
570
|
+
return elapsed > submission.validUntil + SUBMISSION_GIVE_UP_MARGIN_SECONDS ? { outcome: "expired" } : { outcome: "unknown" };
|
|
571
|
+
}
|
|
572
|
+
var submissionClosesAccount = (submission) => submission.covers.some((step) => step === "MERGE" || step === "CLOSE_ACCOUNT");
|
|
573
|
+
var ROUND_MARGIN = 5;
|
|
574
|
+
var HARD_MAX_ROUNDS = 200;
|
|
575
|
+
var CloseDidNotConvergeError = class extends Error {
|
|
576
|
+
constructor(rounds, bound, reason) {
|
|
577
|
+
super(
|
|
578
|
+
reason === "empty_batch" ? "The close stopped because the service reported more steps but had nothing to sign. Check your account, then try again - the close picks up where it stopped." : `The close needed more steps than expected (${rounds} of at most ${bound}). Check your account, then try again - the close picks up where it stopped.`
|
|
579
|
+
);
|
|
580
|
+
this.rounds = rounds;
|
|
581
|
+
this.bound = bound;
|
|
582
|
+
this.reason = reason;
|
|
583
|
+
this.name = "CloseDidNotConvergeError";
|
|
584
|
+
}
|
|
585
|
+
};
|
|
337
586
|
async function runClose(deps, resume) {
|
|
338
|
-
var _a, _b;
|
|
339
|
-
|
|
587
|
+
var _a, _b, _c, _d, _e, _f;
|
|
588
|
+
let bound = 0;
|
|
589
|
+
let planHash = resume ? void 0 : deps.planHash;
|
|
590
|
+
const run = __spreadProps(__spreadValues({}, deps), {
|
|
591
|
+
onConfirmed: (tx, hash) => {
|
|
592
|
+
var _a2;
|
|
593
|
+
planHash = void 0;
|
|
594
|
+
(_a2 = deps.onConfirmed) == null ? void 0 : _a2.call(deps, tx, hash);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
if ((_a = deps.reconcile) == null ? void 0 : _a.unresolved) {
|
|
598
|
+
const settled = await settleLeftOver(deps.reconcile, deps.reconcile.unresolved);
|
|
599
|
+
if (settled === "closed") return { mergeConfirmed: true };
|
|
600
|
+
if (settled === "confirmed") planHash = void 0;
|
|
601
|
+
}
|
|
602
|
+
let firstRound = 0;
|
|
340
603
|
if (resume) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
604
|
+
const at = resumedPosition(resume);
|
|
605
|
+
firstRound = at.round + 1;
|
|
606
|
+
await signOrThrow(run, resume.tx, resume.xdr, resume.queue, at);
|
|
607
|
+
await processTxs(run, resume.queue, __spreadProps(__spreadValues({}, at), { txIndex: at.txIndex + 1 }));
|
|
608
|
+
if (!resume.requiresAnotherCall) return resultOf(run);
|
|
344
609
|
}
|
|
345
|
-
for (let round = 0; round < maxRounds; round++) {
|
|
346
|
-
(
|
|
347
|
-
|
|
610
|
+
for (let round = 0; round < ((_b = run.maxRounds) != null ? _b : Math.max(bound, 1)); round++) {
|
|
611
|
+
(_c = run.onProgress) == null ? void 0 : _c.call(run, "Preparing transactions\u2026");
|
|
612
|
+
report(run, () => ({ phase: "preparing", round: firstRound + round }));
|
|
613
|
+
const batch = await fetchRound(run, planHash).catch(async (error) => {
|
|
614
|
+
if (await mergeExplainsMissingAccount(run, error)) return null;
|
|
615
|
+
throw error;
|
|
616
|
+
});
|
|
617
|
+
if (!batch) return resultOf(run);
|
|
348
618
|
const txs = [...batch.transactions].sort((a, b) => a.order - b.order);
|
|
349
|
-
|
|
350
|
-
if (
|
|
619
|
+
const { requiresAnotherCall, steps } = batch.remaining;
|
|
620
|
+
if (requiresAnotherCall && txs.length === 0) {
|
|
621
|
+
throw new CloseDidNotConvergeError(round + 1, (_d = run.maxRounds) != null ? _d : bound, "empty_batch");
|
|
622
|
+
}
|
|
623
|
+
await processTxs(run, txs, {
|
|
624
|
+
round: firstRound + round,
|
|
625
|
+
txIndex: 0,
|
|
626
|
+
txCount: txs.length,
|
|
627
|
+
moreRoundsExpected: requiresAnotherCall
|
|
628
|
+
});
|
|
629
|
+
if (!requiresAnotherCall) return resultOf(run);
|
|
630
|
+
bound = Math.max(bound, Math.min(HARD_MAX_ROUNDS, round + 1 + steps + ROUND_MARGIN));
|
|
351
631
|
}
|
|
352
|
-
throw new
|
|
632
|
+
throw new CloseDidNotConvergeError((_e = run.maxRounds) != null ? _e : bound, (_f = run.maxRounds) != null ? _f : bound, "round_limit");
|
|
633
|
+
}
|
|
634
|
+
function resultOf(run) {
|
|
635
|
+
return { mergeConfirmed: run.confirmedMerge !== void 0 };
|
|
353
636
|
}
|
|
354
|
-
async function
|
|
637
|
+
async function settleLeftOver(reconcile, submission) {
|
|
638
|
+
var _a, _b;
|
|
639
|
+
const settled = await resolveSubmission(submission, reconcile.lookup, reconcile.now);
|
|
640
|
+
if (settled.outcome === "unknown") throw new CloseOutcomeUnknownError(submission);
|
|
641
|
+
await ((_a = reconcile.onSubmission) == null ? void 0 : _a.call(reconcile, null));
|
|
642
|
+
if (settled.outcome === "failed") {
|
|
643
|
+
throw new CloseTransactionFailedError(submission, settled.message);
|
|
644
|
+
}
|
|
645
|
+
if (settled.outcome === "expired") return "expired";
|
|
646
|
+
(_b = reconcile.onResolved) == null ? void 0 : _b.call(reconcile, submission);
|
|
647
|
+
return submissionClosesAccount(submission) ? "closed" : "confirmed";
|
|
648
|
+
}
|
|
649
|
+
async function mergeExplainsMissingAccount(run, error) {
|
|
650
|
+
if (!hasCode(error, "account_not_found") || !run.reconcile || !run.confirmedMerge) return false;
|
|
651
|
+
const settled = await resolveSubmission(
|
|
652
|
+
run.confirmedMerge,
|
|
653
|
+
run.reconcile.lookup,
|
|
654
|
+
run.reconcile.now
|
|
655
|
+
);
|
|
656
|
+
return settled.outcome === "confirmed";
|
|
657
|
+
}
|
|
658
|
+
async function fetchRound(deps, planHash) {
|
|
659
|
+
var _a;
|
|
660
|
+
for (let attempt = 0; ; attempt++) {
|
|
661
|
+
if (attempt > 0) await ((_a = deps.sleep) != null ? _a : defaultSleep)(STATE_CHANGED_REFETCH_DELAY_MS);
|
|
662
|
+
try {
|
|
663
|
+
return await deps.getTransactions(planHash === void 0 ? {} : { planHash });
|
|
664
|
+
} catch (error) {
|
|
665
|
+
if (!hasCode(error, "state_changed")) throw error;
|
|
666
|
+
if (planHash !== void 0 || attempt === STATE_CHANGED_REFETCHES) {
|
|
667
|
+
throw new CloseStateChangedError(error);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function resumedPosition(resume) {
|
|
673
|
+
var _a, _b, _c;
|
|
674
|
+
const txIndex = (_a = resume.txIndex) != null ? _a : 0;
|
|
675
|
+
return {
|
|
676
|
+
round: (_b = resume.round) != null ? _b : 0,
|
|
677
|
+
txIndex,
|
|
678
|
+
txCount: (_c = resume.txCount) != null ? _c : txIndex + 1 + resume.queue.length,
|
|
679
|
+
moreRoundsExpected: resume.requiresAnotherCall
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function report(deps, build) {
|
|
683
|
+
if (!deps.onProgressEvent) return;
|
|
684
|
+
try {
|
|
685
|
+
const returned = deps.onProgressEvent(build());
|
|
686
|
+
if (returned instanceof Promise) returned.catch(() => {
|
|
687
|
+
});
|
|
688
|
+
} catch (e) {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function reportTx(deps, phase, at, covers) {
|
|
692
|
+
report(deps, () => ({
|
|
693
|
+
phase,
|
|
694
|
+
round: at.round,
|
|
695
|
+
txIndex: at.txIndex,
|
|
696
|
+
txCount: at.txCount,
|
|
697
|
+
covers: [...covers],
|
|
698
|
+
moreRoundsExpected: at.moreRoundsExpected
|
|
699
|
+
}));
|
|
700
|
+
}
|
|
701
|
+
async function processTxs(deps, txs, start) {
|
|
355
702
|
for (let i = 0; i < txs.length; i++) {
|
|
356
703
|
const tx = txs[i];
|
|
704
|
+
const at = __spreadProps(__spreadValues({}, start), { txIndex: start.txIndex + i });
|
|
705
|
+
reportTx(deps, "verifying", at, tx.covers);
|
|
357
706
|
await deps.verify(tx);
|
|
358
|
-
await signOrThrow(deps, tx, tx.xdr, txs.slice(i + 1),
|
|
707
|
+
await signOrThrow(deps, tx, tx.xdr, txs.slice(i + 1), at);
|
|
359
708
|
}
|
|
360
709
|
}
|
|
361
|
-
async function signOrThrow(deps, tx, xdr, queue,
|
|
362
|
-
var _a;
|
|
710
|
+
async function signOrThrow(deps, tx, xdr, queue, at) {
|
|
711
|
+
var _a, _b, _c, _d, _e, _f;
|
|
363
712
|
const required = deps.requiredWeight(tx);
|
|
713
|
+
reportTx(deps, "awaiting-signature", at, tx.covers);
|
|
364
714
|
const { xdr: signedXdr, weight } = await deps.sign(tx, xdr);
|
|
365
715
|
if (weight < required) {
|
|
366
716
|
throw new InsufficientSignatureWeightError({
|
|
@@ -369,18 +719,81 @@ async function signOrThrow(deps, tx, xdr, queue, requiresAnotherCall) {
|
|
|
369
719
|
requiredWeight: required,
|
|
370
720
|
accumulatedWeight: weight,
|
|
371
721
|
queue,
|
|
372
|
-
requiresAnotherCall
|
|
722
|
+
requiresAnotherCall: at.moreRoundsExpected,
|
|
723
|
+
round: at.round,
|
|
724
|
+
txIndex: at.txIndex,
|
|
725
|
+
txCount: at.txCount
|
|
373
726
|
});
|
|
374
727
|
}
|
|
375
|
-
|
|
376
|
-
|
|
728
|
+
reportTx(deps, "submitting", at, tx.covers);
|
|
729
|
+
const finalXdr = deps.prepare ? await deps.prepare(tx, signedXdr) : signedXdr;
|
|
730
|
+
if (!deps.reconcile) {
|
|
731
|
+
(_a = deps.onConfirmed) == null ? void 0 : _a.call(deps, tx, await deps.submit(tx, finalXdr));
|
|
732
|
+
reportTx(deps, "confirmed", at, tx.covers);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const submission = openSubmission(deps.reconcile, tx, finalXdr);
|
|
736
|
+
await ((_c = (_b = deps.reconcile).onSubmission) == null ? void 0 : _c.call(_b, submission));
|
|
737
|
+
await submitUntilSettled(deps, deps.reconcile, tx, finalXdr, submission);
|
|
738
|
+
await ((_e = (_d = deps.reconcile).onSubmission) == null ? void 0 : _e.call(_d, null));
|
|
739
|
+
if (submissionClosesAccount(submission)) deps.confirmedMerge = submission;
|
|
740
|
+
(_f = deps.onConfirmed) == null ? void 0 : _f.call(deps, __spreadProps(__spreadValues({}, tx), { covers: submission.covers }), submission.hash);
|
|
741
|
+
reportTx(deps, "confirmed", at, submission.covers);
|
|
742
|
+
}
|
|
743
|
+
function openSubmission(reconcile, tx, finalXdr) {
|
|
744
|
+
const claimed = tx.covers.filter((s) => s !== "MERGE" && s !== "CLOSE_ACCOUNT");
|
|
745
|
+
return __spreadValues(__spreadValues({
|
|
746
|
+
hash: reconcile.hashOf(finalXdr),
|
|
747
|
+
covers: reconcile.closesAccount(finalXdr) ? [...claimed, "MERGE", "CLOSE_ACCOUNT"] : claimed,
|
|
748
|
+
validUntil: reconcile.validUntil(finalXdr)
|
|
749
|
+
}, tx.coversTargets && { coversTargets: tx.coversTargets }), tx.needsSponsoredFee && { needsSponsoredFee: true });
|
|
750
|
+
}
|
|
751
|
+
async function submitUntilSettled(deps, reconcile, tx, finalXdr, submission) {
|
|
752
|
+
var _a, _b, _c, _d;
|
|
753
|
+
for (let attempt = 0; attempt <= SUBMIT_RESEND_ATTEMPTS; attempt++) {
|
|
754
|
+
if (attempt > 0) {
|
|
755
|
+
(_a = deps.onProgress) == null ? void 0 : _a.call(deps, "Still waiting for the network to confirm\u2026");
|
|
756
|
+
await ((_b = deps.sleep) != null ? _b : defaultSleep)(SUBMIT_RESEND_DELAY_MS);
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
if (await deps.submit(tx, finalXdr) === submission.hash) return;
|
|
760
|
+
} catch (error) {
|
|
761
|
+
if (!isSubmitRefused(error)) continue;
|
|
762
|
+
if (attempt === 0) {
|
|
763
|
+
await ((_c = reconcile.onSubmission) == null ? void 0 : _c.call(reconcile, null));
|
|
764
|
+
throw error;
|
|
765
|
+
}
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const settled = await resolveSubmission(submission, reconcile.lookup, reconcile.now);
|
|
770
|
+
if (settled.outcome === "confirmed") return;
|
|
771
|
+
if (settled.outcome === "failed") {
|
|
772
|
+
await ((_d = reconcile.onSubmission) == null ? void 0 : _d.call(reconcile, null));
|
|
773
|
+
throw new CloseTransactionFailedError(submission, settled.message);
|
|
774
|
+
}
|
|
775
|
+
throw new CloseOutcomeUnknownError(submission);
|
|
377
776
|
}
|
|
378
777
|
export {
|
|
778
|
+
CloseDidNotConvergeError,
|
|
779
|
+
CloseOutcomeUnknownError,
|
|
780
|
+
CloseStateChangedError,
|
|
781
|
+
CloseTransactionFailedError,
|
|
379
782
|
InsufficientSignatureWeightError,
|
|
380
783
|
LumenWipeAbortError,
|
|
381
784
|
LumenWipeApiError,
|
|
382
785
|
LumenWipeClient,
|
|
786
|
+
LumenWipeIntegratorClient,
|
|
787
|
+
LumenWipeInvalidNetworkError,
|
|
383
788
|
LumenWipeTimeoutError,
|
|
789
|
+
STATE_CHANGED_REFETCHES,
|
|
790
|
+
STATE_CHANGED_REFETCH_DELAY_MS,
|
|
791
|
+
SUBMISSION_GIVE_UP_MARGIN_SECONDS,
|
|
792
|
+
SUBMIT_RESEND_ATTEMPTS,
|
|
793
|
+
SUBMIT_RESEND_DELAY_MS,
|
|
384
794
|
isApiErrorCode,
|
|
385
|
-
|
|
795
|
+
isSubmitRefused,
|
|
796
|
+
resolveSubmission,
|
|
797
|
+
runClose,
|
|
798
|
+
submissionClosesAccount
|
|
386
799
|
};
|