@arkade-os/sdk 0.4.20 → 0.4.21
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 +26 -2
- 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/wallet.js +127 -153
- package/dist/esm/contracts/contractWatcher.js +26 -2
- 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/wallet.js +127 -153
- package/dist/types/contracts/contractWatcher.d.ts +3 -0
- package/dist/types/providers/utils.d.ts +9 -5
- package/dist/types/wallet/wallet.d.ts +7 -6
- package/package.json +1 -1
|
@@ -254,13 +254,18 @@ class ContractWatcher {
|
|
|
254
254
|
}
|
|
255
255
|
/**
|
|
256
256
|
* Connect to the subscription.
|
|
257
|
+
*
|
|
258
|
+
* @param skipUpdate - Skip the leading `updateSubscription` call when
|
|
259
|
+
* the caller has already established `subscriptionId`.
|
|
257
260
|
*/
|
|
258
|
-
async connect() {
|
|
261
|
+
async connect(skipUpdate = false) {
|
|
259
262
|
if (!this.isWatching)
|
|
260
263
|
return;
|
|
261
264
|
this.connectionState = "connecting";
|
|
262
265
|
try {
|
|
263
|
-
|
|
266
|
+
if (!skipUpdate) {
|
|
267
|
+
await this.updateSubscription();
|
|
268
|
+
}
|
|
264
269
|
// Poll immediately after connection to sync state
|
|
265
270
|
await this.pollAllContracts();
|
|
266
271
|
this.connectionState = "connected";
|
|
@@ -391,11 +396,30 @@ class ContractWatcher {
|
|
|
391
396
|
}
|
|
392
397
|
}
|
|
393
398
|
async tryUpdateSubscription() {
|
|
399
|
+
const hadSubscription = this.subscriptionId !== undefined;
|
|
394
400
|
try {
|
|
395
401
|
await this.updateSubscription();
|
|
396
402
|
}
|
|
397
403
|
catch (error) {
|
|
398
404
|
// nothing, the connection will be retried later
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
// Cold start: `startWatching` may have run with zero scripts,
|
|
408
|
+
// leaving `listenLoop` parked behind the reconnect timer. Kick
|
|
409
|
+
// `connect` now so streaming resumes without waiting on the
|
|
410
|
+
// backoff. `skipUpdate` avoids re-issuing `subscribeForScripts`.
|
|
411
|
+
const justGotSubscription = !hadSubscription && this.subscriptionId !== undefined;
|
|
412
|
+
const listenerParked = this.connectionState === "disconnected" ||
|
|
413
|
+
this.connectionState === "reconnecting";
|
|
414
|
+
if (this.isWatching && justGotSubscription && listenerParked) {
|
|
415
|
+
if (this.reconnectTimeoutId) {
|
|
416
|
+
clearTimeout(this.reconnectTimeoutId);
|
|
417
|
+
this.reconnectTimeoutId = undefined;
|
|
418
|
+
}
|
|
419
|
+
this.reconnectAttempts = 0;
|
|
420
|
+
this.connect(true).catch((error) => {
|
|
421
|
+
console.warn("ContractWatcher cold-start connect failed:", error);
|
|
422
|
+
});
|
|
399
423
|
}
|
|
400
424
|
}
|
|
401
425
|
/**
|
|
@@ -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";
|