@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.
Files changed (77) hide show
  1. package/README.md +64 -14
  2. package/dist/cjs/arknote/index.js +3 -3
  3. package/dist/cjs/forfeit.js +5 -2
  4. package/dist/cjs/identity/singleKey.js +5 -4
  5. package/dist/cjs/index.js +6 -3
  6. package/dist/cjs/{bip322 → intent}/index.js +37 -55
  7. package/dist/cjs/providers/ark.js +62 -23
  8. package/dist/cjs/providers/expoArk.js +15 -170
  9. package/dist/cjs/providers/expoIndexer.js +22 -111
  10. package/dist/cjs/providers/expoUtils.js +124 -0
  11. package/dist/cjs/script/base.js +1 -2
  12. package/dist/cjs/script/tapscript.js +20 -21
  13. package/dist/cjs/script/vhtlc.js +2 -2
  14. package/dist/cjs/tree/signingSession.js +7 -8
  15. package/dist/cjs/tree/txTree.js +3 -4
  16. package/dist/cjs/tree/validation.js +2 -3
  17. package/dist/cjs/utils/arkTransaction.js +104 -12
  18. package/dist/cjs/utils/unknownFields.js +5 -5
  19. package/dist/cjs/wallet/onchain.js +4 -5
  20. package/dist/cjs/wallet/serviceWorker/utils.js +2 -0
  21. package/dist/cjs/wallet/serviceWorker/wallet.js +4 -8
  22. package/dist/cjs/wallet/serviceWorker/worker.js +23 -18
  23. package/dist/cjs/wallet/unroll.js +6 -7
  24. package/dist/cjs/wallet/vtxo-manager.js +381 -0
  25. package/dist/cjs/wallet/wallet.js +63 -94
  26. package/dist/esm/arknote/index.js +2 -2
  27. package/dist/esm/forfeit.js +4 -1
  28. package/dist/esm/identity/singleKey.js +7 -6
  29. package/dist/esm/index.js +7 -6
  30. package/dist/esm/{bip322 → intent}/index.js +31 -48
  31. package/dist/esm/providers/ark.js +62 -23
  32. package/dist/esm/providers/expoArk.js +15 -137
  33. package/dist/esm/providers/expoIndexer.js +22 -78
  34. package/dist/esm/providers/expoUtils.js +87 -0
  35. package/dist/esm/script/base.js +1 -2
  36. package/dist/esm/script/tapscript.js +1 -2
  37. package/dist/esm/script/vhtlc.js +1 -1
  38. package/dist/esm/tree/signingSession.js +8 -9
  39. package/dist/esm/tree/txTree.js +3 -4
  40. package/dist/esm/tree/validation.js +2 -3
  41. package/dist/esm/utils/arkTransaction.js +95 -4
  42. package/dist/esm/utils/unknownFields.js +1 -1
  43. package/dist/esm/wallet/onchain.js +1 -2
  44. package/dist/esm/wallet/serviceWorker/utils.js +1 -0
  45. package/dist/esm/wallet/serviceWorker/wallet.js +5 -9
  46. package/dist/esm/wallet/serviceWorker/worker.js +23 -18
  47. package/dist/esm/wallet/unroll.js +2 -3
  48. package/dist/esm/wallet/vtxo-manager.js +372 -0
  49. package/dist/esm/wallet/wallet.js +56 -87
  50. package/dist/types/arknote/index.d.ts +1 -1
  51. package/dist/types/forfeit.d.ts +2 -2
  52. package/dist/types/identity/index.d.ts +1 -1
  53. package/dist/types/identity/singleKey.d.ts +1 -1
  54. package/dist/types/index.d.ts +6 -5
  55. package/dist/types/intent/index.d.ts +41 -0
  56. package/dist/types/providers/ark.d.ts +55 -21
  57. package/dist/types/providers/expoIndexer.d.ts +2 -10
  58. package/dist/types/providers/expoUtils.d.ts +18 -0
  59. package/dist/types/providers/indexer.d.ts +1 -9
  60. package/dist/types/script/base.d.ts +3 -2
  61. package/dist/types/tree/signingSession.d.ts +10 -10
  62. package/dist/types/utils/anchor.d.ts +2 -2
  63. package/dist/types/utils/arkTransaction.d.ts +13 -3
  64. package/dist/types/utils/unknownFields.d.ts +2 -2
  65. package/dist/types/wallet/index.d.ts +6 -4
  66. package/dist/types/wallet/onchain.d.ts +1 -1
  67. package/dist/types/wallet/serviceWorker/utils.d.ts +1 -0
  68. package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
  69. package/dist/types/wallet/serviceWorker/worker.d.ts +3 -1
  70. package/dist/types/wallet/unroll.d.ts +1 -1
  71. package/dist/types/wallet/vtxo-manager.d.ts +207 -0
  72. package/dist/types/wallet/wallet.d.ts +7 -3
  73. package/package.json +1 -2
  74. package/dist/cjs/bip322/errors.js +0 -13
  75. package/dist/esm/bip322/errors.js +0 -9
  76. package/dist/types/bip322/errors.d.ts +0 -6
  77. 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
- checkpointExitClosure: fromServer.checkpointTapscript ?? "",
46
- marketHour: "marketHour" in fromServer && fromServer.marketHour != null
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
- nextStartTime: BigInt(fromServer.marketHour.nextStartTime ?? 0),
49
- nextEndTime: BigInt(fromServer.marketHour.nextEndTime ?? 0),
50
- period: BigInt(fromServer.marketHour.period ?? 0),
51
- roundInterval: BigInt(fromServer.marketHour.roundInterval ?? 0),
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: signedArkTx,
65
- checkpointTxs: 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
- signature: intent.signature,
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
- signature: intent.signature,
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 JSON.stringify(noncesObject);
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 JSON.stringify(sigObject);
476
+ return sigObject;
437
477
  }
438
- function decodeMusig2Nonces(str) {
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
- // Dynamic import to avoid bundling expo/fetch in non-Expo environments
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
- const response = await expoFetch(url + queryParams, {
45
- headers: {
46
- Accept: "text/event-stream",
47
- },
48
- signal: fetchController.signal,
49
- });
50
- if (!response.ok) {
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
- // Append new data to buffer and split by newlines
65
- buffer += decoder.decode(value, { stream: true });
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
- // Dynamic import to avoid bundling expo/fetch in non-Expo environments
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
- const response = await expoFetch(url, {
144
- headers: {
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("Address subscription error:", 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
- // Dynamic import to avoid bundling expo/fetch in non-Expo environments
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
- // In non-RN environments, fall back to standard fetch but warn about potential streaming issues
68
- console.warn("Using standard fetch instead of expo/fetch. " +
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
- const res = await expoFetch(url, {
75
- headers: {
76
- Accept: "text/event-stream",
77
- "Content-Type": "application/json",
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
- buffer += decoder.decode(value, { stream: true });
103
- const lines = buffer.split("\n");
104
- for (let i = 0; i < lines.length - 1; i++) {
105
- const line = lines[i].trim();
106
- if (!line)
107
- continue;
108
- try {
109
- // Parse SSE format: "data: {json}"
110
- if (line.startsWith("data:")) {
111
- const jsonStr = line.substring(5).trim();
112
- if (!jsonStr)
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
- buffer = lines[lines.length - 1];
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
+ }
@@ -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/script.js";
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;
@@ -1,4 +1,4 @@
1
- import { Script } from "@scure/btc-signer/script.js";
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, sha256x2 } from "@scure/btc-signer/utils.js";
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 (txid === graph.txid) {
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(new Uint8Array(parentInput.txid));
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");