@arkade-os/sdk 0.4.20 → 0.4.22
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/dist/cjs/contracts/contractWatcher.js +42 -20
- package/dist/cjs/providers/ark.js +65 -48
- package/dist/cjs/providers/indexer.js +60 -47
- package/dist/cjs/providers/utils.js +58 -12
- package/dist/cjs/wallet/delegator.js +27 -18
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +3 -1
- package/dist/cjs/wallet/vtxo-manager.js +7 -5
- package/dist/cjs/wallet/wallet.js +127 -153
- package/dist/esm/contracts/contractWatcher.js +40 -18
- package/dist/esm/providers/ark.js +65 -48
- package/dist/esm/providers/indexer.js +60 -47
- package/dist/esm/providers/utils.js +58 -12
- package/dist/esm/wallet/delegator.js +27 -18
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +3 -1
- package/dist/esm/wallet/vtxo-manager.js +7 -5
- package/dist/esm/wallet/wallet.js +127 -153
- package/dist/types/contracts/contractWatcher.d.ts +3 -0
- package/dist/types/contracts/types.d.ts +5 -5
- package/dist/types/providers/utils.d.ts +9 -5
- package/dist/types/wallet/delegator.d.ts +8 -3
- package/dist/types/wallet/wallet.d.ts +7 -6
- package/package.json +4 -4
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ContractWatcher = void 0;
|
|
4
|
-
const utils_1 = require("../
|
|
4
|
+
const utils_1 = require("../wallet/utils");
|
|
5
|
+
const utils_2 = require("../providers/utils");
|
|
5
6
|
/**
|
|
6
7
|
* Watches multiple contracts for virtual output state changes with resilient connection handling.
|
|
7
8
|
*
|
|
@@ -254,13 +255,18 @@ class ContractWatcher {
|
|
|
254
255
|
}
|
|
255
256
|
/**
|
|
256
257
|
* Connect to the subscription.
|
|
258
|
+
*
|
|
259
|
+
* @param skipUpdate - Skip the leading `updateSubscription` call when
|
|
260
|
+
* the caller has already established `subscriptionId`.
|
|
257
261
|
*/
|
|
258
|
-
async connect() {
|
|
262
|
+
async connect(skipUpdate = false) {
|
|
259
263
|
if (!this.isWatching)
|
|
260
264
|
return;
|
|
261
265
|
this.connectionState = "connecting";
|
|
262
266
|
try {
|
|
263
|
-
|
|
267
|
+
if (!skipUpdate) {
|
|
268
|
+
await this.updateSubscription();
|
|
269
|
+
}
|
|
264
270
|
// Poll immediately after connection to sync state
|
|
265
271
|
await this.pollAllContracts();
|
|
266
272
|
this.connectionState = "connected";
|
|
@@ -271,7 +277,7 @@ class ContractWatcher {
|
|
|
271
277
|
// indefinitely and block the caller.
|
|
272
278
|
// Error management must be implemented to ensure the connection
|
|
273
279
|
// is restored and events are fired.
|
|
274
|
-
if ((0,
|
|
280
|
+
if ((0, utils_2.isEventSourceError)(e)) {
|
|
275
281
|
console.debug("ContractWatcher subscription disconnected; reconnecting");
|
|
276
282
|
}
|
|
277
283
|
else {
|
|
@@ -391,11 +397,30 @@ class ContractWatcher {
|
|
|
391
397
|
}
|
|
392
398
|
}
|
|
393
399
|
async tryUpdateSubscription() {
|
|
400
|
+
const hadSubscription = this.subscriptionId !== undefined;
|
|
394
401
|
try {
|
|
395
402
|
await this.updateSubscription();
|
|
396
403
|
}
|
|
397
404
|
catch (error) {
|
|
398
405
|
// nothing, the connection will be retried later
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
// Cold start: `startWatching` may have run with zero scripts,
|
|
409
|
+
// leaving `listenLoop` parked behind the reconnect timer. Kick
|
|
410
|
+
// `connect` now so streaming resumes without waiting on the
|
|
411
|
+
// backoff. `skipUpdate` avoids re-issuing `subscribeForScripts`.
|
|
412
|
+
const justGotSubscription = !hadSubscription && this.subscriptionId !== undefined;
|
|
413
|
+
const listenerParked = this.connectionState === "disconnected" ||
|
|
414
|
+
this.connectionState === "reconnecting";
|
|
415
|
+
if (this.isWatching && justGotSubscription && listenerParked) {
|
|
416
|
+
if (this.reconnectTimeoutId) {
|
|
417
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
418
|
+
this.reconnectTimeoutId = undefined;
|
|
419
|
+
}
|
|
420
|
+
this.reconnectAttempts = 0;
|
|
421
|
+
this.connect(true).catch((error) => {
|
|
422
|
+
console.warn("ContractWatcher cold-start connect failed:", error);
|
|
423
|
+
});
|
|
399
424
|
}
|
|
400
425
|
}
|
|
401
426
|
/**
|
|
@@ -529,18 +554,22 @@ class ContractWatcher {
|
|
|
529
554
|
const state = this.contracts.get(contractScript);
|
|
530
555
|
if (!state)
|
|
531
556
|
return;
|
|
557
|
+
const extended = [];
|
|
558
|
+
for (const v of vtxos) {
|
|
559
|
+
try {
|
|
560
|
+
const extendedVtxo = (0, utils_1.extendVirtualCoinForContract)(v, state.contract);
|
|
561
|
+
extended.push({ ...extendedVtxo, contractScript });
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
console.warn("failed to extend vtxo: ", v);
|
|
565
|
+
extended.push({ ...v, contractScript });
|
|
566
|
+
}
|
|
567
|
+
}
|
|
532
568
|
switch (eventType) {
|
|
533
569
|
case "vtxo_received":
|
|
534
570
|
this.eventCallback({
|
|
535
571
|
type: "vtxo_received",
|
|
536
|
-
vtxos:
|
|
537
|
-
...v,
|
|
538
|
-
contractScript,
|
|
539
|
-
// These fields may not be available from basic VirtualCoin
|
|
540
|
-
forfeitTapLeafScript: undefined,
|
|
541
|
-
intentTapLeafScript: undefined,
|
|
542
|
-
tapTree: undefined,
|
|
543
|
-
})),
|
|
572
|
+
vtxos: extended,
|
|
544
573
|
contractScript,
|
|
545
574
|
contract: state.contract,
|
|
546
575
|
timestamp,
|
|
@@ -549,14 +578,7 @@ class ContractWatcher {
|
|
|
549
578
|
case "vtxo_spent":
|
|
550
579
|
this.eventCallback({
|
|
551
580
|
type: "vtxo_spent",
|
|
552
|
-
vtxos:
|
|
553
|
-
...v,
|
|
554
|
-
contractScript,
|
|
555
|
-
// These fields may not be available from basic VirtualCoin
|
|
556
|
-
forfeitTapLeafScript: undefined,
|
|
557
|
-
intentTapLeafScript: undefined,
|
|
558
|
-
tapTree: undefined,
|
|
559
|
-
})),
|
|
581
|
+
vtxos: extended,
|
|
560
582
|
contractScript,
|
|
561
583
|
contract: state.contract,
|
|
562
584
|
timestamp,
|
|
@@ -236,18 +236,19 @@ class RestArkProvider {
|
|
|
236
236
|
// leak the underlying SSE connection. `return()` is overridden below
|
|
237
237
|
// so that closing the generator also closes the connection even when
|
|
238
238
|
// the body is currently suspended at an await point.
|
|
239
|
-
let
|
|
239
|
+
let iterator = null;
|
|
240
|
+
const closeIterator = () => iterator?.close();
|
|
240
241
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
241
242
|
const self = this;
|
|
242
243
|
const gen = (async function* () {
|
|
243
|
-
const abortHandler =
|
|
244
|
+
const abortHandler = closeIterator;
|
|
244
245
|
signal?.addEventListener("abort", abortHandler);
|
|
245
246
|
try {
|
|
246
247
|
while (!signal?.aborted) {
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
const currentIterator = (0, utils_1.eventSourceIterator)(new EventSource(url + queryParams));
|
|
249
|
+
iterator = currentIterator;
|
|
249
250
|
try {
|
|
250
|
-
for await (const event of
|
|
251
|
+
for await (const event of currentIterator) {
|
|
251
252
|
if (signal?.aborted)
|
|
252
253
|
break;
|
|
253
254
|
try {
|
|
@@ -281,71 +282,87 @@ class RestArkProvider {
|
|
|
281
282
|
throw error;
|
|
282
283
|
}
|
|
283
284
|
finally {
|
|
284
|
-
|
|
285
|
+
currentIterator.close();
|
|
286
|
+
iterator = null;
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
}
|
|
288
290
|
finally {
|
|
289
291
|
signal?.removeEventListener("abort", abortHandler);
|
|
290
|
-
|
|
292
|
+
closeIterator();
|
|
291
293
|
}
|
|
292
294
|
})();
|
|
293
295
|
const origReturn = gen.return.bind(gen);
|
|
294
296
|
gen.return = (value) => {
|
|
295
|
-
|
|
297
|
+
closeIterator();
|
|
296
298
|
return origReturn(value);
|
|
297
299
|
};
|
|
298
300
|
return gen;
|
|
299
301
|
}
|
|
300
|
-
|
|
302
|
+
getTransactionsStream(signal) {
|
|
301
303
|
const url = `${this.serverUrl}/v1/txs`;
|
|
302
|
-
|
|
304
|
+
let iterator = null;
|
|
305
|
+
const closeIterator = () => iterator?.close();
|
|
306
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
307
|
+
const self = this;
|
|
308
|
+
const gen = (async function* () {
|
|
309
|
+
const abortHandler = closeIterator;
|
|
310
|
+
signal?.addEventListener("abort", abortHandler);
|
|
303
311
|
try {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
312
|
+
while (!signal?.aborted) {
|
|
313
|
+
try {
|
|
314
|
+
const currentIterator = (0, utils_1.eventSourceIterator)(new EventSource(url));
|
|
315
|
+
iterator = currentIterator;
|
|
316
|
+
for await (const event of currentIterator) {
|
|
317
|
+
if (signal?.aborted)
|
|
318
|
+
break;
|
|
319
|
+
try {
|
|
320
|
+
const data = JSON.parse(event.data);
|
|
321
|
+
const txNotification = self.parseTransactionNotification(data);
|
|
322
|
+
if (txNotification) {
|
|
323
|
+
yield txNotification;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
console.error("Failed to parse transaction notification:", err);
|
|
328
|
+
throw err;
|
|
319
329
|
}
|
|
320
330
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (signal?.aborted ||
|
|
334
|
+
(error instanceof Error &&
|
|
335
|
+
error.name === "AbortError")) {
|
|
336
|
+
break;
|
|
324
337
|
}
|
|
338
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
339
|
+
if (isFetchTimeoutError(error)) {
|
|
340
|
+
console.debug("Timeout error ignored");
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if ((0, utils_1.isEventSourceError)(error)) {
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
console.error("Transaction stream error:", error);
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
closeIterator();
|
|
351
|
+
iterator = null;
|
|
325
352
|
}
|
|
326
|
-
}
|
|
327
|
-
finally {
|
|
328
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
329
|
-
eventSource.close();
|
|
330
353
|
}
|
|
331
354
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
338
|
-
if (isFetchTimeoutError(error)) {
|
|
339
|
-
console.debug("Timeout error ignored");
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
if ((0, utils_1.isEventSourceError)(error)) {
|
|
343
|
-
throw error;
|
|
344
|
-
}
|
|
345
|
-
console.error("Transaction stream error:", error);
|
|
346
|
-
throw error;
|
|
355
|
+
finally {
|
|
356
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
357
|
+
closeIterator();
|
|
347
358
|
}
|
|
348
|
-
}
|
|
359
|
+
})();
|
|
360
|
+
const origReturn = gen.return.bind(gen);
|
|
361
|
+
gen.return = (value) => {
|
|
362
|
+
closeIterator();
|
|
363
|
+
return origReturn(value);
|
|
364
|
+
};
|
|
365
|
+
return gen;
|
|
349
366
|
}
|
|
350
367
|
async getPendingTxs(intent) {
|
|
351
368
|
const url = `${this.serverUrl}/v1/tx/pending`;
|
|
@@ -156,62 +156,75 @@ class RestIndexerProvider {
|
|
|
156
156
|
}
|
|
157
157
|
return data;
|
|
158
158
|
}
|
|
159
|
-
|
|
159
|
+
getSubscription(subscriptionId, abortSignal) {
|
|
160
160
|
const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
|
|
161
|
-
|
|
161
|
+
let iterator = null;
|
|
162
|
+
const closeIterator = () => iterator?.close();
|
|
163
|
+
const gen = (async function* () {
|
|
164
|
+
const abortHandler = closeIterator;
|
|
165
|
+
abortSignal?.addEventListener("abort", abortHandler);
|
|
162
166
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
167
|
+
while (!abortSignal?.aborted) {
|
|
168
|
+
try {
|
|
169
|
+
const currentIterator = (0, utils_1.eventSourceIterator)(new EventSource(url));
|
|
170
|
+
iterator = currentIterator;
|
|
171
|
+
for await (const event of currentIterator) {
|
|
172
|
+
if (abortSignal?.aborted)
|
|
173
|
+
break;
|
|
174
|
+
try {
|
|
175
|
+
const data = JSON.parse(event.data);
|
|
176
|
+
if (data.event) {
|
|
177
|
+
yield {
|
|
178
|
+
txid: data.event.txid,
|
|
179
|
+
scripts: data.event.scripts || [],
|
|
180
|
+
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
181
|
+
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
182
|
+
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
183
|
+
tx: data.event.tx,
|
|
184
|
+
checkpointTxs: data.event.checkpointTxs,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.error("Failed to parse subscription event:", err);
|
|
190
|
+
throw err;
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (abortSignal?.aborted ||
|
|
196
|
+
(error instanceof Error &&
|
|
197
|
+
error.name === "AbortError")) {
|
|
198
|
+
break;
|
|
190
199
|
}
|
|
200
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
201
|
+
if ((0, ark_1.isFetchTimeoutError)(error)) {
|
|
202
|
+
console.debug("Timeout error ignored");
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if ((0, utils_1.isEventSourceError)(error)) {
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
console.error("Subscription error:", error);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
closeIterator();
|
|
213
|
+
iterator = null;
|
|
191
214
|
}
|
|
192
|
-
}
|
|
193
|
-
finally {
|
|
194
|
-
abortSignal?.removeEventListener("abort", abortHandler);
|
|
195
|
-
eventSource.close();
|
|
196
215
|
}
|
|
197
216
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
204
|
-
if ((0, ark_1.isFetchTimeoutError)(error)) {
|
|
205
|
-
console.debug("Timeout error ignored");
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
if ((0, utils_1.isEventSourceError)(error)) {
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
|
-
console.error("Subscription error:", error);
|
|
212
|
-
throw error;
|
|
217
|
+
finally {
|
|
218
|
+
abortSignal?.removeEventListener("abort", abortHandler);
|
|
219
|
+
closeIterator();
|
|
213
220
|
}
|
|
214
|
-
}
|
|
221
|
+
})();
|
|
222
|
+
const origReturn = gen.return.bind(gen);
|
|
223
|
+
gen.return = (value) => {
|
|
224
|
+
closeIterator();
|
|
225
|
+
return origReturn(value);
|
|
226
|
+
};
|
|
227
|
+
return gen;
|
|
215
228
|
}
|
|
216
229
|
async getVirtualTxs(txids, opts) {
|
|
217
230
|
let url = `${this.serverUrl}/v1/indexer/virtualTx/${txids.join(",")}`;
|
|
@@ -2,32 +2,70 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.eventSourceIterator = eventSourceIterator;
|
|
4
4
|
exports.isEventSourceError = isEventSourceError;
|
|
5
|
+
function createAbortError() {
|
|
6
|
+
const error = new Error("EventSource closed");
|
|
7
|
+
error.name = "AbortError";
|
|
8
|
+
return error;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
|
-
* Creates
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* Creates a close-aware EventSource async iterator.
|
|
12
|
+
*
|
|
13
|
+
* Listeners attach eagerly so events are buffered before the first next() call.
|
|
14
|
+
* close() closes the EventSource, removes listeners, and wakes any pending
|
|
15
|
+
* next() even when the browser does not emit an error from EventSource.close().
|
|
10
16
|
*/
|
|
11
17
|
function eventSourceIterator(eventSource) {
|
|
12
18
|
const messageQueue = [];
|
|
13
19
|
const errorQueue = [];
|
|
14
20
|
let messageResolve = null;
|
|
15
21
|
let errorResolve = null;
|
|
22
|
+
let closed = false;
|
|
23
|
+
let cleanedUp = false;
|
|
24
|
+
const cleanup = () => {
|
|
25
|
+
if (cleanedUp)
|
|
26
|
+
return;
|
|
27
|
+
cleanedUp = true;
|
|
28
|
+
eventSource.removeEventListener("message", messageHandler);
|
|
29
|
+
eventSource.removeEventListener("error", errorHandler);
|
|
30
|
+
};
|
|
31
|
+
const close = () => {
|
|
32
|
+
if (closed)
|
|
33
|
+
return;
|
|
34
|
+
closed = true;
|
|
35
|
+
messageQueue.length = 0;
|
|
36
|
+
errorQueue.length = 0;
|
|
37
|
+
eventSource.close();
|
|
38
|
+
cleanup();
|
|
39
|
+
if (errorResolve) {
|
|
40
|
+
const reject = errorResolve;
|
|
41
|
+
messageResolve = null;
|
|
42
|
+
errorResolve = null;
|
|
43
|
+
reject(createAbortError());
|
|
44
|
+
}
|
|
45
|
+
};
|
|
16
46
|
const messageHandler = (event) => {
|
|
47
|
+
if (closed)
|
|
48
|
+
return;
|
|
17
49
|
if (messageResolve) {
|
|
18
|
-
messageResolve
|
|
50
|
+
const resolve = messageResolve;
|
|
19
51
|
messageResolve = null;
|
|
52
|
+
errorResolve = null;
|
|
53
|
+
resolve(event);
|
|
20
54
|
}
|
|
21
55
|
else {
|
|
22
56
|
messageQueue.push(event);
|
|
23
57
|
}
|
|
24
58
|
};
|
|
25
59
|
const errorHandler = () => {
|
|
60
|
+
if (closed)
|
|
61
|
+
return;
|
|
26
62
|
const error = new Error("EventSource error");
|
|
27
63
|
error.name = "EventSourceError";
|
|
28
64
|
if (errorResolve) {
|
|
29
|
-
errorResolve
|
|
65
|
+
const reject = errorResolve;
|
|
66
|
+
messageResolve = null;
|
|
30
67
|
errorResolve = null;
|
|
68
|
+
reject(error);
|
|
31
69
|
}
|
|
32
70
|
else {
|
|
33
71
|
errorQueue.push(error);
|
|
@@ -37,9 +75,9 @@ function eventSourceIterator(eventSource) {
|
|
|
37
75
|
// even before the caller starts iterating
|
|
38
76
|
eventSource.addEventListener("message", messageHandler);
|
|
39
77
|
eventSource.addEventListener("error", errorHandler);
|
|
40
|
-
|
|
78
|
+
const gen = (async function* () {
|
|
41
79
|
try {
|
|
42
|
-
while (
|
|
80
|
+
while (!closed) {
|
|
43
81
|
// if we have queued messages, yield the first one, remove it from the queue
|
|
44
82
|
if (messageQueue.length > 0) {
|
|
45
83
|
yield messageQueue.shift();
|
|
@@ -58,17 +96,25 @@ function eventSourceIterator(eventSource) {
|
|
|
58
96
|
messageResolve = null;
|
|
59
97
|
errorResolve = null;
|
|
60
98
|
});
|
|
61
|
-
if (result) {
|
|
99
|
+
if (!closed && result) {
|
|
62
100
|
yield result;
|
|
63
101
|
}
|
|
64
102
|
}
|
|
65
103
|
}
|
|
66
104
|
finally {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
eventSource.
|
|
105
|
+
closed = true;
|
|
106
|
+
cleanup();
|
|
107
|
+
eventSource.close();
|
|
70
108
|
}
|
|
71
109
|
})();
|
|
110
|
+
const origReturn = gen.return.bind(gen);
|
|
111
|
+
const managed = gen;
|
|
112
|
+
managed.close = close;
|
|
113
|
+
managed.return = (value) => {
|
|
114
|
+
close();
|
|
115
|
+
return origReturn(value);
|
|
116
|
+
};
|
|
117
|
+
return managed;
|
|
72
118
|
}
|
|
73
119
|
function isEventSourceError(error) {
|
|
74
120
|
return error instanceof Error && error.name === "EventSourceError";
|
|
@@ -29,20 +29,30 @@ class DelegatorManagerImpl {
|
|
|
29
29
|
// fetch server and delegator info once, shared across all groups
|
|
30
30
|
const arkInfo = await this.arkInfoProvider.getInfo();
|
|
31
31
|
const delegateInfo = await this.delegatorProvider.getDelegateInfo();
|
|
32
|
+
// keep only vtxos that can be signed by the delegate
|
|
33
|
+
const eligible = vtxos
|
|
34
|
+
.filter((v) => findDelegateTapLeaf(v, delegateInfo.pubkey) !== undefined)
|
|
35
|
+
.map((v) => v);
|
|
36
|
+
if (eligible.length === 0) {
|
|
37
|
+
return { delegated: [], failed: [] };
|
|
38
|
+
}
|
|
32
39
|
// if explicit delegateAt is provided, delegate all virtual outputs at once without sorting
|
|
33
40
|
if (delegateAt) {
|
|
34
41
|
try {
|
|
35
|
-
await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo,
|
|
42
|
+
await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, eligible, destinationScript, delegateAt);
|
|
36
43
|
}
|
|
37
44
|
catch (error) {
|
|
38
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
delegated: [],
|
|
47
|
+
failed: [{ outpoints: eligible, error }],
|
|
48
|
+
};
|
|
39
49
|
}
|
|
40
|
-
return { delegated:
|
|
50
|
+
return { delegated: eligible, failed: [] };
|
|
41
51
|
}
|
|
42
52
|
// if no explicit delegateAt is provided, sort virtual outputs by expiry and delegate in groups of the same expiry day
|
|
43
53
|
const groupByExpiry = new Map();
|
|
44
54
|
let recoverableVtxos = [];
|
|
45
|
-
for (const vtxo of
|
|
55
|
+
for (const vtxo of eligible) {
|
|
46
56
|
if ((0, __1.isRecoverable)(vtxo)) {
|
|
47
57
|
recoverableVtxos.push(vtxo);
|
|
48
58
|
continue;
|
|
@@ -190,20 +200,7 @@ async function delegate(identity, delegatorProvider, arkInfo, delegateInfo, vtxo
|
|
|
190
200
|
await delegatorProvider.delegate(registerIntent, forfeits);
|
|
191
201
|
}
|
|
192
202
|
async function makeDelegateForfeitTx(input, connectorAmount, delegatePubkey, forfeitOutputScript, identity) {
|
|
193
|
-
|
|
194
|
-
delegatePubkey = delegatePubkey.slice(2);
|
|
195
|
-
}
|
|
196
|
-
const vtxoScript = __1.VtxoScript.decode(input.tapTree);
|
|
197
|
-
const delegateTapLeaf = vtxoScript.leaves.find((tapLeaf) => {
|
|
198
|
-
const arkTapscript = (0, __1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(tapLeaf));
|
|
199
|
-
if (!__1.MultisigTapscript.is(arkTapscript))
|
|
200
|
-
return false;
|
|
201
|
-
if (!arkTapscript.params.pubkeys
|
|
202
|
-
.map(base_1.hex.encode)
|
|
203
|
-
.includes(delegatePubkey))
|
|
204
|
-
return false;
|
|
205
|
-
return true;
|
|
206
|
-
});
|
|
203
|
+
const delegateTapLeaf = findDelegateTapLeaf(input, delegatePubkey);
|
|
207
204
|
if (!delegateTapLeaf) {
|
|
208
205
|
throw new Error(`delegate tap leaf not found for input: ${input.txid}:${input.vout}`);
|
|
209
206
|
}
|
|
@@ -291,3 +288,15 @@ function getDayTimestamp(timestamp) {
|
|
|
291
288
|
date.setUTCHours(0, 0, 0, 0);
|
|
292
289
|
return date.getTime();
|
|
293
290
|
}
|
|
291
|
+
function findDelegateTapLeaf(vtxo, delegatePubkey) {
|
|
292
|
+
if (!vtxo.tapTree)
|
|
293
|
+
return undefined;
|
|
294
|
+
const pk = delegatePubkey.length === 66 ? delegatePubkey.slice(2) : delegatePubkey;
|
|
295
|
+
const vtxoScript = __1.VtxoScript.decode(vtxo.tapTree);
|
|
296
|
+
return vtxoScript.leaves.find((tapLeaf) => {
|
|
297
|
+
const arkTapscript = (0, __1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(tapLeaf));
|
|
298
|
+
if (!__1.MultisigTapscript.is(arkTapscript))
|
|
299
|
+
return false;
|
|
300
|
+
return arkTapscript.params.pubkeys.map(base_1.hex.encode).includes(pk);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
@@ -706,7 +706,9 @@ class WalletMessageHandler {
|
|
|
706
706
|
const { vtxoOutpoints, destination, delegateAt } = message.payload;
|
|
707
707
|
const allVtxos = await wallet.getVtxos();
|
|
708
708
|
const outpointSet = new Set(vtxoOutpoints.map((o) => `${o.txid}:${o.vout}`));
|
|
709
|
-
const filtered = allVtxos
|
|
709
|
+
const filtered = allVtxos
|
|
710
|
+
.filter((v) => outpointSet.has(`${v.txid}:${v.vout}`))
|
|
711
|
+
.map((v) => ({ ...v, contractScript: v.script }));
|
|
710
712
|
const result = await delegatorManager.delegate(filtered, destination, delegateAt !== undefined ? new Date(delegateAt) : undefined);
|
|
711
713
|
return {
|
|
712
714
|
tag: this.messageTag,
|
|
@@ -698,11 +698,13 @@ class VtxoManager {
|
|
|
698
698
|
console.error("Error renewing VTXOs:", e);
|
|
699
699
|
});
|
|
700
700
|
}
|
|
701
|
-
delegatorManager
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
701
|
+
if (delegatorManager) {
|
|
702
|
+
delegatorManager
|
|
703
|
+
.delegate(event.vtxos, destination)
|
|
704
|
+
.catch((e) => {
|
|
705
|
+
console.error("Error delegating VTXOs:", e);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
706
708
|
});
|
|
707
709
|
return stopWatching;
|
|
708
710
|
}
|