@arkade-os/sdk 0.1.3 → 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.
- package/README.md +156 -174
- package/dist/cjs/arknote/index.js +61 -58
- package/dist/cjs/bip322/errors.js +13 -0
- package/dist/cjs/bip322/index.js +178 -0
- package/dist/cjs/forfeit.js +14 -25
- package/dist/cjs/identity/singleKey.js +68 -0
- package/dist/cjs/index.js +41 -17
- package/dist/cjs/providers/ark.js +253 -317
- package/dist/cjs/providers/indexer.js +525 -0
- package/dist/cjs/providers/onchain.js +193 -15
- package/dist/cjs/script/address.js +48 -17
- package/dist/cjs/script/base.js +120 -3
- package/dist/cjs/script/default.js +18 -4
- package/dist/cjs/script/tapscript.js +46 -14
- package/dist/cjs/script/vhtlc.js +27 -7
- package/dist/cjs/tree/signingSession.js +63 -106
- package/dist/cjs/tree/txTree.js +193 -0
- package/dist/cjs/tree/validation.js +79 -155
- package/dist/cjs/utils/anchor.js +35 -0
- package/dist/cjs/utils/arkTransaction.js +108 -0
- package/dist/cjs/utils/transactionHistory.js +84 -72
- package/dist/cjs/utils/txSizeEstimator.js +12 -0
- package/dist/cjs/utils/unknownFields.js +211 -0
- package/dist/cjs/wallet/index.js +12 -0
- package/dist/cjs/wallet/onchain.js +201 -0
- package/dist/cjs/wallet/ramps.js +95 -0
- package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +32 -0
- package/dist/cjs/wallet/serviceWorker/request.js +15 -12
- package/dist/cjs/wallet/serviceWorker/response.js +22 -27
- package/dist/cjs/wallet/serviceWorker/utils.js +8 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +58 -34
- package/dist/cjs/wallet/serviceWorker/worker.js +117 -108
- package/dist/cjs/wallet/unroll.js +270 -0
- package/dist/cjs/wallet/wallet.js +701 -459
- package/dist/esm/arknote/index.js +61 -57
- package/dist/esm/bip322/errors.js +9 -0
- package/dist/esm/bip322/index.js +174 -0
- package/dist/esm/forfeit.js +15 -26
- package/dist/esm/identity/singleKey.js +64 -0
- package/dist/esm/index.js +30 -12
- package/dist/esm/providers/ark.js +252 -317
- package/dist/esm/providers/indexer.js +521 -0
- package/dist/esm/providers/onchain.js +193 -15
- package/dist/esm/script/address.js +48 -17
- package/dist/esm/script/base.js +120 -3
- package/dist/esm/script/default.js +18 -4
- package/dist/esm/script/tapscript.js +46 -14
- package/dist/esm/script/vhtlc.js +27 -7
- package/dist/esm/tree/signingSession.js +65 -108
- package/dist/esm/tree/txTree.js +189 -0
- package/dist/esm/tree/validation.js +75 -152
- package/dist/esm/utils/anchor.js +31 -0
- package/dist/esm/utils/arkTransaction.js +105 -0
- package/dist/esm/utils/transactionHistory.js +84 -72
- package/dist/esm/utils/txSizeEstimator.js +12 -0
- package/dist/esm/utils/unknownFields.js +173 -0
- package/dist/esm/wallet/index.js +9 -0
- package/dist/esm/wallet/onchain.js +196 -0
- package/dist/esm/wallet/ramps.js +91 -0
- package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +32 -0
- package/dist/esm/wallet/serviceWorker/request.js +15 -12
- package/dist/esm/wallet/serviceWorker/response.js +22 -27
- package/dist/esm/wallet/serviceWorker/utils.js +8 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +59 -35
- package/dist/esm/wallet/serviceWorker/worker.js +117 -108
- package/dist/esm/wallet/unroll.js +267 -0
- package/dist/esm/wallet/wallet.js +674 -466
- package/dist/types/arknote/index.d.ts +40 -13
- package/dist/types/bip322/errors.d.ts +6 -0
- package/dist/types/bip322/index.d.ts +57 -0
- package/dist/types/forfeit.d.ts +2 -14
- package/dist/types/identity/singleKey.d.ts +27 -0
- package/dist/types/index.d.ts +23 -12
- package/dist/types/providers/ark.d.ts +114 -95
- package/dist/types/providers/indexer.d.ts +186 -0
- package/dist/types/providers/onchain.d.ts +41 -11
- package/dist/types/script/address.d.ts +26 -2
- package/dist/types/script/base.d.ts +13 -3
- package/dist/types/script/default.d.ts +22 -0
- package/dist/types/script/tapscript.d.ts +61 -5
- package/dist/types/script/vhtlc.d.ts +27 -0
- package/dist/types/tree/signingSession.d.ts +5 -5
- package/dist/types/tree/txTree.d.ts +28 -0
- package/dist/types/tree/validation.d.ts +15 -22
- package/dist/types/utils/anchor.d.ts +19 -0
- package/dist/types/utils/arkTransaction.d.ts +27 -0
- package/dist/types/utils/transactionHistory.d.ts +7 -1
- package/dist/types/utils/txSizeEstimator.d.ts +3 -0
- package/dist/types/utils/unknownFields.d.ts +83 -0
- package/dist/types/wallet/index.d.ts +51 -50
- package/dist/types/wallet/onchain.d.ts +49 -0
- package/dist/types/wallet/ramps.d.ts +32 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +2 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +2 -0
- package/dist/types/wallet/serviceWorker/request.d.ts +14 -16
- package/dist/types/wallet/serviceWorker/response.d.ts +17 -19
- package/dist/types/wallet/serviceWorker/utils.d.ts +8 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +36 -8
- package/dist/types/wallet/serviceWorker/worker.d.ts +7 -3
- package/dist/types/wallet/unroll.d.ts +102 -0
- package/dist/types/wallet/wallet.d.ts +71 -26
- package/package.json +14 -15
- package/dist/cjs/identity/inMemoryKey.js +0 -40
- package/dist/cjs/tree/vtxoTree.js +0 -231
- package/dist/cjs/utils/coinselect.js +0 -73
- package/dist/cjs/utils/psbt.js +0 -137
- package/dist/esm/identity/inMemoryKey.js +0 -36
- package/dist/esm/tree/vtxoTree.js +0 -191
- package/dist/esm/utils/coinselect.js +0 -69
- package/dist/esm/utils/psbt.js +0 -131
- package/dist/types/identity/inMemoryKey.d.ts +0 -12
- package/dist/types/tree/vtxoTree.d.ts +0 -33
- package/dist/types/utils/coinselect.d.ts +0 -21
- 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["
|
|
6
|
-
SettlementEventType["
|
|
7
|
-
SettlementEventType["
|
|
8
|
-
SettlementEventType["
|
|
9
|
-
SettlementEventType["
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
58
|
-
const url = `${this.serverUrl}/v1/
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
145
|
-
const url = `${this.serverUrl}/v1/
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
|
119
|
+
throw new Error(`Failed to register intent: ${errorText}`);
|
|
177
120
|
}
|
|
178
121
|
const data = await response.json();
|
|
179
|
-
return
|
|
122
|
+
return data.intentId;
|
|
180
123
|
}
|
|
181
|
-
async
|
|
182
|
-
const url = `${this.serverUrl}/v1/
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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(
|
|
206
|
-
const url = `${this.serverUrl}/v1/
|
|
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
|
-
|
|
167
|
+
batchId,
|
|
214
168
|
pubkey,
|
|
215
|
-
treeNonces:
|
|
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(
|
|
224
|
-
const url = `${this.serverUrl}/v1/
|
|
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
|
-
|
|
185
|
+
batchId,
|
|
232
186
|
pubkey,
|
|
233
|
-
treeSignatures:
|
|
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,
|
|
242
|
-
const url = `${this.serverUrl}/v1/
|
|
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
|
-
|
|
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
|
|
258
|
-
const url = `${this.serverUrl}/v1/
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
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 *
|
|
322
|
-
const url = `${this.serverUrl}/v1/
|
|
323
|
-
while (!
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
402
|
-
if (data.
|
|
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.
|
|
405
|
-
id: data.
|
|
406
|
-
|
|
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
|
|
416
|
-
if (data.
|
|
368
|
+
// Check for TreeSigningStarted event
|
|
369
|
+
if (data.treeSigningStarted) {
|
|
417
370
|
return {
|
|
418
|
-
type: SettlementEventType.
|
|
419
|
-
id: data.
|
|
420
|
-
|
|
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
|
|
424
|
-
if (data.
|
|
377
|
+
// Check for TreeNoncesAggregated event
|
|
378
|
+
if (data.treeNoncesAggregated) {
|
|
425
379
|
return {
|
|
426
|
-
type: SettlementEventType.
|
|
427
|
-
id: data.
|
|
428
|
-
|
|
380
|
+
type: SettlementEventType.TreeNoncesAggregated,
|
|
381
|
+
id: data.treeNoncesAggregated.id,
|
|
382
|
+
treeNonces: decodeMusig2Nonces(data.treeNoncesAggregated.treeNonces),
|
|
429
383
|
};
|
|
430
384
|
}
|
|
431
|
-
// Check for
|
|
432
|
-
if (data.
|
|
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.
|
|
435
|
-
id: data.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
442
|
-
if (data.roundSigningNoncesGenerated) {
|
|
402
|
+
if (data.treeSignature) {
|
|
443
403
|
return {
|
|
444
|
-
type: SettlementEventType.
|
|
445
|
-
id: data.
|
|
446
|
-
|
|
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
|
|
412
|
+
console.warn("Unknown event type:", data);
|
|
450
413
|
return null;
|
|
451
414
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
|
|
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
|
|
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
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
+
}
|