@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.
- package/dist/cjs/identity/singleKey.js +3 -0
- package/dist/cjs/providers/ark.js +53 -71
- package/dist/cjs/providers/indexer.js +45 -51
- package/dist/cjs/providers/utils.js +60 -0
- package/dist/cjs/repositories/walletRepository.js +34 -4
- package/dist/cjs/script/tapscript.js +8 -2
- package/dist/cjs/tree/signingSession.js +3 -3
- package/dist/cjs/tree/txTree.js +3 -3
- package/dist/cjs/tree/validation.js +1 -1
- package/dist/cjs/wallet/serviceWorker/utils.js +5 -5
- package/dist/cjs/wallet/wallet.js +5 -6
- package/dist/esm/identity/singleKey.js +4 -1
- package/dist/esm/providers/ark.js +53 -71
- package/dist/esm/providers/indexer.js +45 -51
- package/dist/esm/providers/utils.js +57 -0
- package/dist/esm/repositories/walletRepository.js +34 -4
- package/dist/esm/script/tapscript.js +8 -2
- package/dist/esm/tree/signingSession.js +3 -3
- package/dist/esm/tree/txTree.js +3 -3
- package/dist/esm/tree/validation.js +1 -1
- package/dist/esm/wallet/serviceWorker/utils.js +5 -5
- package/dist/esm/wallet/wallet.js +5 -6
- package/dist/types/identity/index.d.ts +3 -2
- package/dist/types/identity/singleKey.d.ts +1 -0
- package/dist/types/providers/ark.d.ts +1 -0
- package/dist/types/providers/utils.d.ts +1 -0
- package/dist/types/tree/txTree.d.ts +2 -2
- package/package.json +2 -1
|
@@ -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
|
|
139
|
-
|
|
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
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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(
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
yield
|
|
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
|
-
|
|
259
|
-
|
|
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
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
316
|
-
|
|
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("
|
|
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
|
|
155
|
+
const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
|
|
156
|
+
while (!abortSignal?.aborted) {
|
|
156
157
|
try {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
-
|
|
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
|
package/dist/esm/tree/txTree.js
CHANGED
|
@@ -154,11 +154,11 @@ export class TxTree {
|
|
|
154
154
|
}
|
|
155
155
|
throw new Error(`tx not found: ${txid}`);
|
|
156
156
|
}
|
|
157
|
-
*
|
|
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
|
}
|