@arkade-os/sdk 0.1.4 → 0.2.0

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 (114) hide show
  1. package/README.md +156 -174
  2. package/dist/cjs/arknote/index.js +61 -58
  3. package/dist/cjs/bip322/errors.js +13 -0
  4. package/dist/cjs/bip322/index.js +178 -0
  5. package/dist/cjs/forfeit.js +14 -25
  6. package/dist/cjs/identity/singleKey.js +68 -0
  7. package/dist/cjs/index.js +41 -17
  8. package/dist/cjs/providers/ark.js +253 -317
  9. package/dist/cjs/providers/indexer.js +525 -0
  10. package/dist/cjs/providers/onchain.js +193 -15
  11. package/dist/cjs/script/address.js +48 -17
  12. package/dist/cjs/script/base.js +120 -3
  13. package/dist/cjs/script/default.js +18 -4
  14. package/dist/cjs/script/tapscript.js +46 -14
  15. package/dist/cjs/script/vhtlc.js +27 -7
  16. package/dist/cjs/tree/signingSession.js +63 -106
  17. package/dist/cjs/tree/txTree.js +193 -0
  18. package/dist/cjs/tree/validation.js +79 -155
  19. package/dist/cjs/utils/anchor.js +35 -0
  20. package/dist/cjs/utils/arkTransaction.js +108 -0
  21. package/dist/cjs/utils/transactionHistory.js +84 -72
  22. package/dist/cjs/utils/txSizeEstimator.js +12 -0
  23. package/dist/cjs/utils/unknownFields.js +211 -0
  24. package/dist/cjs/wallet/index.js +12 -0
  25. package/dist/cjs/wallet/onchain.js +201 -0
  26. package/dist/cjs/wallet/ramps.js +95 -0
  27. package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  28. package/dist/cjs/wallet/serviceWorker/request.js +15 -12
  29. package/dist/cjs/wallet/serviceWorker/response.js +22 -27
  30. package/dist/cjs/wallet/serviceWorker/utils.js +8 -0
  31. package/dist/cjs/wallet/serviceWorker/wallet.js +58 -34
  32. package/dist/cjs/wallet/serviceWorker/worker.js +117 -108
  33. package/dist/cjs/wallet/unroll.js +270 -0
  34. package/dist/cjs/wallet/wallet.js +701 -454
  35. package/dist/esm/arknote/index.js +61 -57
  36. package/dist/esm/bip322/errors.js +9 -0
  37. package/dist/esm/bip322/index.js +174 -0
  38. package/dist/esm/forfeit.js +15 -26
  39. package/dist/esm/identity/singleKey.js +64 -0
  40. package/dist/esm/index.js +30 -12
  41. package/dist/esm/providers/ark.js +252 -317
  42. package/dist/esm/providers/indexer.js +521 -0
  43. package/dist/esm/providers/onchain.js +193 -15
  44. package/dist/esm/script/address.js +48 -17
  45. package/dist/esm/script/base.js +120 -3
  46. package/dist/esm/script/default.js +18 -4
  47. package/dist/esm/script/tapscript.js +46 -14
  48. package/dist/esm/script/vhtlc.js +27 -7
  49. package/dist/esm/tree/signingSession.js +65 -108
  50. package/dist/esm/tree/txTree.js +189 -0
  51. package/dist/esm/tree/validation.js +75 -152
  52. package/dist/esm/utils/anchor.js +31 -0
  53. package/dist/esm/utils/arkTransaction.js +105 -0
  54. package/dist/esm/utils/transactionHistory.js +84 -72
  55. package/dist/esm/utils/txSizeEstimator.js +12 -0
  56. package/dist/esm/utils/unknownFields.js +173 -0
  57. package/dist/esm/wallet/index.js +9 -0
  58. package/dist/esm/wallet/onchain.js +196 -0
  59. package/dist/esm/wallet/ramps.js +91 -0
  60. package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  61. package/dist/esm/wallet/serviceWorker/request.js +15 -12
  62. package/dist/esm/wallet/serviceWorker/response.js +22 -27
  63. package/dist/esm/wallet/serviceWorker/utils.js +8 -0
  64. package/dist/esm/wallet/serviceWorker/wallet.js +59 -35
  65. package/dist/esm/wallet/serviceWorker/worker.js +117 -108
  66. package/dist/esm/wallet/unroll.js +267 -0
  67. package/dist/esm/wallet/wallet.js +674 -461
  68. package/dist/types/arknote/index.d.ts +40 -13
  69. package/dist/types/bip322/errors.d.ts +6 -0
  70. package/dist/types/bip322/index.d.ts +57 -0
  71. package/dist/types/forfeit.d.ts +2 -14
  72. package/dist/types/identity/singleKey.d.ts +27 -0
  73. package/dist/types/index.d.ts +23 -12
  74. package/dist/types/providers/ark.d.ts +114 -95
  75. package/dist/types/providers/indexer.d.ts +186 -0
  76. package/dist/types/providers/onchain.d.ts +41 -11
  77. package/dist/types/script/address.d.ts +26 -2
  78. package/dist/types/script/base.d.ts +13 -3
  79. package/dist/types/script/default.d.ts +22 -0
  80. package/dist/types/script/tapscript.d.ts +61 -5
  81. package/dist/types/script/vhtlc.d.ts +27 -0
  82. package/dist/types/tree/signingSession.d.ts +5 -5
  83. package/dist/types/tree/txTree.d.ts +28 -0
  84. package/dist/types/tree/validation.d.ts +15 -22
  85. package/dist/types/utils/anchor.d.ts +19 -0
  86. package/dist/types/utils/arkTransaction.d.ts +27 -0
  87. package/dist/types/utils/transactionHistory.d.ts +7 -1
  88. package/dist/types/utils/txSizeEstimator.d.ts +3 -0
  89. package/dist/types/utils/unknownFields.d.ts +83 -0
  90. package/dist/types/wallet/index.d.ts +51 -50
  91. package/dist/types/wallet/onchain.d.ts +49 -0
  92. package/dist/types/wallet/ramps.d.ts +32 -0
  93. package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +2 -0
  94. package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +2 -0
  95. package/dist/types/wallet/serviceWorker/request.d.ts +14 -16
  96. package/dist/types/wallet/serviceWorker/response.d.ts +17 -19
  97. package/dist/types/wallet/serviceWorker/utils.d.ts +8 -0
  98. package/dist/types/wallet/serviceWorker/wallet.d.ts +36 -8
  99. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -3
  100. package/dist/types/wallet/unroll.d.ts +102 -0
  101. package/dist/types/wallet/wallet.d.ts +71 -25
  102. package/package.json +14 -15
  103. package/dist/cjs/identity/inMemoryKey.js +0 -40
  104. package/dist/cjs/tree/vtxoTree.js +0 -231
  105. package/dist/cjs/utils/coinselect.js +0 -73
  106. package/dist/cjs/utils/psbt.js +0 -137
  107. package/dist/esm/identity/inMemoryKey.js +0 -36
  108. package/dist/esm/tree/vtxoTree.js +0 -191
  109. package/dist/esm/utils/coinselect.js +0 -69
  110. package/dist/esm/utils/psbt.js +0 -131
  111. package/dist/types/identity/inMemoryKey.d.ts +0 -12
  112. package/dist/types/tree/vtxoTree.d.ts +0 -33
  113. package/dist/types/utils/coinselect.d.ts +0 -21
  114. package/dist/types/utils/psbt.d.ts +0 -11
