@arkade-os/sdk 0.3.0-alpha.7 → 0.3.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 +99 -14
- package/dist/cjs/adapters/expo.js +8 -0
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +2 -2
- package/dist/cjs/identity/singleKey.js +8 -8
- package/dist/cjs/index.js +14 -5
- package/dist/cjs/{bip322 → intent}/index.js +38 -61
- package/dist/cjs/musig2/index.js +2 -1
- package/dist/cjs/musig2/nonces.js +4 -0
- package/dist/cjs/providers/ark.js +76 -45
- package/dist/cjs/providers/errors.js +59 -0
- package/dist/cjs/providers/expoArk.js +82 -0
- package/dist/cjs/providers/expoIndexer.js +105 -0
- package/dist/cjs/providers/expoUtils.js +124 -0
- package/dist/cjs/providers/indexer.js +3 -1
- package/dist/cjs/providers/onchain.js +19 -20
- package/dist/cjs/repositories/walletRepository.js +64 -28
- package/dist/cjs/script/base.js +15 -7
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +44 -11
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +118 -15
- package/dist/cjs/utils/transaction.js +28 -0
- package/dist/cjs/utils/unknownFields.js +7 -7
- package/dist/cjs/wallet/index.js +1 -1
- package/dist/cjs/wallet/onchain.js +6 -7
- package/dist/cjs/wallet/serviceWorker/response.js +32 -0
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
- package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +48 -32
- package/dist/cjs/wallet/unroll.js +7 -9
- package/dist/cjs/wallet/utils.js +20 -0
- package/dist/cjs/wallet/vtxo-manager.js +323 -0
- package/dist/cjs/wallet/wallet.js +165 -174
- package/dist/esm/adapters/expo.js +3 -0
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +1 -1
- package/dist/esm/identity/singleKey.js +9 -9
- package/dist/esm/index.js +14 -10
- package/dist/esm/{bip322 → intent}/index.js +32 -54
- package/dist/esm/musig2/index.js +1 -1
- package/dist/esm/musig2/nonces.js +3 -0
- package/dist/esm/providers/ark.js +76 -45
- package/dist/esm/providers/errors.js +54 -0
- package/dist/esm/providers/expoArk.js +78 -0
- package/dist/esm/providers/expoIndexer.js +101 -0
- package/dist/esm/providers/expoUtils.js +87 -0
- package/dist/esm/providers/indexer.js +3 -1
- package/dist/esm/providers/onchain.js +19 -20
- package/dist/esm/repositories/walletRepository.js +64 -28
- package/dist/esm/script/base.js +12 -4
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +45 -12
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +110 -9
- package/dist/esm/utils/transaction.js +24 -0
- package/dist/esm/utils/unknownFields.js +3 -3
- package/dist/esm/wallet/index.js +1 -1
- package/dist/esm/wallet/onchain.js +3 -4
- package/dist/esm/wallet/serviceWorker/response.js +32 -0
- package/dist/esm/wallet/serviceWorker/utils.js +1 -8
- package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
- package/dist/esm/wallet/serviceWorker/worker.js +49 -33
- package/dist/esm/wallet/unroll.js +5 -7
- package/dist/esm/wallet/utils.js +16 -0
- package/dist/esm/wallet/vtxo-manager.js +317 -0
- package/dist/esm/wallet/wallet.js +159 -168
- package/dist/types/adapters/expo.d.ts +4 -0
- package/dist/types/arknote/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +2 -2
- package/dist/types/identity/index.d.ts +2 -2
- package/dist/types/identity/singleKey.d.ts +2 -2
- package/dist/types/index.d.ts +11 -9
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/musig2/index.d.ts +1 -1
- package/dist/types/musig2/nonces.d.ts +1 -0
- package/dist/types/providers/ark.d.ts +197 -27
- package/dist/types/providers/errors.d.ts +13 -0
- package/dist/types/providers/expoArk.d.ts +22 -0
- package/dist/types/providers/expoIndexer.d.ts +18 -0
- package/dist/types/providers/expoUtils.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +8 -8
- package/dist/types/providers/onchain.d.ts +6 -2
- package/dist/types/repositories/walletRepository.d.ts +9 -5
- package/dist/types/script/base.d.ts +5 -2
- package/dist/types/tree/signingSession.d.ts +16 -11
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +15 -5
- package/dist/types/utils/transaction.d.ts +13 -0
- package/dist/types/utils/unknownFields.d.ts +4 -4
- package/dist/types/wallet/index.d.ts +47 -7
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
- package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +3 -0
- package/dist/types/wallet/vtxo-manager.d.ts +179 -0
- package/dist/types/wallet/wallet.d.ts +17 -5
- package/package.json +11 -3
- package/dist/cjs/bip322/errors.js +0 -13
- package/dist/esm/bip322/errors.js +0 -9
- package/dist/types/bip322/errors.d.ts +0 -6
- package/dist/types/bip322/index.d.ts +0 -57
|
@@ -4,6 +4,7 @@ exports.RestArkProvider = exports.SettlementEventType = void 0;
|
|
|
4
4
|
exports.isFetchTimeoutError = isFetchTimeoutError;
|
|
5
5
|
const base_1 = require("@scure/base");
|
|
6
6
|
const utils_1 = require("./utils");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
7
8
|
var SettlementEventType;
|
|
8
9
|
(function (SettlementEventType) {
|
|
9
10
|
SettlementEventType["BatchStarted"] = "batch_started";
|
|
@@ -11,7 +12,7 @@ var SettlementEventType;
|
|
|
11
12
|
SettlementEventType["BatchFinalized"] = "batch_finalized";
|
|
12
13
|
SettlementEventType["BatchFailed"] = "batch_failed";
|
|
13
14
|
SettlementEventType["TreeSigningStarted"] = "tree_signing_started";
|
|
14
|
-
SettlementEventType["
|
|
15
|
+
SettlementEventType["TreeNonces"] = "tree_nonces";
|
|
15
16
|
SettlementEventType["TreeTx"] = "tree_tx";
|
|
16
17
|
SettlementEventType["TreeSignature"] = "tree_signature";
|
|
17
18
|
})(SettlementEventType || (exports.SettlementEventType = SettlementEventType = {}));
|
|
@@ -32,29 +33,41 @@ class RestArkProvider {
|
|
|
32
33
|
const url = `${this.serverUrl}/v1/info`;
|
|
33
34
|
const response = await fetch(url);
|
|
34
35
|
if (!response.ok) {
|
|
35
|
-
|
|
36
|
+
const errorText = await response.text();
|
|
37
|
+
handleError(errorText, `Failed to get server info: ${response.statusText}`);
|
|
36
38
|
}
|
|
37
39
|
const fromServer = await response.json();
|
|
38
40
|
return {
|
|
39
|
-
...fromServer,
|
|
40
|
-
vtxoTreeExpiry: BigInt(fromServer.vtxoTreeExpiry ?? 0),
|
|
41
|
-
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
42
|
-
roundInterval: BigInt(fromServer.roundInterval ?? 0),
|
|
43
|
-
dust: BigInt(fromServer.dust ?? 0),
|
|
44
|
-
utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
|
|
45
|
-
utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
|
|
46
|
-
vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
|
|
47
|
-
vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
|
|
48
41
|
boardingExitDelay: BigInt(fromServer.boardingExitDelay ?? 0),
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
checkpointTapscript: fromServer.checkpointTapscript ?? "",
|
|
43
|
+
deprecatedSigners: fromServer.deprecatedSigners?.map((signer) => ({
|
|
44
|
+
cutoffDate: BigInt(signer.cutoffDate ?? 0),
|
|
45
|
+
pubkey: signer.pubkey ?? "",
|
|
46
|
+
})) ?? [],
|
|
47
|
+
digest: fromServer.digest ?? "",
|
|
48
|
+
dust: BigInt(fromServer.dust ?? 0),
|
|
49
|
+
fees: fromServer.fees,
|
|
50
|
+
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
51
|
+
forfeitPubkey: fromServer.forfeitPubkey ?? "",
|
|
52
|
+
network: fromServer.network ?? "",
|
|
53
|
+
scheduledSession: "scheduledSession" in fromServer &&
|
|
54
|
+
fromServer.scheduledSession != null
|
|
51
55
|
? {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
duration: BigInt(fromServer.scheduledSession.duration ?? 0),
|
|
57
|
+
nextStartTime: BigInt(fromServer.scheduledSession.nextStartTime ?? 0),
|
|
58
|
+
nextEndTime: BigInt(fromServer.scheduledSession.nextEndTime ?? 0),
|
|
59
|
+
period: BigInt(fromServer.scheduledSession.period ?? 0),
|
|
56
60
|
}
|
|
57
61
|
: undefined,
|
|
62
|
+
serviceStatus: fromServer.serviceStatus ?? {},
|
|
63
|
+
sessionDuration: BigInt(fromServer.sessionDuration ?? 0),
|
|
64
|
+
signerPubkey: fromServer.signerPubkey ?? "",
|
|
65
|
+
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
66
|
+
utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
|
|
67
|
+
utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
|
|
68
|
+
version: fromServer.version ?? "",
|
|
69
|
+
vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
|
|
70
|
+
vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
|
|
58
71
|
};
|
|
59
72
|
}
|
|
60
73
|
async submitTx(signedArkTx, checkpointTxs) {
|
|
@@ -65,22 +78,13 @@ class RestArkProvider {
|
|
|
65
78
|
"Content-Type": "application/json",
|
|
66
79
|
},
|
|
67
80
|
body: JSON.stringify({
|
|
68
|
-
signedArkTx
|
|
69
|
-
checkpointTxs
|
|
81
|
+
signedArkTx,
|
|
82
|
+
checkpointTxs,
|
|
70
83
|
}),
|
|
71
84
|
});
|
|
72
85
|
if (!response.ok) {
|
|
73
86
|
const errorText = await response.text();
|
|
74
|
-
|
|
75
|
-
const grpcError = JSON.parse(errorText);
|
|
76
|
-
// gRPC errors usually have a message and code field
|
|
77
|
-
throw new Error(`Failed to submit virtual transaction: ${grpcError.message || grpcError.error || errorText}`);
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
79
|
-
}
|
|
80
|
-
catch (_) {
|
|
81
|
-
// If JSON parse fails, use the raw error text
|
|
82
|
-
throw new Error(`Failed to submit virtual transaction: ${errorText}`);
|
|
83
|
-
}
|
|
87
|
+
handleError(errorText, `Failed to submit virtual transaction: ${errorText}`);
|
|
84
88
|
}
|
|
85
89
|
const data = await response.json();
|
|
86
90
|
return {
|
|
@@ -103,7 +107,7 @@ class RestArkProvider {
|
|
|
103
107
|
});
|
|
104
108
|
if (!response.ok) {
|
|
105
109
|
const errorText = await response.text();
|
|
106
|
-
|
|
110
|
+
handleError(errorText, `Failed to finalize offchain transaction: ${errorText}`);
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
async registerIntent(intent) {
|
|
@@ -115,14 +119,14 @@ class RestArkProvider {
|
|
|
115
119
|
},
|
|
116
120
|
body: JSON.stringify({
|
|
117
121
|
intent: {
|
|
118
|
-
|
|
122
|
+
proof: intent.proof,
|
|
119
123
|
message: intent.message,
|
|
120
124
|
},
|
|
121
125
|
}),
|
|
122
126
|
});
|
|
123
127
|
if (!response.ok) {
|
|
124
128
|
const errorText = await response.text();
|
|
125
|
-
|
|
129
|
+
handleError(errorText, `Failed to register intent: ${errorText}`);
|
|
126
130
|
}
|
|
127
131
|
const data = await response.json();
|
|
128
132
|
return data.intentId;
|
|
@@ -136,14 +140,14 @@ class RestArkProvider {
|
|
|
136
140
|
},
|
|
137
141
|
body: JSON.stringify({
|
|
138
142
|
proof: {
|
|
139
|
-
|
|
143
|
+
proof: intent.proof,
|
|
140
144
|
message: intent.message,
|
|
141
145
|
},
|
|
142
146
|
}),
|
|
143
147
|
});
|
|
144
148
|
if (!response.ok) {
|
|
145
149
|
const errorText = await response.text();
|
|
146
|
-
|
|
150
|
+
handleError(errorText, `Failed to delete intent: ${errorText}`);
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
async confirmRegistration(intentId) {
|
|
@@ -159,7 +163,7 @@ class RestArkProvider {
|
|
|
159
163
|
});
|
|
160
164
|
if (!response.ok) {
|
|
161
165
|
const errorText = await response.text();
|
|
162
|
-
|
|
166
|
+
handleError(errorText, `Failed to confirm registration: ${errorText}`);
|
|
163
167
|
}
|
|
164
168
|
}
|
|
165
169
|
async submitTreeNonces(batchId, pubkey, nonces) {
|
|
@@ -177,7 +181,7 @@ class RestArkProvider {
|
|
|
177
181
|
});
|
|
178
182
|
if (!response.ok) {
|
|
179
183
|
const errorText = await response.text();
|
|
180
|
-
|
|
184
|
+
handleError(errorText, `Failed to submit tree nonces: ${errorText}`);
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
async submitTreeSignatures(batchId, pubkey, signatures) {
|
|
@@ -195,7 +199,7 @@ class RestArkProvider {
|
|
|
195
199
|
});
|
|
196
200
|
if (!response.ok) {
|
|
197
201
|
const errorText = await response.text();
|
|
198
|
-
|
|
202
|
+
handleError(errorText, `Failed to submit tree signatures: ${errorText}`);
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
async submitSignedForfeitTxs(signedForfeitTxs, signedCommitmentTx) {
|
|
@@ -211,7 +215,8 @@ class RestArkProvider {
|
|
|
211
215
|
}),
|
|
212
216
|
});
|
|
213
217
|
if (!response.ok) {
|
|
214
|
-
|
|
218
|
+
const errorText = await response.text();
|
|
219
|
+
handleError(errorText, `Failed to submit forfeit transactions: ${response.statusText}`);
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
async *getEventStream(signal, topics) {
|
|
@@ -309,6 +314,22 @@ class RestArkProvider {
|
|
|
309
314
|
}
|
|
310
315
|
}
|
|
311
316
|
}
|
|
317
|
+
async getPendingTxs(intent) {
|
|
318
|
+
const url = `${this.serverUrl}/v1/tx/pending`;
|
|
319
|
+
const response = await fetch(url, {
|
|
320
|
+
method: "POST",
|
|
321
|
+
headers: {
|
|
322
|
+
"Content-Type": "application/json",
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({ intent }),
|
|
325
|
+
});
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
const errorText = await response.text();
|
|
328
|
+
handleError(errorText, `Failed to get pending transactions: ${errorText}`);
|
|
329
|
+
}
|
|
330
|
+
const data = await response.json();
|
|
331
|
+
return data.pendingTxs;
|
|
332
|
+
}
|
|
312
333
|
parseSettlementEvent(data) {
|
|
313
334
|
// Check for BatchStarted event
|
|
314
335
|
if (data.batchStarted) {
|
|
@@ -354,10 +375,16 @@ class RestArkProvider {
|
|
|
354
375
|
}
|
|
355
376
|
// Check for TreeNoncesAggregated event
|
|
356
377
|
if (data.treeNoncesAggregated) {
|
|
378
|
+
// skip treeNoncesAggregated event, deprecated
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
if (data.treeNonces) {
|
|
357
382
|
return {
|
|
358
|
-
type: SettlementEventType.
|
|
359
|
-
id: data.
|
|
360
|
-
|
|
383
|
+
type: SettlementEventType.TreeNonces,
|
|
384
|
+
id: data.treeNonces.id,
|
|
385
|
+
topic: data.treeNonces.topic,
|
|
386
|
+
txid: data.treeNonces.txid,
|
|
387
|
+
nonces: decodeMusig2Nonces(data.treeNonces.nonces), // pubkey -> public nonce
|
|
361
388
|
};
|
|
362
389
|
}
|
|
363
390
|
// Check for TreeTx event
|
|
@@ -431,17 +458,16 @@ function encodeMusig2Nonces(nonces) {
|
|
|
431
458
|
for (const [txid, nonce] of nonces) {
|
|
432
459
|
noncesObject[txid] = base_1.hex.encode(nonce.pubNonce);
|
|
433
460
|
}
|
|
434
|
-
return
|
|
461
|
+
return noncesObject;
|
|
435
462
|
}
|
|
436
463
|
function encodeMusig2Signatures(signatures) {
|
|
437
464
|
const sigObject = {};
|
|
438
465
|
for (const [txid, sig] of signatures) {
|
|
439
466
|
sigObject[txid] = base_1.hex.encode(sig.encode());
|
|
440
467
|
}
|
|
441
|
-
return
|
|
468
|
+
return sigObject;
|
|
442
469
|
}
|
|
443
|
-
function decodeMusig2Nonces(
|
|
444
|
-
const noncesObject = JSON.parse(str);
|
|
470
|
+
function decodeMusig2Nonces(noncesObject) {
|
|
445
471
|
return new Map(Object.entries(noncesObject).map(([txid, nonce]) => {
|
|
446
472
|
if (typeof nonce !== "string") {
|
|
447
473
|
throw new Error("invalid nonce");
|
|
@@ -483,3 +509,8 @@ function mapVtxo(vtxo) {
|
|
|
483
509
|
arkTxid: vtxo.arkTxid,
|
|
484
510
|
};
|
|
485
511
|
}
|
|
512
|
+
function handleError(errorText, defaultMessage) {
|
|
513
|
+
const error = new Error(errorText);
|
|
514
|
+
const arkError = (0, errors_1.maybeArkError)(error);
|
|
515
|
+
throw arkError ?? new Error(defaultMessage);
|
|
516
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ArkError = void 0;
|
|
4
|
+
exports.maybeArkError = maybeArkError;
|
|
5
|
+
class ArkError extends Error {
|
|
6
|
+
constructor(code, message, name, metadata) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.message = message;
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.metadata = metadata;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.ArkError = ArkError;
|
|
15
|
+
/**
|
|
16
|
+
* Try to convert an error to an ArkError class, returning undefined if the error is not an ArkError
|
|
17
|
+
* @param error - The error to parse
|
|
18
|
+
* @returns The parsed ArkError, or undefined if the error is not an ArkError
|
|
19
|
+
*/
|
|
20
|
+
function maybeArkError(error) {
|
|
21
|
+
try {
|
|
22
|
+
if (!(error instanceof Error))
|
|
23
|
+
return undefined;
|
|
24
|
+
const decoded = JSON.parse(error.message);
|
|
25
|
+
if (!("details" in decoded))
|
|
26
|
+
return undefined;
|
|
27
|
+
if (!Array.isArray(decoded.details))
|
|
28
|
+
return undefined;
|
|
29
|
+
// search for a valid details object with the correct type
|
|
30
|
+
for (const details of decoded.details) {
|
|
31
|
+
if (!("@type" in details))
|
|
32
|
+
continue;
|
|
33
|
+
const type = details["@type"];
|
|
34
|
+
if (type !== "type.googleapis.com/ark.v1.ErrorDetails")
|
|
35
|
+
continue;
|
|
36
|
+
if (!("code" in details))
|
|
37
|
+
continue;
|
|
38
|
+
const code = details.code;
|
|
39
|
+
if (!("message" in details))
|
|
40
|
+
continue;
|
|
41
|
+
const message = details.message;
|
|
42
|
+
if (!("name" in details))
|
|
43
|
+
continue;
|
|
44
|
+
const name = details.name;
|
|
45
|
+
let metadata;
|
|
46
|
+
if ("metadata" in details && isMetadata(details.metadata)) {
|
|
47
|
+
metadata = details.metadata;
|
|
48
|
+
}
|
|
49
|
+
return new ArkError(code, message, name, metadata);
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isMetadata(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
59
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExpoArkProvider = void 0;
|
|
4
|
+
const ark_1 = require("./ark");
|
|
5
|
+
const expoUtils_1 = require("./expoUtils");
|
|
6
|
+
/**
|
|
7
|
+
* Expo-compatible Ark provider implementation using expo/fetch for SSE support.
|
|
8
|
+
* This provider works specifically in React Native/Expo environments where
|
|
9
|
+
* standard EventSource is not available but expo/fetch provides SSE capabilities.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { ExpoArkProvider } from '@arkade-os/sdk/providers/expo';
|
|
14
|
+
*
|
|
15
|
+
* const provider = new ExpoArkProvider('https://ark.example.com');
|
|
16
|
+
* const info = await provider.getInfo();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
class ExpoArkProvider extends ark_1.RestArkProvider {
|
|
20
|
+
constructor(serverUrl) {
|
|
21
|
+
super(serverUrl);
|
|
22
|
+
}
|
|
23
|
+
async *getEventStream(signal, topics) {
|
|
24
|
+
const expoFetch = await (0, expoUtils_1.getExpoFetch)();
|
|
25
|
+
const url = `${this.serverUrl}/v1/batch/events`;
|
|
26
|
+
const queryParams = topics.length > 0
|
|
27
|
+
? `?${topics.map((topic) => `topics=${encodeURIComponent(topic)}`).join("&")}`
|
|
28
|
+
: "";
|
|
29
|
+
while (!signal?.aborted) {
|
|
30
|
+
try {
|
|
31
|
+
yield* (0, expoUtils_1.sseStreamIterator)(url + queryParams, signal, expoFetch, {}, (data) => {
|
|
32
|
+
// Handle different response structures
|
|
33
|
+
// v8 mesh API might wrap in {result: ...} or send directly
|
|
34
|
+
const eventData = data.result || data;
|
|
35
|
+
// Skip heartbeat messages
|
|
36
|
+
if (eventData.heartbeat !== undefined) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return this.parseSettlementEvent(eventData);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
47
|
+
// these timeouts are set by expo/fetch function
|
|
48
|
+
if ((0, ark_1.isFetchTimeoutError)(error)) {
|
|
49
|
+
console.debug("Timeout error ignored");
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
console.error("Event stream error:", error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async *getTransactionsStream(signal) {
|
|
58
|
+
const expoFetch = await (0, expoUtils_1.getExpoFetch)();
|
|
59
|
+
const url = `${this.serverUrl}/v1/txs`;
|
|
60
|
+
while (!signal?.aborted) {
|
|
61
|
+
try {
|
|
62
|
+
yield* (0, expoUtils_1.sseStreamIterator)(url, signal, expoFetch, {}, (data) => {
|
|
63
|
+
return this.parseTransactionNotification(data.result);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
71
|
+
// these timeouts are set by expo/fetch function
|
|
72
|
+
if ((0, ark_1.isFetchTimeoutError)(error)) {
|
|
73
|
+
console.debug("Timeout error ignored");
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
console.error("Transaction stream error:", error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.ExpoArkProvider = ExpoArkProvider;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExpoIndexerProvider = void 0;
|
|
4
|
+
const indexer_1 = require("./indexer");
|
|
5
|
+
const ark_1 = require("./ark");
|
|
6
|
+
const expoUtils_1 = require("./expoUtils");
|
|
7
|
+
// Helper function to convert Vtxo to VirtualCoin (same as in indexer.ts)
|
|
8
|
+
function convertVtxo(vtxo) {
|
|
9
|
+
return {
|
|
10
|
+
txid: vtxo.outpoint.txid,
|
|
11
|
+
vout: vtxo.outpoint.vout,
|
|
12
|
+
value: Number(vtxo.amount),
|
|
13
|
+
status: {
|
|
14
|
+
confirmed: !vtxo.isSwept && !vtxo.isPreconfirmed,
|
|
15
|
+
},
|
|
16
|
+
virtualStatus: {
|
|
17
|
+
state: vtxo.isSwept
|
|
18
|
+
? "swept"
|
|
19
|
+
: vtxo.isPreconfirmed
|
|
20
|
+
? "preconfirmed"
|
|
21
|
+
: "settled",
|
|
22
|
+
commitmentTxIds: vtxo.commitmentTxids,
|
|
23
|
+
batchExpiry: vtxo.expiresAt
|
|
24
|
+
? Number(vtxo.expiresAt) * 1000
|
|
25
|
+
: undefined,
|
|
26
|
+
},
|
|
27
|
+
spentBy: vtxo.spentBy ?? "",
|
|
28
|
+
settledBy: vtxo.settledBy,
|
|
29
|
+
arkTxId: vtxo.arkTxid,
|
|
30
|
+
createdAt: new Date(Number(vtxo.createdAt) * 1000),
|
|
31
|
+
isUnrolled: vtxo.isUnrolled,
|
|
32
|
+
isSpent: vtxo.isSpent,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Expo-compatible Indexer provider implementation using expo/fetch for streaming support.
|
|
37
|
+
* This provider works specifically in React Native/Expo environments where
|
|
38
|
+
* standard fetch streaming may not work properly but expo/fetch provides streaming capabilities.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo';
|
|
43
|
+
*
|
|
44
|
+
* const provider = new ExpoIndexerProvider('https://indexer.example.com');
|
|
45
|
+
* const vtxos = await provider.getVtxos({ scripts: ['script1'] });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
class ExpoIndexerProvider extends indexer_1.RestIndexerProvider {
|
|
49
|
+
constructor(serverUrl) {
|
|
50
|
+
super(serverUrl);
|
|
51
|
+
}
|
|
52
|
+
async *getSubscription(subscriptionId, abortSignal) {
|
|
53
|
+
// Detect if we're running in React Native/Expo environment
|
|
54
|
+
const isReactNative = typeof navigator !== "undefined" &&
|
|
55
|
+
navigator.product === "ReactNative";
|
|
56
|
+
const expoFetch = await (0, expoUtils_1.getExpoFetch)().catch((error) => {
|
|
57
|
+
// In React Native/Expo, expo/fetch is required for proper streaming support
|
|
58
|
+
if (isReactNative) {
|
|
59
|
+
throw new Error("expo/fetch is unavailable in React Native environment. " +
|
|
60
|
+
"Please ensure expo/fetch is installed and properly configured. " +
|
|
61
|
+
"Streaming support may not work with standard fetch in React Native.");
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
});
|
|
65
|
+
const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
|
|
66
|
+
while (!abortSignal.aborted) {
|
|
67
|
+
try {
|
|
68
|
+
yield* (0, expoUtils_1.sseStreamIterator)(url, abortSignal, expoFetch, { "Content-Type": "application/json" }, (data) => {
|
|
69
|
+
// Handle new v8 proto format with heartbeat or event
|
|
70
|
+
if (data.heartbeat !== undefined) {
|
|
71
|
+
// Skip heartbeat messages
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Process event messages
|
|
75
|
+
if (data.event) {
|
|
76
|
+
return {
|
|
77
|
+
txid: data.event.txid,
|
|
78
|
+
scripts: data.event.scripts || [],
|
|
79
|
+
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
80
|
+
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
81
|
+
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
82
|
+
tx: data.event.tx,
|
|
83
|
+
checkpointTxs: data.event.checkpointTxs,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
94
|
+
// these timeouts are set by expo/fetch function
|
|
95
|
+
if ((0, ark_1.isFetchTimeoutError)(error)) {
|
|
96
|
+
console.debug("Timeout error ignored");
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
console.error("Subscription error:", error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.ExpoIndexerProvider = ExpoIndexerProvider;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getExpoFetch = getExpoFetch;
|
|
37
|
+
exports.sseStreamIterator = sseStreamIterator;
|
|
38
|
+
/**
|
|
39
|
+
* Dynamically imports expo/fetch with fallback to standard fetch.
|
|
40
|
+
* @returns A fetch function suitable for SSE streaming
|
|
41
|
+
*/
|
|
42
|
+
async function getExpoFetch(options) {
|
|
43
|
+
const requireExpo = options?.requireExpo ?? false;
|
|
44
|
+
try {
|
|
45
|
+
const expoFetchModule = await Promise.resolve().then(() => __importStar(require("expo/fetch")));
|
|
46
|
+
console.debug("Using expo/fetch for streaming");
|
|
47
|
+
return expoFetchModule.fetch;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (requireExpo) {
|
|
51
|
+
throw new Error("expo/fetch is unavailable in this environment. " +
|
|
52
|
+
"Please ensure expo/fetch is installed and properly configured.");
|
|
53
|
+
}
|
|
54
|
+
console.warn("Using standard fetch instead of expo/fetch. " +
|
|
55
|
+
"Streaming may not be fully supported in some environments.", error);
|
|
56
|
+
return fetch;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Generic SSE stream processor using fetch API with ReadableStream.
|
|
61
|
+
* Handles SSE format parsing, buffer management, and abort signals.
|
|
62
|
+
*
|
|
63
|
+
* @param url - The SSE endpoint URL
|
|
64
|
+
* @param abortSignal - Signal to abort the stream
|
|
65
|
+
* @param fetchFn - Fetch function to use (defaults to standard fetch)
|
|
66
|
+
* @param headers - Additional headers to send
|
|
67
|
+
* @param parseData - Function to parse and yield data from SSE events
|
|
68
|
+
*/
|
|
69
|
+
async function* sseStreamIterator(url, abortSignal, fetchFn, headers, parseData) {
|
|
70
|
+
const fetchController = new AbortController();
|
|
71
|
+
const cleanup = () => fetchController.abort();
|
|
72
|
+
abortSignal?.addEventListener("abort", cleanup, { once: true });
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetchFn(url, {
|
|
75
|
+
headers: {
|
|
76
|
+
Accept: "text/event-stream",
|
|
77
|
+
...headers,
|
|
78
|
+
},
|
|
79
|
+
signal: fetchController.signal,
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`Unexpected status ${response.status} when fetching SSE stream`);
|
|
83
|
+
}
|
|
84
|
+
if (!response.body) {
|
|
85
|
+
throw new Error("Response body is null");
|
|
86
|
+
}
|
|
87
|
+
const reader = response.body.getReader();
|
|
88
|
+
const decoder = new TextDecoder();
|
|
89
|
+
let buffer = "";
|
|
90
|
+
while (!abortSignal?.aborted) {
|
|
91
|
+
const { done, value } = await reader.read();
|
|
92
|
+
if (done) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
buffer += decoder.decode(value, { stream: true });
|
|
96
|
+
const lines = buffer.split("\n");
|
|
97
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
98
|
+
const line = lines[i].trim();
|
|
99
|
+
if (!line)
|
|
100
|
+
continue;
|
|
101
|
+
if (line.startsWith("data:")) {
|
|
102
|
+
const jsonStr = line.substring(5).trim();
|
|
103
|
+
if (!jsonStr)
|
|
104
|
+
continue;
|
|
105
|
+
try {
|
|
106
|
+
const data = JSON.parse(jsonStr);
|
|
107
|
+
const parsed = parseData(data);
|
|
108
|
+
if (parsed !== null) {
|
|
109
|
+
yield parsed;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (parseError) {
|
|
113
|
+
console.error("Failed to parse SSE data:", parseError);
|
|
114
|
+
throw parseError;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
buffer = lines[lines.length - 1];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
abortSignal?.removeEventListener("abort", cleanup);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -176,6 +176,7 @@ class RestIndexerProvider {
|
|
|
176
176
|
scripts: data.event.scripts || [],
|
|
177
177
|
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
178
178
|
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
179
|
+
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
179
180
|
tx: data.event.tx,
|
|
180
181
|
checkpointTxs: data.event.checkpointTxs,
|
|
181
182
|
};
|
|
@@ -329,7 +330,7 @@ class RestIndexerProvider {
|
|
|
329
330
|
});
|
|
330
331
|
if (!res.ok) {
|
|
331
332
|
const errorText = await res.text();
|
|
332
|
-
|
|
333
|
+
console.warn(`Failed to unsubscribe to scripts: ${errorText}`);
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
}
|
|
@@ -358,6 +359,7 @@ function convertVtxo(vtxo) {
|
|
|
358
359
|
arkTxId: vtxo.arkTxid,
|
|
359
360
|
createdAt: new Date(Number(vtxo.createdAt) * 1000),
|
|
360
361
|
isUnrolled: vtxo.isUnrolled,
|
|
362
|
+
isSpent: vtxo.isSpent,
|
|
361
363
|
};
|
|
362
364
|
}
|
|
363
365
|
// Unexported namespace for type guards only
|