@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.
Files changed (109) hide show
  1. package/README.md +99 -14
  2. package/dist/cjs/adapters/expo.js +8 -0
  3. package/dist/cjs/arknote/index.js +3 -3
  4. package/dist/cjs/forfeit.js +2 -2
  5. package/dist/cjs/identity/singleKey.js +8 -8
  6. package/dist/cjs/index.js +14 -5
  7. package/dist/cjs/{bip322 → intent}/index.js +38 -61
  8. package/dist/cjs/musig2/index.js +2 -1
  9. package/dist/cjs/musig2/nonces.js +4 -0
  10. package/dist/cjs/providers/ark.js +76 -45
  11. package/dist/cjs/providers/errors.js +59 -0
  12. package/dist/cjs/providers/expoArk.js +82 -0
  13. package/dist/cjs/providers/expoIndexer.js +105 -0
  14. package/dist/cjs/providers/expoUtils.js +124 -0
  15. package/dist/cjs/providers/indexer.js +3 -1
  16. package/dist/cjs/providers/onchain.js +19 -20
  17. package/dist/cjs/repositories/walletRepository.js +64 -28
  18. package/dist/cjs/script/base.js +15 -7
  19. package/dist/cjs/script/tapscript.js +20 -21
  20. package/dist/cjs/script/vhtlc.js +2 -2
  21. package/dist/cjs/tree/signingSession.js +44 -11
  22. package/dist/cjs/tree/txTree.js +3 -4
  23. package/dist/cjs/tree/validation.js +2 -3
  24. package/dist/cjs/utils/arkTransaction.js +118 -15
  25. package/dist/cjs/utils/transaction.js +28 -0
  26. package/dist/cjs/utils/unknownFields.js +7 -7
  27. package/dist/cjs/wallet/index.js +1 -1
  28. package/dist/cjs/wallet/onchain.js +6 -7
  29. package/dist/cjs/wallet/serviceWorker/response.js +32 -0
  30. package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
  31. package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
  32. package/dist/cjs/wallet/serviceWorker/worker.js +48 -32
  33. package/dist/cjs/wallet/unroll.js +7 -9
  34. package/dist/cjs/wallet/utils.js +20 -0
  35. package/dist/cjs/wallet/vtxo-manager.js +323 -0
  36. package/dist/cjs/wallet/wallet.js +165 -174
  37. package/dist/esm/adapters/expo.js +3 -0
  38. package/dist/esm/arknote/index.js +2 -2
  39. package/dist/esm/forfeit.js +1 -1
  40. package/dist/esm/identity/singleKey.js +9 -9
  41. package/dist/esm/index.js +14 -10
  42. package/dist/esm/{bip322 → intent}/index.js +32 -54
  43. package/dist/esm/musig2/index.js +1 -1
  44. package/dist/esm/musig2/nonces.js +3 -0
  45. package/dist/esm/providers/ark.js +76 -45
  46. package/dist/esm/providers/errors.js +54 -0
  47. package/dist/esm/providers/expoArk.js +78 -0
  48. package/dist/esm/providers/expoIndexer.js +101 -0
  49. package/dist/esm/providers/expoUtils.js +87 -0
  50. package/dist/esm/providers/indexer.js +3 -1
  51. package/dist/esm/providers/onchain.js +19 -20
  52. package/dist/esm/repositories/walletRepository.js +64 -28
  53. package/dist/esm/script/base.js +12 -4
  54. package/dist/esm/script/tapscript.js +1 -2
  55. package/dist/esm/script/vhtlc.js +1 -1
  56. package/dist/esm/tree/signingSession.js +45 -12
  57. package/dist/esm/tree/txTree.js +3 -4
  58. package/dist/esm/tree/validation.js +2 -3
  59. package/dist/esm/utils/arkTransaction.js +110 -9
  60. package/dist/esm/utils/transaction.js +24 -0
  61. package/dist/esm/utils/unknownFields.js +3 -3
  62. package/dist/esm/wallet/index.js +1 -1
  63. package/dist/esm/wallet/onchain.js +3 -4
  64. package/dist/esm/wallet/serviceWorker/response.js +32 -0
  65. package/dist/esm/wallet/serviceWorker/utils.js +1 -8
  66. package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
  67. package/dist/esm/wallet/serviceWorker/worker.js +49 -33
  68. package/dist/esm/wallet/unroll.js +5 -7
  69. package/dist/esm/wallet/utils.js +16 -0
  70. package/dist/esm/wallet/vtxo-manager.js +317 -0
  71. package/dist/esm/wallet/wallet.js +159 -168
  72. package/dist/types/adapters/expo.d.ts +4 -0
  73. package/dist/types/arknote/index.d.ts +1 -1
  74. package/dist/types/forfeit.d.ts +2 -2
  75. package/dist/types/identity/index.d.ts +2 -2
  76. package/dist/types/identity/singleKey.d.ts +2 -2
  77. package/dist/types/index.d.ts +11 -9
  78. package/dist/types/intent/index.d.ts +41 -0
  79. package/dist/types/musig2/index.d.ts +1 -1
  80. package/dist/types/musig2/nonces.d.ts +1 -0
  81. package/dist/types/providers/ark.d.ts +197 -27
  82. package/dist/types/providers/errors.d.ts +13 -0
  83. package/dist/types/providers/expoArk.d.ts +22 -0
  84. package/dist/types/providers/expoIndexer.d.ts +18 -0
  85. package/dist/types/providers/expoUtils.d.ts +18 -0
  86. package/dist/types/providers/indexer.d.ts +8 -8
  87. package/dist/types/providers/onchain.d.ts +6 -2
  88. package/dist/types/repositories/walletRepository.d.ts +9 -5
  89. package/dist/types/script/base.d.ts +5 -2
  90. package/dist/types/tree/signingSession.d.ts +16 -11
  91. package/dist/types/utils/anchor.d.ts +2 -2
  92. package/dist/types/utils/arkTransaction.d.ts +15 -5
  93. package/dist/types/utils/transaction.d.ts +13 -0
  94. package/dist/types/utils/unknownFields.d.ts +4 -4
  95. package/dist/types/wallet/index.d.ts +47 -7
  96. package/dist/types/wallet/onchain.d.ts +1 -1
  97. package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
  98. package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
  99. package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
  100. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
  101. package/dist/types/wallet/unroll.d.ts +1 -1
  102. package/dist/types/wallet/utils.d.ts +3 -0
  103. package/dist/types/wallet/vtxo-manager.d.ts +179 -0
  104. package/dist/types/wallet/wallet.d.ts +17 -5
  105. package/package.json +11 -3
  106. package/dist/cjs/bip322/errors.js +0 -13
  107. package/dist/esm/bip322/errors.js +0 -9
  108. package/dist/types/bip322/errors.d.ts +0 -6
  109. 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["TreeNoncesAggregated"] = "tree_nonces_aggregated";
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
- throw new Error(`Failed to get server info: ${response.statusText}`);
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
- checkpointExitClosure: fromServer.checkpointTapscript ?? "",
50
- marketHour: "marketHour" in fromServer && fromServer.marketHour != null
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
- nextStartTime: BigInt(fromServer.marketHour.nextStartTime ?? 0),
53
- nextEndTime: BigInt(fromServer.marketHour.nextEndTime ?? 0),
54
- period: BigInt(fromServer.marketHour.period ?? 0),
55
- roundInterval: BigInt(fromServer.marketHour.roundInterval ?? 0),
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: signedArkTx,
69
- checkpointTxs: checkpointTxs,
81
+ signedArkTx,
82
+ checkpointTxs,
70
83
  }),
71
84
  });
72
85
  if (!response.ok) {
73
86
  const errorText = await response.text();
74
- try {
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
- throw new Error(`Failed to finalize offchain transaction: ${errorText}`);
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
- signature: intent.signature,
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
- throw new Error(`Failed to register intent: ${errorText}`);
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
- signature: intent.signature,
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
- throw new Error(`Failed to delete intent: ${errorText}`);
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
- throw new Error(`Failed to confirm registration: ${errorText}`);
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
- throw new Error(`Failed to submit tree nonces: ${errorText}`);
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
- throw new Error(`Failed to submit tree signatures: ${errorText}`);
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
- throw new Error(`Failed to submit forfeit transactions: ${response.statusText}`);
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.TreeNoncesAggregated,
359
- id: data.treeNoncesAggregated.id,
360
- treeNonces: decodeMusig2Nonces(data.treeNoncesAggregated.treeNonces),
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 JSON.stringify(noncesObject);
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 JSON.stringify(sigObject);
468
+ return sigObject;
442
469
  }
443
- function decodeMusig2Nonces(str) {
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
- throw new Error(`Failed to unsubscribe to scripts: ${errorText}`);
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