@arkade-os/sdk 0.3.0-alpha.8 → 0.3.1-alpha.1
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/README.md +64 -14
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +5 -2
- package/dist/cjs/identity/singleKey.js +5 -4
- package/dist/cjs/index.js +6 -3
- package/dist/cjs/{bip322 → intent}/index.js +37 -55
- package/dist/cjs/providers/ark.js +62 -23
- package/dist/cjs/providers/expoArk.js +15 -170
- package/dist/cjs/providers/expoIndexer.js +22 -111
- package/dist/cjs/providers/expoUtils.js +124 -0
- package/dist/cjs/script/base.js +1 -2
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +7 -8
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +104 -12
- package/dist/cjs/utils/unknownFields.js +5 -5
- package/dist/cjs/wallet/onchain.js +4 -5
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +4 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +23 -18
- package/dist/cjs/wallet/unroll.js +6 -7
- package/dist/cjs/wallet/vtxo-manager.js +381 -0
- package/dist/cjs/wallet/wallet.js +63 -94
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +4 -1
- package/dist/esm/identity/singleKey.js +7 -6
- package/dist/esm/index.js +7 -6
- package/dist/esm/{bip322 → intent}/index.js +31 -48
- package/dist/esm/providers/ark.js +62 -23
- package/dist/esm/providers/expoArk.js +15 -137
- package/dist/esm/providers/expoIndexer.js +22 -78
- package/dist/esm/providers/expoUtils.js +87 -0
- package/dist/esm/script/base.js +1 -2
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +8 -9
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +95 -4
- package/dist/esm/utils/unknownFields.js +1 -1
- package/dist/esm/wallet/onchain.js +1 -2
- package/dist/esm/wallet/serviceWorker/utils.js +1 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -9
- package/dist/esm/wallet/serviceWorker/worker.js +23 -18
- package/dist/esm/wallet/unroll.js +2 -3
- package/dist/esm/wallet/vtxo-manager.js +372 -0
- package/dist/esm/wallet/wallet.js +56 -87
- package/dist/types/arknote/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +2 -2
- package/dist/types/identity/index.d.ts +1 -1
- package/dist/types/identity/singleKey.d.ts +1 -1
- package/dist/types/index.d.ts +6 -5
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/providers/ark.d.ts +55 -21
- package/dist/types/providers/expoIndexer.d.ts +2 -10
- package/dist/types/providers/expoUtils.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +1 -9
- package/dist/types/script/base.d.ts +3 -2
- package/dist/types/tree/signingSession.d.ts +10 -10
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +13 -3
- package/dist/types/utils/unknownFields.d.ts +2 -2
- package/dist/types/wallet/index.d.ts +6 -4
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
- package/dist/types/wallet/serviceWorker/worker.d.ts +3 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/vtxo-manager.d.ts +207 -0
- package/dist/types/wallet/wallet.d.ts +7 -3
- package/package.json +1 -2
- package/dist/cjs/bip322/errors.js +0 -13
- package/dist/esm/bip322/errors.js +0 -9
- package/dist/types/bip322/errors.d.ts +0 -6
- package/dist/types/bip322/index.d.ts +0 -57
|
@@ -32,25 +32,36 @@ export class RestArkProvider {
|
|
|
32
32
|
}
|
|
33
33
|
const fromServer = await response.json();
|
|
34
34
|
return {
|
|
35
|
-
...fromServer,
|
|
36
|
-
vtxoTreeExpiry: BigInt(fromServer.vtxoTreeExpiry ?? 0),
|
|
37
|
-
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
38
|
-
roundInterval: BigInt(fromServer.roundInterval ?? 0),
|
|
39
|
-
dust: BigInt(fromServer.dust ?? 0),
|
|
40
|
-
utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
|
|
41
|
-
utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
|
|
42
|
-
vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
|
|
43
|
-
vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
|
|
44
35
|
boardingExitDelay: BigInt(fromServer.boardingExitDelay ?? 0),
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
checkpointTapscript: fromServer.checkpointTapscript ?? "",
|
|
37
|
+
deprecatedSigners: fromServer.deprecatedSigners?.map((signer) => ({
|
|
38
|
+
cutoffDate: BigInt(signer.cutoffDate ?? 0),
|
|
39
|
+
pubkey: signer.pubkey ?? "",
|
|
40
|
+
})) ?? [],
|
|
41
|
+
digest: fromServer.digest ?? "",
|
|
42
|
+
dust: BigInt(fromServer.dust ?? 0),
|
|
43
|
+
fees: fromServer.fees,
|
|
44
|
+
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
45
|
+
forfeitPubkey: fromServer.forfeitPubkey ?? "",
|
|
46
|
+
network: fromServer.network ?? "",
|
|
47
|
+
scheduledSession: "scheduledSession" in fromServer &&
|
|
48
|
+
fromServer.scheduledSession != null
|
|
47
49
|
? {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
duration: BigInt(fromServer.scheduledSession.duration ?? 0),
|
|
51
|
+
nextStartTime: BigInt(fromServer.scheduledSession.nextStartTime ?? 0),
|
|
52
|
+
nextEndTime: BigInt(fromServer.scheduledSession.nextEndTime ?? 0),
|
|
53
|
+
period: BigInt(fromServer.scheduledSession.period ?? 0),
|
|
52
54
|
}
|
|
53
55
|
: undefined,
|
|
56
|
+
serviceStatus: fromServer.serviceStatus ?? {},
|
|
57
|
+
sessionDuration: BigInt(fromServer.sessionDuration ?? 0),
|
|
58
|
+
signerPubkey: fromServer.signerPubkey ?? "",
|
|
59
|
+
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
60
|
+
utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
|
|
61
|
+
utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
|
|
62
|
+
version: fromServer.version ?? "",
|
|
63
|
+
vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
|
|
64
|
+
vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
|
|
54
65
|
};
|
|
55
66
|
}
|
|
56
67
|
async submitTx(signedArkTx, checkpointTxs) {
|
|
@@ -61,8 +72,8 @@ export class RestArkProvider {
|
|
|
61
72
|
"Content-Type": "application/json",
|
|
62
73
|
},
|
|
63
74
|
body: JSON.stringify({
|
|
64
|
-
signedArkTx
|
|
65
|
-
checkpointTxs
|
|
75
|
+
signedArkTx,
|
|
76
|
+
checkpointTxs,
|
|
66
77
|
}),
|
|
67
78
|
});
|
|
68
79
|
if (!response.ok) {
|
|
@@ -111,7 +122,7 @@ export class RestArkProvider {
|
|
|
111
122
|
},
|
|
112
123
|
body: JSON.stringify({
|
|
113
124
|
intent: {
|
|
114
|
-
|
|
125
|
+
proof: intent.proof,
|
|
115
126
|
message: intent.message,
|
|
116
127
|
},
|
|
117
128
|
}),
|
|
@@ -132,7 +143,7 @@ export class RestArkProvider {
|
|
|
132
143
|
},
|
|
133
144
|
body: JSON.stringify({
|
|
134
145
|
proof: {
|
|
135
|
-
|
|
146
|
+
proof: intent.proof,
|
|
136
147
|
message: intent.message,
|
|
137
148
|
},
|
|
138
149
|
}),
|
|
@@ -305,6 +316,31 @@ export class RestArkProvider {
|
|
|
305
316
|
}
|
|
306
317
|
}
|
|
307
318
|
}
|
|
319
|
+
async getPendingTxs(intent) {
|
|
320
|
+
const url = `${this.serverUrl}/v1/tx/pending`;
|
|
321
|
+
const response = await fetch(url, {
|
|
322
|
+
method: "POST",
|
|
323
|
+
headers: {
|
|
324
|
+
"Content-Type": "application/json",
|
|
325
|
+
},
|
|
326
|
+
body: JSON.stringify({ intent }),
|
|
327
|
+
});
|
|
328
|
+
if (!response.ok) {
|
|
329
|
+
const errorText = await response.text();
|
|
330
|
+
try {
|
|
331
|
+
const grpcError = JSON.parse(errorText);
|
|
332
|
+
// gRPC errors usually have a message and code field
|
|
333
|
+
throw new Error(`Failed to get pending transactions: ${grpcError.message || grpcError.error || errorText}`);
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
335
|
+
}
|
|
336
|
+
catch (_) {
|
|
337
|
+
// If JSON parse fails, use the raw error text
|
|
338
|
+
throw new Error(`Failed to get pending transactions: ${errorText}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const data = await response.json();
|
|
342
|
+
return data.pendingTxs;
|
|
343
|
+
}
|
|
308
344
|
parseSettlementEvent(data) {
|
|
309
345
|
// Check for BatchStarted event
|
|
310
346
|
if (data.batchStarted) {
|
|
@@ -383,6 +419,10 @@ export class RestArkProvider {
|
|
|
383
419
|
signature: data.treeSignature.signature,
|
|
384
420
|
};
|
|
385
421
|
}
|
|
422
|
+
// TODO: Handle TreeNoncesEvent when implemented server-side
|
|
423
|
+
if (data.treeNonces) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
386
426
|
// Skip heartbeat events
|
|
387
427
|
if (data.heartbeat) {
|
|
388
428
|
return null;
|
|
@@ -426,17 +466,16 @@ function encodeMusig2Nonces(nonces) {
|
|
|
426
466
|
for (const [txid, nonce] of nonces) {
|
|
427
467
|
noncesObject[txid] = hex.encode(nonce.pubNonce);
|
|
428
468
|
}
|
|
429
|
-
return
|
|
469
|
+
return noncesObject;
|
|
430
470
|
}
|
|
431
471
|
function encodeMusig2Signatures(signatures) {
|
|
432
472
|
const sigObject = {};
|
|
433
473
|
for (const [txid, sig] of signatures) {
|
|
434
474
|
sigObject[txid] = hex.encode(sig.encode());
|
|
435
475
|
}
|
|
436
|
-
return
|
|
476
|
+
return sigObject;
|
|
437
477
|
}
|
|
438
|
-
function decodeMusig2Nonces(
|
|
439
|
-
const noncesObject = JSON.parse(str);
|
|
478
|
+
function decodeMusig2Nonces(noncesObject) {
|
|
440
479
|
return new Map(Object.entries(noncesObject).map(([txid, nonce]) => {
|
|
441
480
|
if (typeof nonce !== "string") {
|
|
442
481
|
throw new Error("invalid nonce");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RestArkProvider, isFetchTimeoutError, } from './ark.js';
|
|
2
|
+
import { getExpoFetch, sseStreamIterator } from './expoUtils.js';
|
|
2
3
|
/**
|
|
3
4
|
* Expo-compatible Ark provider implementation using expo/fetch for SSE support.
|
|
4
5
|
* This provider works specifically in React Native/Expo environments where
|
|
@@ -17,87 +18,23 @@ export class ExpoArkProvider extends RestArkProvider {
|
|
|
17
18
|
super(serverUrl);
|
|
18
19
|
}
|
|
19
20
|
async *getEventStream(signal, topics) {
|
|
20
|
-
|
|
21
|
-
let expoFetch = fetch; // Default to standard fetch
|
|
22
|
-
try {
|
|
23
|
-
const expoFetchModule = await import("expo/fetch");
|
|
24
|
-
// expo/fetch returns a compatible fetch function but with different types
|
|
25
|
-
expoFetch = expoFetchModule.fetch;
|
|
26
|
-
console.debug("Using expo/fetch for SSE");
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
// Fall back to standard fetch if expo/fetch is not available
|
|
30
|
-
console.warn("Using standard fetch instead of expo/fetch. " +
|
|
31
|
-
"Streaming may not be fully supported in some environments.", error);
|
|
32
|
-
}
|
|
21
|
+
const expoFetch = await getExpoFetch();
|
|
33
22
|
const url = `${this.serverUrl}/v1/batch/events`;
|
|
34
23
|
const queryParams = topics.length > 0
|
|
35
24
|
? `?${topics.map((topic) => `topics=${encodeURIComponent(topic)}`).join("&")}`
|
|
36
25
|
: "";
|
|
37
26
|
while (!signal?.aborted) {
|
|
38
|
-
// Create a new AbortController for this specific fetch attempt
|
|
39
|
-
// to prevent accumulating listeners on the parent signal
|
|
40
|
-
const fetchController = new AbortController();
|
|
41
|
-
const cleanup = () => fetchController.abort();
|
|
42
|
-
signal?.addEventListener("abort", cleanup, { once: true });
|
|
43
27
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
throw new Error(`Unexpected status ${response.status} when fetching event stream`);
|
|
52
|
-
}
|
|
53
|
-
if (!response.body) {
|
|
54
|
-
throw new Error("Response body is null");
|
|
55
|
-
}
|
|
56
|
-
const reader = response.body.getReader();
|
|
57
|
-
const decoder = new TextDecoder();
|
|
58
|
-
let buffer = "";
|
|
59
|
-
while (!signal?.aborted) {
|
|
60
|
-
const { done, value } = await reader.read();
|
|
61
|
-
if (done) {
|
|
62
|
-
break;
|
|
28
|
+
yield* sseStreamIterator(url + queryParams, signal, expoFetch, {}, (data) => {
|
|
29
|
+
// Handle different response structures
|
|
30
|
+
// v8 mesh API might wrap in {result: ...} or send directly
|
|
31
|
+
const eventData = data.result || data;
|
|
32
|
+
// Skip heartbeat messages
|
|
33
|
+
if (eventData.heartbeat !== undefined) {
|
|
34
|
+
return null;
|
|
63
35
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const lines = buffer.split("\n");
|
|
67
|
-
// Process all complete lines
|
|
68
|
-
for (let i = 0; i < lines.length - 1; i++) {
|
|
69
|
-
const line = lines[i].trim();
|
|
70
|
-
if (!line)
|
|
71
|
-
continue;
|
|
72
|
-
try {
|
|
73
|
-
// Parse SSE format: "data: {json}"
|
|
74
|
-
if (line.startsWith("data:")) {
|
|
75
|
-
const jsonStr = line.substring(5).trim();
|
|
76
|
-
if (!jsonStr)
|
|
77
|
-
continue;
|
|
78
|
-
const data = JSON.parse(jsonStr);
|
|
79
|
-
// Handle different response structures
|
|
80
|
-
// v8 mesh API might wrap in {result: ...} or send directly
|
|
81
|
-
const eventData = data.result || data;
|
|
82
|
-
// Skip heartbeat messages
|
|
83
|
-
if (eventData.heartbeat !== undefined) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const event = this.parseSettlementEvent(eventData);
|
|
87
|
-
if (event) {
|
|
88
|
-
yield event;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
console.error("Failed to parse event:", line);
|
|
94
|
-
console.error("Parse error:", err);
|
|
95
|
-
throw err;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// Keep the last partial line in the buffer
|
|
99
|
-
buffer = lines[lines.length - 1];
|
|
100
|
-
}
|
|
36
|
+
return this.parseSettlementEvent(eventData);
|
|
37
|
+
});
|
|
101
38
|
}
|
|
102
39
|
catch (error) {
|
|
103
40
|
if (error instanceof Error && error.name === "AbortError") {
|
|
@@ -112,71 +49,16 @@ export class ExpoArkProvider extends RestArkProvider {
|
|
|
112
49
|
console.error("Event stream error:", error);
|
|
113
50
|
throw error;
|
|
114
51
|
}
|
|
115
|
-
finally {
|
|
116
|
-
// Clean up the abort listener
|
|
117
|
-
signal?.removeEventListener("abort", cleanup);
|
|
118
|
-
}
|
|
119
52
|
}
|
|
120
53
|
}
|
|
121
54
|
async *getTransactionsStream(signal) {
|
|
122
|
-
|
|
123
|
-
let expoFetch = fetch; // Default to standard fetch
|
|
124
|
-
try {
|
|
125
|
-
const expoFetchModule = await import("expo/fetch");
|
|
126
|
-
// expo/fetch returns a compatible fetch function but with different types
|
|
127
|
-
expoFetch = expoFetchModule.fetch;
|
|
128
|
-
console.debug("Using expo/fetch for transaction stream");
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
// Fall back to standard fetch if expo/fetch is not available
|
|
132
|
-
console.warn("Using standard fetch instead of expo/fetch. " +
|
|
133
|
-
"Streaming may not be fully supported in some environments.", error);
|
|
134
|
-
}
|
|
55
|
+
const expoFetch = await getExpoFetch();
|
|
135
56
|
const url = `${this.serverUrl}/v1/txs`;
|
|
136
57
|
while (!signal?.aborted) {
|
|
137
|
-
// Create a new AbortController for this specific fetch attempt
|
|
138
|
-
// to prevent accumulating listeners on the parent signal
|
|
139
|
-
const fetchController = new AbortController();
|
|
140
|
-
const cleanup = () => fetchController.abort();
|
|
141
|
-
signal?.addEventListener("abort", cleanup, { once: true });
|
|
142
58
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Accept: "text/event-stream",
|
|
146
|
-
},
|
|
147
|
-
signal: fetchController.signal,
|
|
59
|
+
yield* sseStreamIterator(url, signal, expoFetch, {}, (data) => {
|
|
60
|
+
return this.parseTransactionNotification(data.result);
|
|
148
61
|
});
|
|
149
|
-
if (!response.ok) {
|
|
150
|
-
throw new Error(`Unexpected status ${response.status} when fetching transaction stream`);
|
|
151
|
-
}
|
|
152
|
-
if (!response.body) {
|
|
153
|
-
throw new Error("Response body is null");
|
|
154
|
-
}
|
|
155
|
-
const reader = response.body.getReader();
|
|
156
|
-
const decoder = new TextDecoder();
|
|
157
|
-
let buffer = "";
|
|
158
|
-
while (!signal?.aborted) {
|
|
159
|
-
const { done, value } = await reader.read();
|
|
160
|
-
if (done) {
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
// Append new data to buffer and split by newlines
|
|
164
|
-
buffer += decoder.decode(value, { stream: true });
|
|
165
|
-
const lines = buffer.split("\n");
|
|
166
|
-
// Process all complete lines
|
|
167
|
-
for (let i = 0; i < lines.length - 1; i++) {
|
|
168
|
-
const line = lines[i].trim();
|
|
169
|
-
if (!line)
|
|
170
|
-
continue;
|
|
171
|
-
const data = JSON.parse(line);
|
|
172
|
-
const txNotification = this.parseTransactionNotification(data.result);
|
|
173
|
-
if (txNotification) {
|
|
174
|
-
yield txNotification;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Keep the last partial line in the buffer
|
|
178
|
-
buffer = lines[lines.length - 1];
|
|
179
|
-
}
|
|
180
62
|
}
|
|
181
63
|
catch (error) {
|
|
182
64
|
if (error instanceof Error && error.name === "AbortError") {
|
|
@@ -188,13 +70,9 @@ export class ExpoArkProvider extends RestArkProvider {
|
|
|
188
70
|
console.debug("Timeout error ignored");
|
|
189
71
|
continue;
|
|
190
72
|
}
|
|
191
|
-
console.error("
|
|
73
|
+
console.error("Transaction stream error:", error);
|
|
192
74
|
throw error;
|
|
193
75
|
}
|
|
194
|
-
finally {
|
|
195
|
-
// Clean up the abort listener
|
|
196
|
-
signal?.removeEventListener("abort", cleanup);
|
|
197
|
-
}
|
|
198
76
|
}
|
|
199
77
|
}
|
|
200
78
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RestIndexerProvider } from './indexer.js';
|
|
2
2
|
import { isFetchTimeoutError } from './ark.js';
|
|
3
|
+
import { getExpoFetch, sseStreamIterator } from './expoUtils.js';
|
|
3
4
|
// Helper function to convert Vtxo to VirtualCoin (same as in indexer.ts)
|
|
4
5
|
function convertVtxo(vtxo) {
|
|
5
6
|
return {
|
|
@@ -49,95 +50,38 @@ export class ExpoIndexerProvider extends RestIndexerProvider {
|
|
|
49
50
|
// Detect if we're running in React Native/Expo environment
|
|
50
51
|
const isReactNative = typeof navigator !== "undefined" &&
|
|
51
52
|
navigator.product === "ReactNative";
|
|
52
|
-
|
|
53
|
-
let expoFetch = fetch; // Default to standard fetch
|
|
54
|
-
try {
|
|
55
|
-
const expoFetchModule = await import("expo/fetch");
|
|
56
|
-
// expo/fetch returns a compatible fetch function but with different types
|
|
57
|
-
expoFetch = expoFetchModule.fetch;
|
|
58
|
-
console.debug("Using expo/fetch for indexer subscription");
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
53
|
+
const expoFetch = await getExpoFetch().catch((error) => {
|
|
61
54
|
// In React Native/Expo, expo/fetch is required for proper streaming support
|
|
62
55
|
if (isReactNative) {
|
|
63
56
|
throw new Error("expo/fetch is unavailable in React Native environment. " +
|
|
64
57
|
"Please ensure expo/fetch is installed and properly configured. " +
|
|
65
58
|
"Streaming support may not work with standard fetch in React Native.");
|
|
66
59
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"Streaming may not be fully supported in some environments.", error);
|
|
70
|
-
}
|
|
60
|
+
throw error;
|
|
61
|
+
});
|
|
71
62
|
const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
|
|
72
63
|
while (!abortSignal.aborted) {
|
|
73
64
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
signal: abortSignal,
|
|
80
|
-
});
|
|
81
|
-
if (!res.ok) {
|
|
82
|
-
throw new Error(`Unexpected status ${res.status} when subscribing to address updates`);
|
|
83
|
-
}
|
|
84
|
-
// Check if response is the expected content type
|
|
85
|
-
const contentType = res.headers.get("content-type");
|
|
86
|
-
if (contentType &&
|
|
87
|
-
!contentType.includes("text/event-stream") &&
|
|
88
|
-
!contentType.includes("application/json")) {
|
|
89
|
-
throw new Error(`Unexpected content-type: ${contentType}. Expected text/event-stream or application/json`);
|
|
90
|
-
}
|
|
91
|
-
if (!res.body) {
|
|
92
|
-
throw new Error("Response body is null");
|
|
93
|
-
}
|
|
94
|
-
const reader = res.body.getReader();
|
|
95
|
-
const decoder = new TextDecoder();
|
|
96
|
-
let buffer = "";
|
|
97
|
-
while (!abortSignal.aborted) {
|
|
98
|
-
const { done, value } = await reader.read();
|
|
99
|
-
if (done) {
|
|
100
|
-
break;
|
|
65
|
+
yield* sseStreamIterator(url, abortSignal, expoFetch, { "Content-Type": "application/json" }, (data) => {
|
|
66
|
+
// Handle new v8 proto format with heartbeat or event
|
|
67
|
+
if (data.heartbeat !== undefined) {
|
|
68
|
+
// Skip heartbeat messages
|
|
69
|
+
return null;
|
|
101
70
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
continue;
|
|
114
|
-
const data = JSON.parse(jsonStr);
|
|
115
|
-
// Handle new v8 proto format with heartbeat or event
|
|
116
|
-
if (data.heartbeat !== undefined) {
|
|
117
|
-
// Skip heartbeat messages
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
// Process event messages
|
|
121
|
-
if (data.event) {
|
|
122
|
-
yield {
|
|
123
|
-
txid: data.event.txid,
|
|
124
|
-
scripts: data.event.scripts || [],
|
|
125
|
-
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
126
|
-
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
127
|
-
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
128
|
-
tx: data.event.tx,
|
|
129
|
-
checkpointTxs: data.event.checkpointTxs,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch (parseError) {
|
|
135
|
-
console.error("Failed to parse subscription response:", parseError);
|
|
136
|
-
throw parseError;
|
|
137
|
-
}
|
|
71
|
+
// Process event messages
|
|
72
|
+
if (data.event) {
|
|
73
|
+
return {
|
|
74
|
+
txid: data.event.txid,
|
|
75
|
+
scripts: data.event.scripts || [],
|
|
76
|
+
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
77
|
+
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
78
|
+
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
79
|
+
tx: data.event.tx,
|
|
80
|
+
checkpointTxs: data.event.checkpointTxs,
|
|
81
|
+
};
|
|
138
82
|
}
|
|
139
|
-
|
|
140
|
-
}
|
|
83
|
+
return null;
|
|
84
|
+
});
|
|
141
85
|
}
|
|
142
86
|
catch (error) {
|
|
143
87
|
if (error instanceof Error && error.name === "AbortError") {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamically imports expo/fetch with fallback to standard fetch.
|
|
3
|
+
* @returns A fetch function suitable for SSE streaming
|
|
4
|
+
*/
|
|
5
|
+
export async function getExpoFetch(options) {
|
|
6
|
+
const requireExpo = options?.requireExpo ?? false;
|
|
7
|
+
try {
|
|
8
|
+
const expoFetchModule = await import("expo/fetch");
|
|
9
|
+
console.debug("Using expo/fetch for streaming");
|
|
10
|
+
return expoFetchModule.fetch;
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (requireExpo) {
|
|
14
|
+
throw new Error("expo/fetch is unavailable in this environment. " +
|
|
15
|
+
"Please ensure expo/fetch is installed and properly configured.");
|
|
16
|
+
}
|
|
17
|
+
console.warn("Using standard fetch instead of expo/fetch. " +
|
|
18
|
+
"Streaming may not be fully supported in some environments.", error);
|
|
19
|
+
return fetch;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generic SSE stream processor using fetch API with ReadableStream.
|
|
24
|
+
* Handles SSE format parsing, buffer management, and abort signals.
|
|
25
|
+
*
|
|
26
|
+
* @param url - The SSE endpoint URL
|
|
27
|
+
* @param abortSignal - Signal to abort the stream
|
|
28
|
+
* @param fetchFn - Fetch function to use (defaults to standard fetch)
|
|
29
|
+
* @param headers - Additional headers to send
|
|
30
|
+
* @param parseData - Function to parse and yield data from SSE events
|
|
31
|
+
*/
|
|
32
|
+
export async function* sseStreamIterator(url, abortSignal, fetchFn, headers, parseData) {
|
|
33
|
+
const fetchController = new AbortController();
|
|
34
|
+
const cleanup = () => fetchController.abort();
|
|
35
|
+
abortSignal?.addEventListener("abort", cleanup, { once: true });
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetchFn(url, {
|
|
38
|
+
headers: {
|
|
39
|
+
Accept: "text/event-stream",
|
|
40
|
+
...headers,
|
|
41
|
+
},
|
|
42
|
+
signal: fetchController.signal,
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`Unexpected status ${response.status} when fetching SSE stream`);
|
|
46
|
+
}
|
|
47
|
+
if (!response.body) {
|
|
48
|
+
throw new Error("Response body is null");
|
|
49
|
+
}
|
|
50
|
+
const reader = response.body.getReader();
|
|
51
|
+
const decoder = new TextDecoder();
|
|
52
|
+
let buffer = "";
|
|
53
|
+
while (!abortSignal?.aborted) {
|
|
54
|
+
const { done, value } = await reader.read();
|
|
55
|
+
if (done) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
buffer += decoder.decode(value, { stream: true });
|
|
59
|
+
const lines = buffer.split("\n");
|
|
60
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
61
|
+
const line = lines[i].trim();
|
|
62
|
+
if (!line)
|
|
63
|
+
continue;
|
|
64
|
+
if (line.startsWith("data:")) {
|
|
65
|
+
const jsonStr = line.substring(5).trim();
|
|
66
|
+
if (!jsonStr)
|
|
67
|
+
continue;
|
|
68
|
+
try {
|
|
69
|
+
const data = JSON.parse(jsonStr);
|
|
70
|
+
const parsed = parseData(data);
|
|
71
|
+
if (parsed !== null) {
|
|
72
|
+
yield parsed;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (parseError) {
|
|
76
|
+
console.error("Failed to parse SSE data:", parseError);
|
|
77
|
+
throw parseError;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
buffer = lines[lines.length - 1];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
abortSignal?.removeEventListener("abort", cleanup);
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/esm/script/base.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Script, Address, p2tr, taprootListToTree } from "@scure/btc-signer";
|
|
1
|
+
import { Script, Address, p2tr, taprootListToTree, TAPROOT_UNSPENDABLE_KEY, } from "@scure/btc-signer";
|
|
2
2
|
import { TAP_LEAF_VERSION } from "@scure/btc-signer/payment.js";
|
|
3
3
|
import { PSBTOutput } from "@scure/btc-signer/psbt.js";
|
|
4
|
-
import { TAPROOT_UNSPENDABLE_KEY, } from "@scure/btc-signer/utils.js";
|
|
5
4
|
import { hex } from "@scure/base";
|
|
6
5
|
import { ArkAddress } from './address.js';
|
|
7
6
|
import { ConditionCSVMultisigTapscript, CSVMultisigTapscript, } from './tapscript.js';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as bip68 from "bip68";
|
|
2
|
-
import { Script, ScriptNum } from "@scure/btc-signer
|
|
3
|
-
import { p2tr_ms } from "@scure/btc-signer/payment.js";
|
|
2
|
+
import { Script, ScriptNum, p2tr_ms } from "@scure/btc-signer";
|
|
4
3
|
import { hex } from "@scure/base";
|
|
5
4
|
const MinimalScriptNum = ScriptNum(undefined, true);
|
|
6
5
|
export var TapscriptType;
|
package/dist/esm/script/vhtlc.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Script } from "@scure/btc-signer
|
|
1
|
+
import { Script } from "@scure/btc-signer";
|
|
2
2
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, MultisigTapscript, } from './tapscript.js';
|
|
3
3
|
import { hex } from "@scure/base";
|
|
4
4
|
import { VtxoScript } from './base.js';
|
|
@@ -3,7 +3,7 @@ import { Script } from "@scure/btc-signer/script.js";
|
|
|
3
3
|
import { SigHash } from "@scure/btc-signer/transaction.js";
|
|
4
4
|
import { hex } from "@scure/base";
|
|
5
5
|
import { schnorr, secp256k1 } from "@noble/curves/secp256k1.js";
|
|
6
|
-
import { randomPrivateKeyBytes
|
|
6
|
+
import { randomPrivateKeyBytes } from "@scure/btc-signer/utils.js";
|
|
7
7
|
import { CosignerPublicKey, getArkPsbtFields } from '../utils/unknownFields.js';
|
|
8
8
|
export const ErrMissingVtxoGraph = new Error("missing vtxo graph");
|
|
9
9
|
export const ErrMissingAggregateKey = new Error("missing aggregate key");
|
|
@@ -20,15 +20,15 @@ export class TreeSignerSession {
|
|
|
20
20
|
const secretKey = randomPrivateKeyBytes();
|
|
21
21
|
return new TreeSignerSession(secretKey);
|
|
22
22
|
}
|
|
23
|
-
init(tree, scriptRoot, rootInputAmount) {
|
|
23
|
+
async init(tree, scriptRoot, rootInputAmount) {
|
|
24
24
|
this.graph = tree;
|
|
25
25
|
this.scriptRoot = scriptRoot;
|
|
26
26
|
this.rootSharedOutputAmount = rootInputAmount;
|
|
27
27
|
}
|
|
28
|
-
getPublicKey() {
|
|
28
|
+
async getPublicKey() {
|
|
29
29
|
return secp256k1.getPublicKey(this.secretKey);
|
|
30
30
|
}
|
|
31
|
-
getNonces() {
|
|
31
|
+
async getNonces() {
|
|
32
32
|
if (!this.graph)
|
|
33
33
|
throw ErrMissingVtxoGraph;
|
|
34
34
|
if (!this.myNonces) {
|
|
@@ -40,12 +40,12 @@ export class TreeSignerSession {
|
|
|
40
40
|
}
|
|
41
41
|
return publicNonces;
|
|
42
42
|
}
|
|
43
|
-
setAggregatedNonces(nonces) {
|
|
43
|
+
async setAggregatedNonces(nonces) {
|
|
44
44
|
if (this.aggregateNonces)
|
|
45
45
|
throw new Error("nonces already set");
|
|
46
46
|
this.aggregateNonces = nonces;
|
|
47
47
|
}
|
|
48
|
-
sign() {
|
|
48
|
+
async sign() {
|
|
49
49
|
if (!this.graph)
|
|
50
50
|
throw ErrMissingVtxoGraph;
|
|
51
51
|
if (!this.aggregateNonces)
|
|
@@ -128,9 +128,8 @@ export async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, v
|
|
|
128
128
|
function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
|
|
129
129
|
// generate P2TR script from musig2 final key
|
|
130
130
|
const pkScript = Script.encode(["OP_1", finalKey.slice(1)]);
|
|
131
|
-
const txid = hex.encode(sha256x2(tx.toBytes(true)).reverse());
|
|
132
131
|
// if the input is the root input, return the shared output amount
|
|
133
|
-
if (
|
|
132
|
+
if (tx.id === graph.txid) {
|
|
134
133
|
return {
|
|
135
134
|
amount: sharedOutputAmount,
|
|
136
135
|
script: pkScript,
|
|
@@ -140,7 +139,7 @@ function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
|
|
|
140
139
|
const parentInput = tx.getInput(0);
|
|
141
140
|
if (!parentInput.txid)
|
|
142
141
|
throw new Error("missing parent input txid");
|
|
143
|
-
const parentTxid = hex.encode(
|
|
142
|
+
const parentTxid = hex.encode(parentInput.txid);
|
|
144
143
|
const parent = graph.find(parentTxid);
|
|
145
144
|
if (!parent)
|
|
146
145
|
throw new Error("parent tx not found");
|