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