@arkade-os/sdk 0.3.0-alpha.1 → 0.3.0-alpha.4

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.
@@ -135,10 +135,8 @@ class Wallet {
135
135
  // Save tapscripts
136
136
  const offchainTapscript = bareVtxoTapscript;
137
137
  // the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
138
- const serverUnrollScript = tapscript_1.CSVMultisigTapscript.encode({
139
- timelock: exitTimelock,
140
- pubkeys: [serverPubKey],
141
- });
138
+ const rawCheckpointExitClosure = base_1.hex.decode(info.checkpointExitClosure);
139
+ const serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(rawCheckpointExitClosure);
142
140
  // parse the server forfeit address
143
141
  // server is expecting funds to be sent to this address
144
142
  const forfeitAddress = (0, payment_js_1.Address)(network).decode(info.forfeitAddress);
@@ -204,8 +202,9 @@ class Wallet {
204
202
  }
205
203
  async getVtxos(filter) {
206
204
  const address = await this.getAddress();
207
- // Try to get from cache first
208
- const cachedVtxos = await this.walletRepository.getVtxos(address);
205
+ // Try to get from cache first first (optional fast path)
206
+ // const cachedVtxos = await this.walletRepository.getVtxos(address);
207
+ // if (cachedVtxos.length) return cachedVtxos;
209
208
  // For now, always fetch fresh data from provider and update cache
210
209
  // In future, we can add cache invalidation logic based on timestamps
211
210
  const spendableVtxos = await this.getVirtualCoins(filter);
@@ -1,4 +1,4 @@
1
- import { pubSchnorr, randomPrivateKeyBytes, sha256, } from "@scure/btc-signer/utils.js";
1
+ import { pubECDSA, pubSchnorr, randomPrivateKeyBytes, sha256, } from "@scure/btc-signer/utils.js";
2
2
  import { hex } from "@scure/base";
3
3
  import { SigHash } from "@scure/btc-signer/transaction.js";
4
4
  import { TreeSignerSession } from '../tree/signingSession.js';
@@ -70,6 +70,9 @@ export class SingleKey {
70
70
  }
71
71
  return txCpy;
72
72
  }
73
+ compressedPublicKey() {
74
+ return Promise.resolve(pubECDSA(this.key, true));
75
+ }
73
76
  xOnlyPublicKey() {
74
77
  return Promise.resolve(pubSchnorr(this.key));
75
78
  }
@@ -1,4 +1,5 @@
1
1
  import { hex } from "@scure/base";
2
+ import { eventSourceIterator } from './utils.js';
2
3
  export var SettlementEventType;
3
4
  (function (SettlementEventType) {
4
5
  SettlementEventType["BatchStarted"] = "batch_started";
@@ -41,6 +42,7 @@ export class RestArkProvider {
41
42
  vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
42
43
  vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
43
44
  boardingExitDelay: BigInt(fromServer.boardingExitDelay ?? 0),
45
+ checkpointExitClosure: fromServer.checkpointTapscript ?? "",
44
46
  marketHour: "marketHour" in fromServer && fromServer.marketHour != null
45
47
  ? {
46
48
  nextStartTime: BigInt(fromServer.marketHour.nextStartTime ?? 0),
@@ -215,39 +217,21 @@ export class RestArkProvider {
215
217
  : "";
216
218
  while (!signal?.aborted) {
217
219
  try {
218
- const response = await fetch(url + queryParams, {
219
- headers: {
220
- Accept: "application/json",
221
- },
222
- signal,
223
- });
224
- if (!response.ok) {
225
- throw new Error(`Unexpected status ${response.status} when fetching event stream`);
226
- }
227
- if (!response.body) {
228
- throw new Error("Response body is null");
229
- }
230
- const reader = response.body.getReader();
231
- const decoder = new TextDecoder();
232
- let buffer = "";
233
- while (!signal?.aborted) {
234
- const { done, value } = await reader.read();
235
- if (done) {
236
- break;
237
- }
238
- // Append new data to buffer and split by newlines
239
- buffer += decoder.decode(value, { stream: true });
240
- const lines = buffer.split("\n");
241
- // Process all complete lines
242
- for (let i = 0; i < lines.length - 1; i++) {
243
- const line = lines[i].trim();
244
- if (!line)
245
- continue;
220
+ const eventSource = new EventSource(url + queryParams);
221
+ // Set up abort handling
222
+ const abortHandler = () => {
223
+ eventSource.close();
224
+ };
225
+ signal?.addEventListener("abort", abortHandler);
226
+ try {
227
+ for await (const event of eventSourceIterator(eventSource)) {
228
+ if (signal?.aborted)
229
+ break;
246
230
  try {
247
- const data = JSON.parse(line);
248
- const event = this.parseSettlementEvent(data.result);
249
- if (event) {
250
- yield event;
231
+ const data = JSON.parse(event.data);
232
+ const settlementEvent = this.parseSettlementEvent(data);
233
+ if (settlementEvent) {
234
+ yield settlementEvent;
251
235
  }
252
236
  }
253
237
  catch (err) {
@@ -255,8 +239,10 @@ export class RestArkProvider {
255
239
  throw err;
256
240
  }
257
241
  }
258
- // Keep the last partial line in the buffer
259
- buffer = lines[lines.length - 1];
242
+ }
243
+ finally {
244
+ signal?.removeEventListener("abort", abortHandler);
245
+ eventSource.close();
260
246
  }
261
247
  }
262
248
  catch (error) {
@@ -264,7 +250,6 @@ export class RestArkProvider {
264
250
  break;
265
251
  }
266
252
  // ignore timeout errors, they're expected when the server is not sending anything for 5 min
267
- // these timeouts are set by builtin fetch function
268
253
  if (isFetchTimeoutError(error)) {
269
254
  console.debug("Timeout error ignored");
270
255
  continue;
@@ -278,42 +263,32 @@ export class RestArkProvider {
278
263
  const url = `${this.serverUrl}/v1/txs`;
279
264
  while (!signal?.aborted) {
280
265
  try {
281
- const response = await fetch(url, {
282
- headers: {
283
- Accept: "application/json",
284
- },
285
- signal,
286
- });
287
- if (!response.ok) {
288
- throw new Error(`Unexpected status ${response.status} when fetching transaction stream`);
289
- }
290
- if (!response.body) {
291
- throw new Error("Response body is null");
292
- }
293
- const reader = response.body.getReader();
294
- const decoder = new TextDecoder();
295
- let buffer = "";
296
- while (!signal?.aborted) {
297
- const { done, value } = await reader.read();
298
- if (done) {
299
- break;
300
- }
301
- // Append new data to buffer and split by newlines
302
- buffer += decoder.decode(value, { stream: true });
303
- const lines = buffer.split("\n");
304
- // Process all complete lines
305
- for (let i = 0; i < lines.length - 1; i++) {
306
- const line = lines[i].trim();
307
- if (!line)
308
- continue;
309
- const data = JSON.parse(line);
310
- const txNotification = this.parseTransactionNotification(data.result);
311
- if (txNotification) {
312
- yield txNotification;
266
+ const eventSource = new EventSource(url);
267
+ // Set up abort handling
268
+ const abortHandler = () => {
269
+ eventSource.close();
270
+ };
271
+ signal?.addEventListener("abort", abortHandler);
272
+ try {
273
+ for await (const event of eventSourceIterator(eventSource)) {
274
+ if (signal?.aborted)
275
+ break;
276
+ try {
277
+ const data = JSON.parse(event.data);
278
+ const txNotification = this.parseTransactionNotification(data);
279
+ if (txNotification) {
280
+ yield txNotification;
281
+ }
282
+ }
283
+ catch (err) {
284
+ console.error("Failed to parse transaction notification:", err);
285
+ throw err;
313
286
  }
314
287
  }
315
- // Keep the last partial line in the buffer
316
- buffer = lines[lines.length - 1];
288
+ }
289
+ finally {
290
+ signal?.removeEventListener("abort", abortHandler);
291
+ eventSource.close();
317
292
  }
318
293
  }
319
294
  catch (error) {
@@ -321,12 +296,11 @@ export class RestArkProvider {
321
296
  break;
322
297
  }
323
298
  // ignore timeout errors, they're expected when the server is not sending anything for 5 min
324
- // these timeouts are set by builtin fetch function
325
299
  if (isFetchTimeoutError(error)) {
326
300
  console.debug("Timeout error ignored");
327
301
  continue;
328
302
  }
329
- console.error("Address subscription error:", error);
303
+ console.error("Transaction stream error:", error);
330
304
  throw error;
331
305
  }
332
306
  }
@@ -409,6 +383,10 @@ export class RestArkProvider {
409
383
  signature: data.treeSignature.signature,
410
384
  };
411
385
  }
386
+ // Skip heartbeat events
387
+ if (data.heartbeat) {
388
+ return null;
389
+ }
412
390
  console.warn("Unknown event type:", data);
413
391
  return null;
414
392
  }
@@ -435,6 +413,10 @@ export class RestArkProvider {
435
413
  },
436
414
  };
437
415
  }
416
+ // Skip heartbeat events
417
+ if (data.heartbeat) {
418
+ return null;
419
+ }
438
420
  console.warn("Unknown transaction notification type:", data);
439
421
  return null;
440
422
  }
@@ -1,4 +1,5 @@
1
1
  import { isFetchTimeoutError } from './ark.js';
2
+ import { eventSourceIterator } from './utils.js';
2
3
  export var IndexerTxType;
3
4
  (function (IndexerTxType) {
4
5
  IndexerTxType[IndexerTxType["INDEXER_TX_TYPE_UNSPECIFIED"] = 0] = "INDEXER_TX_TYPE_UNSPECIFIED";
@@ -27,7 +28,7 @@ export class RestIndexerProvider {
27
28
  this.serverUrl = serverUrl;
28
29
  }
29
30
  async getVtxoTree(batchOutpoint, opts) {
30
- let url = `${this.serverUrl}/v1/batch/${batchOutpoint.txid}/${batchOutpoint.vout}/tree`;
31
+ let url = `${this.serverUrl}/v1/indexer/batch/${batchOutpoint.txid}/${batchOutpoint.vout}/tree`;
31
32
  const params = new URLSearchParams();
32
33
  if (opts) {
33
34
  if (opts.pageIndex !== undefined)
@@ -55,7 +56,7 @@ export class RestIndexerProvider {
55
56
  return data;
56
57
  }
57
58
  async getVtxoTreeLeaves(batchOutpoint, opts) {
58
- let url = `${this.serverUrl}/v1/batch/${batchOutpoint.txid}/${batchOutpoint.vout}/tree/leaves`;
59
+ let url = `${this.serverUrl}/v1/indexer/batch/${batchOutpoint.txid}/${batchOutpoint.vout}/tree/leaves`;
59
60
  const params = new URLSearchParams();
60
61
  if (opts) {
61
62
  if (opts.pageIndex !== undefined)
@@ -77,7 +78,7 @@ export class RestIndexerProvider {
77
78
  return data;
78
79
  }
79
80
  async getBatchSweepTransactions(batchOutpoint) {
80
- const url = `${this.serverUrl}/v1/batch/${batchOutpoint.txid}/${batchOutpoint.vout}/sweepTxs`;
81
+ const url = `${this.serverUrl}/v1/indexer/batch/${batchOutpoint.txid}/${batchOutpoint.vout}/sweepTxs`;
81
82
  const res = await fetch(url);
82
83
  if (!res.ok) {
83
84
  throw new Error(`Failed to fetch batch sweep transactions: ${res.statusText}`);
@@ -89,7 +90,7 @@ export class RestIndexerProvider {
89
90
  return data;
90
91
  }
91
92
  async getCommitmentTx(txid) {
92
- const url = `${this.serverUrl}/v1/commitmentTx/${txid}`;
93
+ const url = `${this.serverUrl}/v1/indexer/commitmentTx/${txid}`;
93
94
  const res = await fetch(url);
94
95
  if (!res.ok) {
95
96
  throw new Error(`Failed to fetch commitment tx: ${res.statusText}`);
@@ -101,7 +102,7 @@ export class RestIndexerProvider {
101
102
  return data;
102
103
  }
103
104
  async getCommitmentTxConnectors(txid, opts) {
104
- let url = `${this.serverUrl}/v1/commitmentTx/${txid}/connectors`;
105
+ let url = `${this.serverUrl}/v1/indexer/commitmentTx/${txid}/connectors`;
105
106
  const params = new URLSearchParams();
106
107
  if (opts) {
107
108
  if (opts.pageIndex !== undefined)
@@ -129,7 +130,7 @@ export class RestIndexerProvider {
129
130
  return data;
130
131
  }
131
132
  async getCommitmentTxForfeitTxs(txid, opts) {
132
- let url = `${this.serverUrl}/v1/commitmentTx/${txid}/forfeitTxs`;
133
+ let url = `${this.serverUrl}/v1/indexer/commitmentTx/${txid}/forfeitTxs`;
133
134
  const params = new URLSearchParams();
134
135
  if (opts) {
135
136
  if (opts.pageIndex !== undefined)
@@ -151,47 +152,41 @@ export class RestIndexerProvider {
151
152
  return data;
152
153
  }
153
154
  async *getSubscription(subscriptionId, abortSignal) {
154
- const url = `${this.serverUrl}/v1/script/subscription/${subscriptionId}`;
155
- while (!abortSignal.aborted) {
155
+ const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
156
+ while (!abortSignal?.aborted) {
156
157
  try {
157
- const res = await fetch(url, {
158
- headers: {
159
- Accept: "application/json",
160
- },
161
- });
162
- if (!res.ok) {
163
- throw new Error(`Unexpected status ${res.status} when subscribing to address updates`);
164
- }
165
- if (!res.body) {
166
- throw new Error("Response body is null");
167
- }
168
- const reader = res.body.getReader();
169
- const decoder = new TextDecoder();
170
- let buffer = "";
171
- while (!abortSignal.aborted) {
172
- const { done, value } = await reader.read();
173
- if (done) {
174
- break;
175
- }
176
- buffer += decoder.decode(value, { stream: true });
177
- const lines = buffer.split("\n");
178
- for (let i = 0; i < lines.length - 1; i++) {
179
- const line = lines[i].trim();
180
- if (!line)
181
- continue;
182
- const data = JSON.parse(line);
183
- if ("result" in data) {
184
- yield {
185
- txid: data.result.txid,
186
- scripts: data.result.scripts || [],
187
- newVtxos: (data.result.newVtxos || []).map(convertVtxo),
188
- spentVtxos: (data.result.spentVtxos || []).map(convertVtxo),
189
- tx: data.result.tx,
190
- checkpointTxs: data.result.checkpointTxs,
191
- };
158
+ const eventSource = new EventSource(url);
159
+ // Set up abort handling
160
+ const abortHandler = () => {
161
+ eventSource.close();
162
+ };
163
+ abortSignal?.addEventListener("abort", abortHandler);
164
+ try {
165
+ for await (const event of eventSourceIterator(eventSource)) {
166
+ if (abortSignal?.aborted)
167
+ break;
168
+ try {
169
+ const data = JSON.parse(event.data);
170
+ if (data.event) {
171
+ yield {
172
+ txid: data.event.txid,
173
+ scripts: data.event.scripts || [],
174
+ newVtxos: (data.event.newVtxos || []).map(convertVtxo),
175
+ spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
176
+ tx: data.event.tx,
177
+ checkpointTxs: data.event.checkpointTxs,
178
+ };
179
+ }
180
+ }
181
+ catch (err) {
182
+ console.error("Failed to parse subscription event:", err);
183
+ throw err;
192
184
  }
193
185
  }
194
- buffer = lines[lines.length - 1];
186
+ }
187
+ finally {
188
+ abortSignal?.removeEventListener("abort", abortHandler);
189
+ eventSource.close();
195
190
  }
196
191
  }
197
192
  catch (error) {
@@ -199,7 +194,6 @@ export class RestIndexerProvider {
199
194
  break;
200
195
  }
201
196
  // ignore timeout errors, they're expected when the server is not sending anything for 5 min
202
- // these timeouts are set by builtin fetch function
203
197
  if (isFetchTimeoutError(error)) {
204
198
  console.debug("Timeout error ignored");
205
199
  continue;
@@ -210,7 +204,7 @@ export class RestIndexerProvider {
210
204
  }
211
205
  }
212
206
  async getVirtualTxs(txids, opts) {
213
- let url = `${this.serverUrl}/v1/virtualTx/${txids.join(",")}`;
207
+ let url = `${this.serverUrl}/v1/indexer/virtualTx/${txids.join(",")}`;
214
208
  const params = new URLSearchParams();
215
209
  if (opts) {
216
210
  if (opts.pageIndex !== undefined)
@@ -232,7 +226,7 @@ export class RestIndexerProvider {
232
226
  return data;
233
227
  }
234
228
  async getVtxoChain(vtxoOutpoint, opts) {
235
- let url = `${this.serverUrl}/v1/vtxo/${vtxoOutpoint.txid}/${vtxoOutpoint.vout}/chain`;
229
+ let url = `${this.serverUrl}/v1/indexer/vtxo/${vtxoOutpoint.txid}/${vtxoOutpoint.vout}/chain`;
236
230
  const params = new URLSearchParams();
237
231
  if (opts) {
238
232
  if (opts.pageIndex !== undefined)
@@ -261,7 +255,7 @@ export class RestIndexerProvider {
261
255
  if (!opts?.scripts && !opts?.outpoints) {
262
256
  throw new Error("Either scripts or outpoints must be provided");
263
257
  }
264
- let url = `${this.serverUrl}/v1/vtxos`;
258
+ let url = `${this.serverUrl}/v1/indexer/vtxos`;
265
259
  const params = new URLSearchParams();
266
260
  // Handle scripts with multi collection format
267
261
  if (opts?.scripts && opts.scripts.length > 0) {
@@ -304,7 +298,7 @@ export class RestIndexerProvider {
304
298
  };
305
299
  }
306
300
  async subscribeForScripts(scripts, subscriptionId) {
307
- const url = `${this.serverUrl}/v1/script/subscribe`;
301
+ const url = `${this.serverUrl}/v1/indexer/script/subscribe`;
308
302
  const res = await fetch(url, {
309
303
  headers: {
310
304
  "Content-Type": "application/json",
@@ -322,7 +316,7 @@ export class RestIndexerProvider {
322
316
  return data.subscriptionId;
323
317
  }
324
318
  async unsubscribeForScripts(subscriptionId, scripts) {
325
- const url = `${this.serverUrl}/v1/script/unsubscribe`;
319
+ const url = `${this.serverUrl}/v1/indexer/script/unsubscribe`;
326
320
  const res = await fetch(url, {
327
321
  headers: {
328
322
  "Content-Type": "application/json",
@@ -438,7 +432,7 @@ var Response;
438
432
  return (typeof data === "object" &&
439
433
  isOutpoint(data.outpoint) &&
440
434
  typeof data.createdAt === "string" &&
441
- typeof data.expiresAt === "string" &&
435
+ (data.expiresAt === null || typeof data.expiresAt === "string") &&
442
436
  typeof data.amount === "string" &&
443
437
  typeof data.script === "string" &&
444
438
  typeof data.isPreconfirmed === "boolean" &&
@@ -0,0 +1,57 @@
1
+ export async function* eventSourceIterator(eventSource) {
2
+ const messageQueue = [];
3
+ const errorQueue = [];
4
+ let messageResolve = null;
5
+ let errorResolve = null;
6
+ const messageHandler = (event) => {
7
+ if (messageResolve) {
8
+ messageResolve(event);
9
+ messageResolve = null;
10
+ }
11
+ else {
12
+ messageQueue.push(event);
13
+ }
14
+ };
15
+ const errorHandler = () => {
16
+ const error = new Error("EventSource error");
17
+ if (errorResolve) {
18
+ errorResolve(error);
19
+ errorResolve = null;
20
+ }
21
+ else {
22
+ errorQueue.push(error);
23
+ }
24
+ };
25
+ eventSource.addEventListener("message", messageHandler);
26
+ eventSource.addEventListener("error", errorHandler);
27
+ try {
28
+ while (true) {
29
+ // if we have queued messages, yield the first one, remove it from the queue
30
+ if (messageQueue.length > 0) {
31
+ yield messageQueue.shift();
32
+ continue;
33
+ }
34
+ // if we have queued errors, throw the first one, remove it from the queue
35
+ if (errorQueue.length > 0) {
36
+ const error = errorQueue.shift();
37
+ throw error;
38
+ }
39
+ // wait for the next message or error
40
+ const result = await new Promise((resolve, reject) => {
41
+ messageResolve = resolve;
42
+ errorResolve = reject;
43
+ }).finally(() => {
44
+ messageResolve = null;
45
+ errorResolve = null;
46
+ });
47
+ if (result) {
48
+ yield result;
49
+ }
50
+ }
51
+ }
52
+ finally {
53
+ // clean up
54
+ eventSource.removeEventListener("message", messageHandler);
55
+ eventSource.removeEventListener("error", errorHandler);
56
+ }
57
+ }
@@ -1,3 +1,32 @@
1
+ import { hex } from "@scure/base";
2
+ import { TaprootControlBlock } from "@scure/btc-signer";
3
+ // Utility functions for (de)serializing complex structures
4
+ const toHex = (b) => (b ? hex.encode(b) : undefined);
5
+ const fromHex = (h) => h ? hex.decode(h) : undefined;
6
+ const serializeTapLeaf = ([cb, s]) => ({
7
+ cb: TaprootControlBlock.encode(cb) &&
8
+ hex.encode(TaprootControlBlock.encode(cb)),
9
+ s: hex.encode(s),
10
+ });
11
+ const serializeVtxo = (v) => ({
12
+ ...v,
13
+ tapTree: toHex(v.tapTree),
14
+ forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
15
+ intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
16
+ extraWitness: v.extraWitness?.map((w) => toHex(w)),
17
+ });
18
+ const deserializeTapLeaf = (t) => {
19
+ const cb = TaprootControlBlock.decode(fromHex(t.cb));
20
+ const s = fromHex(t.s);
21
+ return [cb, s];
22
+ };
23
+ const deserializeVtxo = (o) => ({
24
+ ...o,
25
+ tapTree: fromHex(o.tapTree),
26
+ forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
27
+ intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
28
+ extraWitness: o.extraWitness?.map((w) => fromHex(w)),
29
+ });
1
30
  export class WalletRepositoryImpl {
2
31
  constructor(storage) {
3
32
  this.storage = storage;
@@ -19,7 +48,8 @@ export class WalletRepositoryImpl {
19
48
  return [];
20
49
  }
21
50
  try {
22
- const vtxos = JSON.parse(stored);
51
+ const parsed = JSON.parse(stored);
52
+ const vtxos = parsed.map(deserializeVtxo);
23
53
  this.cache.vtxos.set(address, vtxos);
24
54
  return vtxos.slice();
25
55
  }
@@ -39,7 +69,7 @@ export class WalletRepositoryImpl {
39
69
  vtxos.push(vtxo);
40
70
  }
41
71
  this.cache.vtxos.set(address, vtxos);
42
- await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos));
72
+ await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
43
73
  }
44
74
  async saveVtxos(address, vtxos) {
45
75
  const storedVtxos = await this.getVtxos(address);
@@ -53,14 +83,14 @@ export class WalletRepositoryImpl {
53
83
  }
54
84
  }
55
85
  this.cache.vtxos.set(address, storedVtxos);
56
- await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos));
86
+ await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos.map(serializeVtxo)));
57
87
  }
58
88
  async removeVtxo(address, vtxoId) {
59
89
  const vtxos = await this.getVtxos(address);
60
90
  const [txid, vout] = vtxoId.split(":");
61
91
  const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout)));
62
92
  this.cache.vtxos.set(address, filtered);
63
- await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered));
93
+ await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered.map(serializeVtxo)));
64
94
  }
65
95
  async clearVtxos(address) {
66
96
  this.cache.vtxos.set(address, []);
@@ -260,7 +260,7 @@ export var CSVMultisigTapscript;
260
260
  throw new Error(`Invalid script: too short (expected at least 3)`);
261
261
  }
262
262
  const sequence = asm[0];
263
- if (typeof sequence === "string" || typeof sequence === "number") {
263
+ if (typeof sequence === "string") {
264
264
  throw new Error("Invalid script: expected sequence number");
265
265
  }
266
266
  if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
@@ -274,7 +274,13 @@ export var CSVMultisigTapscript;
274
274
  catch (error) {
275
275
  throw new Error(`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`);
276
276
  }
277
- const sequenceNum = Number(MinimalScriptNum.decode(sequence));
277
+ let sequenceNum;
278
+ if (typeof sequence === "number") {
279
+ sequenceNum = sequence;
280
+ }
281
+ else {
282
+ sequenceNum = Number(MinimalScriptNum.decode(sequence));
283
+ }
278
284
  const decodedTimelock = bip68.decode(sequenceNum);
279
285
  const timelock = decodedTimelock.blocks !== undefined
280
286
  ? { type: "blocks", value: BigInt(decodedTimelock.blocks) }
@@ -53,7 +53,7 @@ export class TreeSignerSession {
53
53
  if (!this.myNonces)
54
54
  throw new Error("nonces not generated");
55
55
  const sigs = new Map();
56
- for (const g of this.graph) {
56
+ for (const g of this.graph.iterator()) {
57
57
  const sig = this.signPartial(g);
58
58
  sigs.set(g.txid, sig);
59
59
  }
@@ -64,7 +64,7 @@ export class TreeSignerSession {
64
64
  throw ErrMissingVtxoGraph;
65
65
  const myNonces = new Map();
66
66
  const publicKey = secp256k1.getPublicKey(this.secretKey);
67
- for (const g of this.graph) {
67
+ for (const g of this.graph.iterator()) {
68
68
  const nonces = musig2.generateNonces(publicKey);
69
69
  myNonces.set(g.txid, nonces);
70
70
  }
@@ -106,7 +106,7 @@ TreeSignerSession.NOT_INITIALIZED = new Error("session not initialized, call ini
106
106
  // Helper function to validate tree signatures
107
107
  export async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, vtxoTree) {
108
108
  // Iterate through each level of the tree
109
- for (const g of vtxoTree) {
109
+ for (const g of vtxoTree.iterator()) {
110
110
  // Parse the transaction
111
111
  const input = g.root.getInput(0);
112
112
  // Check if input has signature
@@ -154,11 +154,11 @@ export class TxTree {
154
154
  }
155
155
  throw new Error(`tx not found: ${txid}`);
156
156
  }
157
- *[Symbol.iterator]() {
158
- yield this;
157
+ *iterator() {
159
158
  for (const child of this.children.values()) {
160
- yield* child;
159
+ yield* child.iterator();
161
160
  }
161
+ yield this;
162
162
  }
163
163
  }
164
164
  // Helper function to check if a chunk has a specific child
@@ -75,7 +75,7 @@ export function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
75
75
  // validate the graph structure
76
76
  graph.validate();
77
77
  // iterates over all the nodes of the graph to verify that cosigners public keys are corresponding to the parent output
78
- for (const g of graph) {
78
+ for (const g of graph.iterator()) {
79
79
  for (const [childIndex, child] of g.children) {
80
80
  const parentOutput = g.root.getOutput(childIndex);
81
81
  if (!parentOutput?.script) {
@@ -14,7 +14,7 @@ export async function setupServiceWorker(path) {
14
14
  // register service worker
15
15
  const registration = await navigator.serviceWorker.register(path);
16
16
  // force update to ensure the service worker is active
17
- registration.update();
17
+ await registration.update();
18
18
  const serviceWorker = registration.active || registration.waiting || registration.installing;
19
19
  if (!serviceWorker) {
20
20
  throw new Error("Failed to get service worker instance");
@@ -36,11 +36,11 @@ export async function setupServiceWorker(path) {
36
36
  reject(new Error("Service worker activation timed out"));
37
37
  }, 10000);
38
38
  const cleanup = () => {
39
- serviceWorker.removeEventListener("activate", onActivate);
40
- serviceWorker.removeEventListener("error", onError);
39
+ navigator.serviceWorker.removeEventListener("activate", onActivate);
40
+ navigator.serviceWorker.removeEventListener("error", onError);
41
41
  clearTimeout(timeout);
42
42
  };
43
- serviceWorker.addEventListener("activate", onActivate);
44
- serviceWorker.addEventListener("error", onError);
43
+ navigator.serviceWorker.addEventListener("activate", onActivate);
44
+ navigator.serviceWorker.addEventListener("error", onError);
45
45
  });
46
46
  }