@@ -1,13 +1,24 @@
1
- import { TxTree } from '../tree/vtxoTree.js';
2
1
  import { hex } from "@scure/base";
3
2
  export var SettlementEventType;
4
3
  (function (SettlementEventType) {
5
- SettlementEventType["Finalization"] = "finalization";
6
- SettlementEventType["Finalized"] = "finalized";
7
- SettlementEventType["Failed"] = "failed";
8
- SettlementEventType["SigningStart"] = "signing_start";
9
- SettlementEventType["SigningNoncesGenerated"] = "signing_nonces_generated";
4
+ SettlementEventType["BatchStarted"] = "batch_started";
5
+ SettlementEventType["BatchFinalization"] = "batch_finalization";
6
+ SettlementEventType["BatchFinalized"] = "batch_finalized";
7
+ SettlementEventType["BatchFailed"] = "batch_failed";
8
+ SettlementEventType["TreeSigningStarted"] = "tree_signing_started";
9
+ SettlementEventType["TreeNoncesAggregated"] = "tree_nonces_aggregated";
10
+ SettlementEventType["TreeTx"] = "tree_tx";
11
+ SettlementEventType["TreeSignature"] = "tree_signature";
10
12
  })(SettlementEventType || (SettlementEventType = {}));
13
+ /**
14
+ * REST-based Ark provider implementation.
15
+ * @see https://buf.build/arkade-os/arkd/docs/main:ark.v1#ark.v1.ArkService
16
+ * @example
17
+ * ```typescript
18
+ * const provider = new RestArkProvider('https://ark.example.com');
19
+ * const info = await provider.getInfo();
20
+ * ```
21
+ */
11
22
  export class RestArkProvider {
12
23
  constructor(serverUrl) {
13
24
  this.serverUrl = serverUrl;
@@ -21,48 +32,35 @@ export class RestArkProvider {
21
32
  const fromServer = await response.json();
22
33
  return {
23
34
  ...fromServer,
35
+ vtxoTreeExpiry: BigInt(fromServer.vtxoTreeExpiry ?? 0),
24
36
  unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
25
- batchExpiry: BigInt(fromServer.vtxoTreeExpiry ?? 0),
26
- };
27
- }
28
- async getVirtualCoins(address) {
29
- const url = `${this.serverUrl}/v1/vtxos/${address}`;
30
- const response = await fetch(url);
31
- if (!response.ok) {
32
- throw new Error(`Failed to fetch VTXOs: ${response.statusText}`);
33
- }
34
- const data = await response.json();
35
- return {
36
- spendableVtxos: [...(data.spendableVtxos || [])].map(convertVtxo),
37
- spentVtxos: [...(data.spentVtxos || [])].map(convertVtxo),
38
- };
39
- }
40
- async getRound(txid) {
41
- const url = `${this.serverUrl}/v1/round/${txid}`;
42
- const response = await fetch(url);
43
- if (!response.ok) {
44
- throw new Error(`Failed to fetch round: ${response.statusText}`);
45
- }
46
- const data = (await response.json());
47
- const round = data.round;
48
- return {
49
- id: round.id,
50
- start: new Date(Number(round.start) * 1000), // Convert from Unix timestamp to Date
51
- end: new Date(Number(round.end) * 1000), // Convert from Unix timestamp to Date
52
- vtxoTree: this.toTxTree(round.vtxoTree),
53
- forfeitTxs: round.forfeitTxs || [],
54
- connectors: this.toTxTree(round.connectors),
37
+ roundInterval: BigInt(fromServer.roundInterval ?? 0),
38
+ dust: BigInt(fromServer.dust ?? 0),
39
+ utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
40
+ utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
41
+ vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
42
+ vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
43
+ boardingExitDelay: BigInt(fromServer.boardingExitDelay ?? 0),
44
+ marketHour: "marketHour" in fromServer && fromServer.marketHour != null
45
+ ? {
46
+ nextStartTime: BigInt(fromServer.marketHour.nextStartTime ?? 0),
47
+ nextEndTime: BigInt(fromServer.marketHour.nextEndTime ?? 0),
48
+ period: BigInt(fromServer.marketHour.period ?? 0),
49
+ roundInterval: BigInt(fromServer.marketHour.roundInterval ?? 0),
50
+ }
51
+ : undefined,
55
52
  };
56
53
  }
57
- async submitVirtualTx(psbtBase64) {
58
- const url = `${this.serverUrl}/v1/redeem-tx`;
54
+ async submitTx(signedArkTx, checkpointTxs) {
55
+ const url = `${this.serverUrl}/v1/tx/submit`;
59
56
  const response = await fetch(url, {
60
57
  method: "POST",
61
58
  headers: {
62
59
  "Content-Type": "application/json",
63
60
  },
64
61
  body: JSON.stringify({
65
- redeem_tx: psbtBase64,
62
+ signedArkTx: signedArkTx,
63
+ checkpointTxs: checkpointTxs,
66
64
  }),
67
65
  });
68
66
  if (!response.ok) {
@@ -79,140 +77,96 @@ export class RestArkProvider {
79
77
  }
80
78
  }
81
79
  const data = await response.json();
82
- // Handle both current and future response formats
83
- return data.txid || data.signedRedeemTx;
84
- }
85
- async subscribeToEvents(callback) {
86
- const url = `${this.serverUrl}/v1/events`;
87
- let abortController = new AbortController();
88
- (async () => {
89
- while (!abortController.signal.aborted) {
90
- try {
91
- const response = await fetch(url, {
92
- headers: {
93
- Accept: "application/json",
94
- },
95
- signal: abortController.signal,
96
- });
97
- if (!response.ok) {
98
- throw new Error(`Unexpected status ${response.status} when fetching event stream`);
99
- }
100
- if (!response.body) {
101
- throw new Error("Response body is null");
102
- }
103
- const reader = response.body.getReader();
104
- const decoder = new TextDecoder();
105
- let buffer = "";
106
- while (!abortController.signal.aborted) {
107
- const { done, value } = await reader.read();
108
- if (done)
109
- break;
110
- // Append new data to buffer and split by newlines
111
- buffer += decoder.decode(value, { stream: true });
112
- const lines = buffer.split("\n");
113
- // Process all complete lines
114
- for (let i = 0; i < lines.length - 1; i++) {
115
- const line = lines[i].trim();
116
- if (!line)
117
- continue;
118
- try {
119
- const data = JSON.parse(line);
120
- callback(data);
121
- }
122
- catch (err) {
123
- console.error("Failed to parse event:", err);
124
- }
125
- }
126
- // Keep the last partial line in the buffer
127
- buffer = lines[lines.length - 1];
128
- }
129
- }
130
- catch (error) {
131
- if (!abortController.signal.aborted) {
132
- console.error("Event stream error:", error);
133
- }
134
- }
135
- }
136
- })();
137
- // Return unsubscribe function
138
- return () => {
139
- abortController.abort();
140
- // Create a new controller for potential future subscriptions
141
- abortController = new AbortController();
80
+ return {
81
+ arkTxid: data.arkTxid,
82
+ finalArkTx: data.finalArkTx,
83
+ signedCheckpointTxs: data.signedCheckpointTxs,
142
84
  };
143
85
  }
144
- async registerInputsForNextRound(inputs) {
145
- const url = `${this.serverUrl}/v1/round/registerInputs`;
146
- const vtxoInputs = [];
147
- const noteInputs = [];
148
- for (const input of inputs) {
149
- if (typeof input === "string") {
150
- noteInputs.push(input);
151
- }
152
- else {
153
- vtxoInputs.push({
154
- outpoint: {
155
- txid: input.outpoint.txid,
156
- vout: input.outpoint.vout,
157
- },
158
- tapscripts: {
159
- scripts: input.tapscripts,
160
- },
161
- });
162
- }
86
+ async finalizeTx(arkTxid, finalCheckpointTxs) {
87
+ const url = `${this.serverUrl}/v1/tx/finalize`;
88
+ const response = await fetch(url, {
89
+ method: "POST",
90
+ headers: {
91
+ "Content-Type": "application/json",
92
+ },
93
+ body: JSON.stringify({
94
+ arkTxid,
95
+ finalCheckpointTxs,
96
+ }),
97
+ });
98
+ if (!response.ok) {
99
+ const errorText = await response.text();
100
+ throw new Error(`Failed to finalize offchain transaction: ${errorText}`);
163
101
  }
102
+ }
103
+ async registerIntent(intent) {
104
+ const url = `${this.serverUrl}/v1/batch/registerIntent`;
164
105
  const response = await fetch(url, {
165
106
  method: "POST",
166
107
  headers: {
167
108
  "Content-Type": "application/json",
168
109
  },
169
110
  body: JSON.stringify({
170
- inputs: vtxoInputs,
171
- notes: noteInputs,
111
+ intent: {
112
+ signature: intent.signature,
113
+ message: intent.message,
114
+ },
172
115
  }),
173
116
  });
174
117
  if (!response.ok) {
175
118
  const errorText = await response.text();
176
- throw new Error(`Failed to register inputs: ${errorText}`);
119
+ throw new Error(`Failed to register intent: ${errorText}`);
177
120
  }
178
121
  const data = await response.json();
179
- return { requestId: data.requestId };
122
+ return data.intentId;
180
123
  }
181
- async registerOutputsForNextRound(requestId, outputs, cosignersPublicKeys, signingAll = false) {
182
- const url = `${this.serverUrl}/v1/round/registerOutputs`;
124
+ async deleteIntent(intent) {
125
+ const url = `${this.serverUrl}/v1/batch/deleteIntent`;
183
126
  const response = await fetch(url, {
184
127
  method: "POST",
185
128
  headers: {
186
129
  "Content-Type": "application/json",
187
130
  },
188
131
  body: JSON.stringify({
189
- requestId,
190
- outputs: outputs.map((output) => ({
191
- address: output.address,
192
- amount: output.amount.toString(10),
193
- })),
194
- musig2: {
195
- cosignersPublicKeys,
196
- signingAll,
132
+ proof: {
133
+ signature: intent.signature,
134
+ message: intent.message,
197
135
  },
198
136
  }),
199
137
  });
200
138
  if (!response.ok) {
201
139
  const errorText = await response.text();
202
- throw new Error(`Failed to register outputs: ${errorText}`);
140
+ throw new Error(`Failed to delete intent: ${errorText}`);
141
+ }
142
+ }
143
+ async confirmRegistration(intentId) {
144
+ const url = `${this.serverUrl}/v1/batch/ack`;
145
+ const response = await fetch(url, {
146
+ method: "POST",
147
+ headers: {
148
+ "Content-Type": "application/json",
149
+ },
150
+ body: JSON.stringify({
151
+ intentId,
152
+ }),
153
+ });
154
+ if (!response.ok) {
155
+ const errorText = await response.text();
156
+ throw new Error(`Failed to confirm registration: ${errorText}`);
203
157
  }
204
158
  }
205
- async submitTreeNonces(settlementID, pubkey, nonces) {
206
- const url = `${this.serverUrl}/v1/round/tree/submitNonces`;
159
+ async submitTreeNonces(batchId, pubkey, nonces) {
160
+ const url = `${this.serverUrl}/v1/batch/tree/submitNonces`;
207
161
  const response = await fetch(url, {
208
162
  method: "POST",
209
163
  headers: {
210
164
  "Content-Type": "application/json",
211
165
  },
212
166
  body: JSON.stringify({
213
- roundId: settlementID,
167
+ batchId,
214
168
  pubkey,
215
- treeNonces: encodeNoncesMatrix(nonces),
169
+ treeNonces: encodeMusig2Nonces(nonces),
216
170
  }),
217
171
  });
218
172
  if (!response.ok) {
@@ -220,17 +174,17 @@ export class RestArkProvider {
220
174
  throw new Error(`Failed to submit tree nonces: ${errorText}`);
221
175
  }
222
176
  }
223
- async submitTreeSignatures(settlementID, pubkey, signatures) {
224
- const url = `${this.serverUrl}/v1/round/tree/submitSignatures`;
177
+ async submitTreeSignatures(batchId, pubkey, signatures) {
178
+ const url = `${this.serverUrl}/v1/batch/tree/submitSignatures`;
225
179
  const response = await fetch(url, {
226
180
  method: "POST",
227
181
  headers: {
228
182
  "Content-Type": "application/json",
229
183
  },
230
184
  body: JSON.stringify({
231
- roundId: settlementID,
185
+ batchId,
232
186
  pubkey,
233
- treeSignatures: encodeSignaturesMatrix(signatures),
187
+ treeSignatures: encodeMusig2Signatures(signatures),
234
188
  }),
235
189
  });
236
190
  if (!response.ok) {
@@ -238,8 +192,8 @@ export class RestArkProvider {
238
192
  throw new Error(`Failed to submit tree signatures: ${errorText}`);
239
193
  }
240
194
  }
241
- async submitSignedForfeitTxs(signedForfeitTxs, signedRoundTx) {
242
- const url = `${this.serverUrl}/v1/round/submitForfeitTxs`;
195
+ async submitSignedForfeitTxs(signedForfeitTxs, signedCommitmentTx) {
196
+ const url = `${this.serverUrl}/v1/batch/submitForfeitTxs`;
243
197
  const response = await fetch(url, {
244
198
  method: "POST",
245
199
  headers: {
@@ -247,25 +201,21 @@ export class RestArkProvider {
247
201
  },
248
202
  body: JSON.stringify({
249
203
  signedForfeitTxs: signedForfeitTxs,
250
- signedRoundTx: signedRoundTx,
204
+ signedCommitmentTx: signedCommitmentTx,
251
205
  }),
252
206
  });
253
207
  if (!response.ok) {
254
208
  throw new Error(`Failed to submit forfeit transactions: ${response.statusText}`);
255
209
  }
256
210
  }
257
- async ping(requestId) {
258
- const url = `${this.serverUrl}/v1/round/ping/${requestId}`;
259
- const response = await fetch(url);
260
- if (!response.ok) {
261
- throw new Error(`Ping failed: ${response.statusText}`);
262
- }
263
- }
264
- async *getEventStream(signal) {
265
- const url = `${this.serverUrl}/v1/events`;
211
+ async *getEventStream(signal, topics) {
212
+ const url = `${this.serverUrl}/v1/batch/events`;
213
+ const queryParams = topics.length > 0
214
+ ? `?${topics.map((topic) => `topics=${encodeURIComponent(topic)}`).join("&")}`
215
+ : "";
266
216
  while (!signal?.aborted) {
267
217
  try {
268
- const response = await fetch(url, {
218
+ const response = await fetch(url + queryParams, {
269
219
  headers: {
270
220
  Accept: "application/json",
271
221
  },
@@ -313,22 +263,29 @@ export class RestArkProvider {
313
263
  if (error instanceof Error && error.name === "AbortError") {
314
264
  break;
315
265
  }
266
+ // 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
+ if (isFetchTimeoutError(error)) {
269
+ console.debug("Timeout error ignored");
270
+ continue;
271
+ }
316
272
  console.error("Event stream error:", error);
317
273
  throw error;
318
274
  }
319
275
  }
320
276
  }
321
- async *subscribeForAddress(address, abortSignal) {
322
- const url = `${this.serverUrl}/v1/vtxos/${address}/subscribe`;
323
- while (!abortSignal.aborted) {
277
+ async *getTransactionsStream(signal) {
278
+ const url = `${this.serverUrl}/v1/txs`;
279
+ while (!signal?.aborted) {
324
280
  try {
325
281
  const response = await fetch(url, {
326
282
  headers: {
327
283
  Accept: "application/json",
328
284
  },
285
+ signal,
329
286
  });
330
287
  if (!response.ok) {
331
- throw new Error(`Unexpected status ${response.status} when subscribing to address updates`);
288
+ throw new Error(`Unexpected status ${response.status} when fetching transaction stream`);
332
289
  }
333
290
  if (!response.body) {
334
291
  throw new Error("Response body is null");
@@ -336,35 +293,33 @@ export class RestArkProvider {
336
293
  const reader = response.body.getReader();
337
294
  const decoder = new TextDecoder();
338
295
  let buffer = "";
339
- while (!abortSignal.aborted) {
296
+ while (!signal?.aborted) {
340
297
  const { done, value } = await reader.read();
341
298
  if (done) {
342
299
  break;
343
300
  }
301
+ // Append new data to buffer and split by newlines
344
302
  buffer += decoder.decode(value, { stream: true });
345
303
  const lines = buffer.split("\n");
304
+ // Process all complete lines
346
305
  for (let i = 0; i < lines.length - 1; i++) {
347
306
  const line = lines[i].trim();
348
307
  if (!line)
349
308
  continue;
350
- try {
351
- const data = JSON.parse(line);
352
- if ("result" in data) {
353
- yield {
354
- newVtxos: (data.result.newVtxos || []).map(convertVtxo),
355
- spentVtxos: (data.result.spentVtxos || []).map(convertVtxo),
356
- };
357
- }
358
- }
359
- catch (err) {
360
- console.error("Failed to parse address update:", err);
361
- throw err;
309
+ const data = JSON.parse(line);
310
+ const txNotification = this.parseTransactionNotification(data.result);
311
+ if (txNotification) {
312
+ yield txNotification;
362
313
  }
363
314
  }
315
+ // Keep the last partial line in the buffer
364
316
  buffer = lines[lines.length - 1];
365
317
  }
366
318
  }
367
319
  catch (error) {
320
+ if (error instanceof Error && error.name === "AbortError") {
321
+ break;
322
+ }
368
323
  // ignore timeout errors, they're expected when the server is not sending anything for 5 min
369
324
  // these timeouts are set by builtin fetch function
370
325
  if (isFetchTimeoutError(error)) {
@@ -376,178 +331,138 @@ export class RestArkProvider {
376
331
  }
377
332
  }
378
333
  }
379
- toConnectorsIndex(connectorsIndex) {
380
- return new Map(Object.entries(connectorsIndex).map(([key, value]) => [
381
- key,
382
- { txid: value.txid, vout: value.vout },
383
- ]));
384
- }
385
- toTxTree(t) {
386
- // collect the parent txids to determine later if a node is a leaf
387
- const parentTxids = new Set();
388
- t.levels.forEach((level) => level.nodes.forEach((node) => {
389
- if (node.parentTxid) {
390
- parentTxids.add(node.parentTxid);
391
- }
392
- }));
393
- return new TxTree(t.levels.map((level) => level.nodes.map((node) => ({
394
- txid: node.txid,
395
- tx: node.tx,
396
- parentTxid: node.parentTxid,
397
- leaf: !parentTxids.has(node.txid),
398
- }))));
399
- }
400
334
  parseSettlementEvent(data) {
401
- // Check for Finalization event
402
- if (data.roundFinalization) {
335
+ // Check for BatchStarted event
336
+ if (data.batchStarted) {
337
+ return {
338
+ type: SettlementEventType.BatchStarted,
339
+ id: data.batchStarted.id,
340
+ intentIdHashes: data.batchStarted.intentIdHashes,
341
+ batchExpiry: BigInt(data.batchStarted.batchExpiry),
342
+ };
343
+ }
344
+ // Check for BatchFinalization event
345
+ if (data.batchFinalization) {
346
+ return {
347
+ type: SettlementEventType.BatchFinalization,
348
+ id: data.batchFinalization.id,
349
+ commitmentTx: data.batchFinalization.commitmentTx,
350
+ };
351
+ }
352
+ // Check for BatchFinalized event
353
+ if (data.batchFinalized) {
354
+ return {
355
+ type: SettlementEventType.BatchFinalized,
356
+ id: data.batchFinalized.id,
357
+ commitmentTxid: data.batchFinalized.commitmentTxid,
358
+ };
359
+ }
360
+ // Check for BatchFailed event
361
+ if (data.batchFailed) {
403
362
  return {
404
- type: SettlementEventType.Finalization,
405
- id: data.roundFinalization.id,
406
- roundTx: data.roundFinalization.roundTx,
407
- vtxoTree: this.toTxTree(data.roundFinalization.vtxoTree),
408
- connectors: this.toTxTree(data.roundFinalization.connectors),
409
- connectorsIndex: this.toConnectorsIndex(data.roundFinalization.connectorsIndex),
410
- // divide by 1000 to convert to sat/vbyte
411
- minRelayFeeRate: BigInt(data.roundFinalization.minRelayFeeRate) /
412
- BigInt(1000),
363
+ type: SettlementEventType.BatchFailed,
364
+ id: data.batchFailed.id,
365
+ reason: data.batchFailed.reason,
413
366
  };
414
367
  }
415
- // Check for Finalized event
416
- if (data.roundFinalized) {
368
+ // Check for TreeSigningStarted event
369
+ if (data.treeSigningStarted) {
417
370
  return {
418
- type: SettlementEventType.Finalized,
419
- id: data.roundFinalized.id,
420
- roundTxid: data.roundFinalized.roundTxid,
371
+ type: SettlementEventType.TreeSigningStarted,
372
+ id: data.treeSigningStarted.id,
373
+ cosignersPublicKeys: data.treeSigningStarted.cosignersPubkeys,
374
+ unsignedCommitmentTx: data.treeSigningStarted.unsignedCommitmentTx,
421
375
  };
422
376
  }
423
- // Check for Failed event
424
- if (data.roundFailed) {
377
+ // Check for TreeNoncesAggregated event
378
+ if (data.treeNoncesAggregated) {
425
379
  return {
426
- type: SettlementEventType.Failed,
427
- id: data.roundFailed.id,
428
- reason: data.roundFailed.reason,
380
+ type: SettlementEventType.TreeNoncesAggregated,
381
+ id: data.treeNoncesAggregated.id,
382
+ treeNonces: decodeMusig2Nonces(data.treeNoncesAggregated.treeNonces),
429
383
  };
430
384
  }
431
- // Check for Signing event
432
- if (data.roundSigning) {
385
+ // Check for TreeTx event
386
+ if (data.treeTx) {
387
+ const children = Object.fromEntries(Object.entries(data.treeTx.children).map(([outputIndex, txid]) => {
388
+ return [parseInt(outputIndex), txid];
389
+ }));
433
390
  return {
434
- type: SettlementEventType.SigningStart,
435
- id: data.roundSigning.id,
436
- cosignersPublicKeys: data.roundSigning.cosignersPubkeys,
437
- unsignedVtxoTree: this.toTxTree(data.roundSigning.unsignedVtxoTree),
438
- unsignedSettlementTx: data.roundSigning.unsignedRoundTx,
391
+ type: SettlementEventType.TreeTx,
392
+ id: data.treeTx.id,
393
+ topic: data.treeTx.topic,
394
+ batchIndex: data.treeTx.batchIndex,
395
+ chunk: {
396
+ txid: data.treeTx.txid,
397
+ tx: data.treeTx.tx,
398
+ children,
399
+ },
439
400
  };
440
401
  }
441
- // Check for SigningNoncesGenerated event
442
- if (data.roundSigningNoncesGenerated) {
402
+ if (data.treeSignature) {
443
403
  return {
444
- type: SettlementEventType.SigningNoncesGenerated,
445
- id: data.roundSigningNoncesGenerated.id,
446
- treeNonces: decodeNoncesMatrix(hex.decode(data.roundSigningNoncesGenerated.treeNonces)),
404
+ type: SettlementEventType.TreeSignature,
405
+ id: data.treeSignature.id,
406
+ topic: data.treeSignature.topic,
407
+ batchIndex: data.treeSignature.batchIndex,
408
+ txid: data.treeSignature.txid,
409
+ signature: data.treeSignature.signature,
447
410
  };
448
411
  }
449
- console.warn("Unknown event structure:", data);
412
+ console.warn("Unknown event type:", data);
450
413
  return null;
451
414
  }
452
- }
453
- function encodeMatrix(matrix) {
454
- // Calculate total size needed:
455
- // 4 bytes for number of rows
456
- // For each row: 4 bytes for length + sum of encoded cell lengths + isNil byte * cell count
457
- let totalSize = 4;
458
- for (const row of matrix) {
459
- totalSize += 4; // row length
460
- for (const cell of row) {
461
- totalSize += 1;
462
- totalSize += cell.length;
415
+ parseTransactionNotification(data) {
416
+ if (data.commitmentTx) {
417
+ return {
418
+ commitmentTx: {
419
+ txid: data.commitmentTx.txid,
420
+ tx: data.commitmentTx.tx,
421
+ spentVtxos: data.commitmentTx.spentVtxos.map(mapVtxo),
422
+ spendableVtxos: data.commitmentTx.spendableVtxos.map(mapVtxo),
423
+ checkpointTxs: data.commitmentTx.checkpointTxs,
424
+ },
425
+ };
463
426
  }
464
- }
465
- // Create buffer and DataView
466
- const buffer = new ArrayBuffer(totalSize);
467
- const view = new DataView(buffer);
468
- let offset = 0;
469
- // Write number of rows
470
- view.setUint32(offset, matrix.length, true); // true for little-endian
471
- offset += 4;
472
- // Write each row
473
- for (const row of matrix) {
474
- // Write row length
475
- view.setUint32(offset, row.length, true);
476
- offset += 4;
477
- // Write each cell
478
- for (const cell of row) {
479
- const notNil = cell.length > 0;
480
- view.setInt8(offset, notNil ? 1 : 0);
481
- offset += 1;
482
- if (!notNil) {
483
- continue;
484
- }
485
- new Uint8Array(buffer).set(cell, offset);
486
- offset += cell.length;
427
+ if (data.arkTx) {
428
+ return {
429
+ arkTx: {
430
+ txid: data.arkTx.txid,
431
+ tx: data.arkTx.tx,
432
+ spentVtxos: data.arkTx.spentVtxos.map(mapVtxo),
433
+ spendableVtxos: data.arkTx.spendableVtxos.map(mapVtxo),
434
+ checkpointTxs: data.arkTx.checkpointTxs,
435
+ },
436
+ };
487
437
  }
438
+ console.warn("Unknown transaction notification type:", data);
439
+ return null;
488
440
  }
489
- return new Uint8Array(buffer);
490
441
  }
491
- function decodeMatrix(matrix, cellLength) {
492
- // Create DataView to read the buffer
493
- const view = new DataView(matrix.buffer, matrix.byteOffset, matrix.byteLength);
494
- let offset = 0;
495
- // Read number of rows
496
- const numRows = view.getUint32(offset, true); // true for little-endian
497
- offset += 4;
498
- // Initialize result matrix
499
- const result = [];
500
- // Read each row
501
- for (let i = 0; i < numRows; i++) {
502
- // Read row length
503
- const rowLength = view.getUint32(offset, true);
504
- offset += 4;
505
- const row = [];
506
- // Read each cell in the row
507
- for (let j = 0; j < rowLength; j++) {
508
- const notNil = view.getUint8(offset) === 1;
509
- offset += 1;
510
- if (notNil) {
511
- const cell = new Uint8Array(matrix.buffer, matrix.byteOffset + offset, cellLength);
512
- row.push(new Uint8Array(cell));
513
- offset += cellLength;
514
- }
515
- else {
516
- row.push(new Uint8Array());
517
- }
518
- }
519
- result.push(row);
442
+ function encodeMusig2Nonces(nonces) {
443
+ const noncesObject = {};
444
+ for (const [txid, nonce] of nonces) {
445
+ noncesObject[txid] = hex.encode(nonce.pubNonce);
520
446
  }
521
- return result;
522
- }
523
- function decodeNoncesMatrix(matrix) {
524
- const decoded = decodeMatrix(matrix, 66);
525
- return decoded.map((row) => row.map((nonce) => ({ pubNonce: nonce })));
447
+ return JSON.stringify(noncesObject);
526
448
  }
527
- function encodeNoncesMatrix(nonces) {
528
- return hex.encode(encodeMatrix(nonces.map((row) => row.map((nonce) => (nonce ? nonce.pubNonce : new Uint8Array())))));
529
- }
530
- function encodeSignaturesMatrix(signatures) {
531
- return hex.encode(encodeMatrix(signatures.map((row) => row.map((s) => (s ? s.encode() : new Uint8Array())))));
449
+ function encodeMusig2Signatures(signatures) {
450
+ const sigObject = {};
451
+ for (const [txid, sig] of signatures) {
452
+ sigObject[txid] = hex.encode(sig.encode());
453
+ }
454
+ return JSON.stringify(sigObject);
532
455
  }
533
- function convertVtxo(vtxo) {
534
- return {
535
- txid: vtxo.outpoint.txid,
536
- vout: vtxo.outpoint.vout,
537
- value: Number(vtxo.amount),
538
- status: {
539
- confirmed: !!vtxo.roundTxid,
540
- },
541
- virtualStatus: {
542
- state: vtxo.isPending ? "pending" : "settled",
543
- batchTxID: vtxo.roundTxid,
544
- batchExpiry: vtxo.expireAt ? Number(vtxo.expireAt) : undefined,
545
- },
546
- spentBy: vtxo.spentBy,
547
- createdAt: new Date(vtxo.createdAt * 1000),
548
- };
456
+ function decodeMusig2Nonces(str) {
457
+ const noncesObject = JSON.parse(str);
458
+ return new Map(Object.entries(noncesObject).map(([txid, nonce]) => {
459
+ if (typeof nonce !== "string") {
460
+ throw new Error("invalid nonce");
461
+ }
462
+ return [txid, { pubNonce: hex.decode(nonce) }];
463
+ }));
549
464
  }
550
- function isFetchTimeoutError(err) {
465
+ export function isFetchTimeoutError(err) {
551
466
  const checkError = (error) => {
552
467
  return (error instanceof Error &&
553
468
  (error.name === "HeadersTimeoutError" ||
@@ -557,3 +472,23 @@ function isFetchTimeoutError(err) {
557
472
  };
558
473
  return checkError(err) || checkError(err.cause);
559
474
  }
475
+ function mapVtxo(vtxo) {
476
+ return {
477
+ outpoint: {
478
+ txid: vtxo.outpoint.txid,
479
+ vout: vtxo.outpoint.vout,
480
+ },
481
+ amount: vtxo.amount,
482
+ script: vtxo.script,
483
+ createdAt: vtxo.createdAt,
484
+ expiresAt: vtxo.expiresAt,
485
+ commitmentTxids: vtxo.commitmentTxids,
486
+ isPreconfirmed: vtxo.isPreconfirmed,
487
+ isSwept: vtxo.isSwept,
488
+ isUnrolled: vtxo.isUnrolled,
489
+ isSpent: vtxo.isSpent,
490
+ spentBy: vtxo.spentBy,
491
+ settledBy: vtxo.settledBy,
492
+ arkTxid: vtxo.arkTxid,
493
+ };
494
+ }