@arkade-os/sdk 0.3.0-alpha.6 → 0.3.0-alpha.8

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 (37) hide show
  1. package/README.md +51 -0
  2. package/dist/cjs/adapters/expo.js +8 -0
  3. package/dist/cjs/index.js +2 -1
  4. package/dist/cjs/providers/expoArk.js +237 -0
  5. package/dist/cjs/providers/expoIndexer.js +194 -0
  6. package/dist/cjs/providers/indexer.js +3 -1
  7. package/dist/cjs/script/base.js +16 -95
  8. package/dist/cjs/utils/arkTransaction.js +13 -0
  9. package/dist/cjs/wallet/index.js +1 -1
  10. package/dist/cjs/wallet/serviceWorker/utils.js +0 -9
  11. package/dist/cjs/wallet/serviceWorker/worker.js +14 -17
  12. package/dist/cjs/wallet/utils.js +11 -0
  13. package/dist/cjs/wallet/wallet.js +69 -51
  14. package/dist/esm/adapters/expo.js +3 -0
  15. package/dist/esm/index.js +2 -2
  16. package/dist/esm/providers/expoArk.js +200 -0
  17. package/dist/esm/providers/expoIndexer.js +157 -0
  18. package/dist/esm/providers/indexer.js +3 -1
  19. package/dist/esm/script/base.js +13 -92
  20. package/dist/esm/utils/arkTransaction.js +13 -1
  21. package/dist/esm/wallet/index.js +1 -1
  22. package/dist/esm/wallet/serviceWorker/utils.js +0 -8
  23. package/dist/esm/wallet/serviceWorker/worker.js +15 -18
  24. package/dist/esm/wallet/utils.js +8 -0
  25. package/dist/esm/wallet/wallet.js +70 -52
  26. package/dist/types/adapters/expo.d.ts +4 -0
  27. package/dist/types/index.d.ts +5 -5
  28. package/dist/types/providers/ark.d.ts +136 -2
  29. package/dist/types/providers/expoArk.d.ts +22 -0
  30. package/dist/types/providers/expoIndexer.d.ts +26 -0
  31. package/dist/types/providers/indexer.d.ts +8 -0
  32. package/dist/types/utils/arkTransaction.d.ts +3 -1
  33. package/dist/types/wallet/index.d.ts +44 -6
  34. package/dist/types/wallet/serviceWorker/utils.d.ts +0 -2
  35. package/dist/types/wallet/utils.d.ts +2 -0
  36. package/dist/types/wallet/wallet.d.ts +9 -1
  37. package/package.json +11 -2
package/README.md CHANGED
@@ -288,6 +288,55 @@ const wallet = await Wallet.create({
288
288
  })
289
289
  ```
290
290
 
291
+ ### Using with Expo/React Native
292
+
293
+ For React Native and Expo applications where standard EventSource and fetch streaming may not work properly, use the Expo-compatible providers:
294
+
295
+ ```typescript
296
+ import { Wallet, SingleKey } from '@arkade-os/sdk'
297
+ import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'
298
+
299
+ const identity = SingleKey.fromHex('your_private_key_hex')
300
+
301
+ const wallet = await Wallet.create({
302
+ identity: identity,
303
+ esploraUrl: 'https://mutinynet.com/api',
304
+ arkProvider: new ExpoArkProvider('https://mutinynet.arkade.sh'), // For settlement events and transactions streaming
305
+ indexerProvider: new ExpoIndexerProvider('https://mutinynet.arkade.sh'), // For address subscriptions and VTXO updates
306
+ })
307
+
308
+ // use expo/fetch for streaming support (SSE)
309
+ // All other wallet functionality remains the same
310
+ const balance = await wallet.getBalance()
311
+ const address = await wallet.getAddress()
312
+ ```
313
+
314
+ Both ExpoArkProvider and ExpoIndexerProvider are available as adapters following the SDK's modular architecture pattern. This keeps the main SDK bundle clean while providing opt-in functionality for specific environments:
315
+
316
+ - **ExpoArkProvider**: Handles settlement events and transaction streaming using expo/fetch for Server-Sent Events
317
+ - **ExpoIndexerProvider**: Handles address subscriptions and VTXO updates using expo/fetch for JSON streaming
318
+
319
+ #### Crypto Polyfill Requirement
320
+
321
+ Install `expo-crypto` and polyfill `crypto.getRandomValues()` at the top of your app entry point:
322
+
323
+ ```bash
324
+ npx expo install expo-crypto
325
+ ```
326
+
327
+ ```typescript
328
+ // App.tsx or index.js - MUST be first import
329
+ import * as Crypto from 'expo-crypto';
330
+ if (!global.crypto) global.crypto = {} as any;
331
+ global.crypto.getRandomValues = Crypto.getRandomValues;
332
+
333
+ // Now import the SDK
334
+ import { Wallet, SingleKey } from '@arkade-os/sdk';
335
+ import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo';
336
+ ```
337
+
338
+ This is required for MuSig2 settlements and cryptographic operations.
339
+
291
340
  ### Repository Pattern
292
341
 
293
342
  Access low-level data management through repositories:
@@ -311,6 +360,8 @@ await wallet.contractRepository.saveToContractCollection(
311
360
  const swaps = await wallet.contractRepository.getContractCollection('swaps')
312
361
  ```
313
362
 
363
+ _For complete API documentation, visit our [TypeScript documentation](https://arkade-os.github.io/ts-sdk/)._
364
+
314
365
  ## Development
315
366
 
316
367
  ### Requirements
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExpoIndexerProvider = exports.ExpoArkProvider = void 0;
4
+ // Expo adapter for React Native/Expo environments
5
+ var expoArk_1 = require("../providers/expoArk");
6
+ Object.defineProperty(exports, "ExpoArkProvider", { enumerable: true, get: function () { return expoArk_1.ExpoArkProvider; } });
7
+ var expoIndexer_1 = require("../providers/expoIndexer");
8
+ Object.defineProperty(exports, "ExpoIndexerProvider", { enumerable: true, get: function () { return expoIndexer_1.ExpoIndexerProvider; } });
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.BIP322 = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.waitForIncomingFunds = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.Ramps = exports.OnchainWallet = exports.SingleKey = exports.Wallet = void 0;
3
+ exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.BIP322 = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.Ramps = exports.OnchainWallet = exports.SingleKey = exports.Wallet = void 0;
4
4
  const transaction_js_1 = require("@scure/btc-signer/transaction.js");
5
5
  Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_js_1.Transaction; } });
6
6
  const singleKey_1 = require("./identity/singleKey");
@@ -48,6 +48,7 @@ Object.defineProperty(exports, "CSVMultisigTapscript", { enumerable: true, get:
48
48
  Object.defineProperty(exports, "decodeTapscript", { enumerable: true, get: function () { return tapscript_1.decodeTapscript; } });
49
49
  Object.defineProperty(exports, "MultisigTapscript", { enumerable: true, get: function () { return tapscript_1.MultisigTapscript; } });
50
50
  const arkTransaction_1 = require("./utils/arkTransaction");
51
+ Object.defineProperty(exports, "hasBoardingTxExpired", { enumerable: true, get: function () { return arkTransaction_1.hasBoardingTxExpired; } });
51
52
  Object.defineProperty(exports, "buildOffchainTx", { enumerable: true, get: function () { return arkTransaction_1.buildOffchainTx; } });
52
53
  const unknownFields_1 = require("./utils/unknownFields");
53
54
  Object.defineProperty(exports, "VtxoTaprootTree", { enumerable: true, get: function () { return unknownFields_1.VtxoTaprootTree; } });
@@ -0,0 +1,237 @@
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.ExpoArkProvider = void 0;
37
+ const ark_1 = require("./ark");
38
+ /**
39
+ * Expo-compatible Ark provider implementation using expo/fetch for SSE support.
40
+ * This provider works specifically in React Native/Expo environments where
41
+ * standard EventSource is not available but expo/fetch provides SSE capabilities.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { ExpoArkProvider } from '@arkade-os/sdk/providers/expo';
46
+ *
47
+ * const provider = new ExpoArkProvider('https://ark.example.com');
48
+ * const info = await provider.getInfo();
49
+ * ```
50
+ */
51
+ class ExpoArkProvider extends ark_1.RestArkProvider {
52
+ constructor(serverUrl) {
53
+ super(serverUrl);
54
+ }
55
+ async *getEventStream(signal, topics) {
56
+ // Dynamic import to avoid bundling expo/fetch in non-Expo environments
57
+ let expoFetch = fetch; // Default to standard fetch
58
+ try {
59
+ const expoFetchModule = await Promise.resolve().then(() => __importStar(require("expo/fetch")));
60
+ // expo/fetch returns a compatible fetch function but with different types
61
+ expoFetch = expoFetchModule.fetch;
62
+ console.debug("Using expo/fetch for SSE");
63
+ }
64
+ catch (error) {
65
+ // Fall back to standard fetch if expo/fetch is not available
66
+ console.warn("Using standard fetch instead of expo/fetch. " +
67
+ "Streaming may not be fully supported in some environments.", error);
68
+ }
69
+ const url = `${this.serverUrl}/v1/batch/events`;
70
+ const queryParams = topics.length > 0
71
+ ? `?${topics.map((topic) => `topics=${encodeURIComponent(topic)}`).join("&")}`
72
+ : "";
73
+ while (!signal?.aborted) {
74
+ // Create a new AbortController for this specific fetch attempt
75
+ // to prevent accumulating listeners on the parent signal
76
+ const fetchController = new AbortController();
77
+ const cleanup = () => fetchController.abort();
78
+ signal?.addEventListener("abort", cleanup, { once: true });
79
+ try {
80
+ const response = await expoFetch(url + queryParams, {
81
+ headers: {
82
+ Accept: "text/event-stream",
83
+ },
84
+ signal: fetchController.signal,
85
+ });
86
+ if (!response.ok) {
87
+ throw new Error(`Unexpected status ${response.status} when fetching event stream`);
88
+ }
89
+ if (!response.body) {
90
+ throw new Error("Response body is null");
91
+ }
92
+ const reader = response.body.getReader();
93
+ const decoder = new TextDecoder();
94
+ let buffer = "";
95
+ while (!signal?.aborted) {
96
+ const { done, value } = await reader.read();
97
+ if (done) {
98
+ break;
99
+ }
100
+ // Append new data to buffer and split by newlines
101
+ buffer += decoder.decode(value, { stream: true });
102
+ const lines = buffer.split("\n");
103
+ // Process all complete lines
104
+ for (let i = 0; i < lines.length - 1; i++) {
105
+ const line = lines[i].trim();
106
+ if (!line)
107
+ continue;
108
+ try {
109
+ // Parse SSE format: "data: {json}"
110
+ if (line.startsWith("data:")) {
111
+ const jsonStr = line.substring(5).trim();
112
+ if (!jsonStr)
113
+ continue;
114
+ const data = JSON.parse(jsonStr);
115
+ // Handle different response structures
116
+ // v8 mesh API might wrap in {result: ...} or send directly
117
+ const eventData = data.result || data;
118
+ // Skip heartbeat messages
119
+ if (eventData.heartbeat !== undefined) {
120
+ continue;
121
+ }
122
+ const event = this.parseSettlementEvent(eventData);
123
+ if (event) {
124
+ yield event;
125
+ }
126
+ }
127
+ }
128
+ catch (err) {
129
+ console.error("Failed to parse event:", line);
130
+ console.error("Parse error:", err);
131
+ throw err;
132
+ }
133
+ }
134
+ // Keep the last partial line in the buffer
135
+ buffer = lines[lines.length - 1];
136
+ }
137
+ }
138
+ catch (error) {
139
+ if (error instanceof Error && error.name === "AbortError") {
140
+ break;
141
+ }
142
+ // ignore timeout errors, they're expected when the server is not sending anything for 5 min
143
+ // these timeouts are set by expo/fetch function
144
+ if ((0, ark_1.isFetchTimeoutError)(error)) {
145
+ console.debug("Timeout error ignored");
146
+ continue;
147
+ }
148
+ console.error("Event stream error:", error);
149
+ throw error;
150
+ }
151
+ finally {
152
+ // Clean up the abort listener
153
+ signal?.removeEventListener("abort", cleanup);
154
+ }
155
+ }
156
+ }
157
+ async *getTransactionsStream(signal) {
158
+ // Dynamic import to avoid bundling expo/fetch in non-Expo environments
159
+ let expoFetch = fetch; // Default to standard fetch
160
+ try {
161
+ const expoFetchModule = await Promise.resolve().then(() => __importStar(require("expo/fetch")));
162
+ // expo/fetch returns a compatible fetch function but with different types
163
+ expoFetch = expoFetchModule.fetch;
164
+ console.debug("Using expo/fetch for transaction stream");
165
+ }
166
+ catch (error) {
167
+ // Fall back to standard fetch if expo/fetch is not available
168
+ console.warn("Using standard fetch instead of expo/fetch. " +
169
+ "Streaming may not be fully supported in some environments.", error);
170
+ }
171
+ const url = `${this.serverUrl}/v1/txs`;
172
+ while (!signal?.aborted) {
173
+ // Create a new AbortController for this specific fetch attempt
174
+ // to prevent accumulating listeners on the parent signal
175
+ const fetchController = new AbortController();
176
+ const cleanup = () => fetchController.abort();
177
+ signal?.addEventListener("abort", cleanup, { once: true });
178
+ try {
179
+ const response = await expoFetch(url, {
180
+ headers: {
181
+ Accept: "text/event-stream",
182
+ },
183
+ signal: fetchController.signal,
184
+ });
185
+ if (!response.ok) {
186
+ throw new Error(`Unexpected status ${response.status} when fetching transaction stream`);
187
+ }
188
+ if (!response.body) {
189
+ throw new Error("Response body is null");
190
+ }
191
+ const reader = response.body.getReader();
192
+ const decoder = new TextDecoder();
193
+ let buffer = "";
194
+ while (!signal?.aborted) {
195
+ const { done, value } = await reader.read();
196
+ if (done) {
197
+ break;
198
+ }
199
+ // Append new data to buffer and split by newlines
200
+ buffer += decoder.decode(value, { stream: true });
201
+ const lines = buffer.split("\n");
202
+ // Process all complete lines
203
+ for (let i = 0; i < lines.length - 1; i++) {
204
+ const line = lines[i].trim();
205
+ if (!line)
206
+ continue;
207
+ const data = JSON.parse(line);
208
+ const txNotification = this.parseTransactionNotification(data.result);
209
+ if (txNotification) {
210
+ yield txNotification;
211
+ }
212
+ }
213
+ // Keep the last partial line in the buffer
214
+ buffer = lines[lines.length - 1];
215
+ }
216
+ }
217
+ catch (error) {
218
+ if (error instanceof Error && error.name === "AbortError") {
219
+ break;
220
+ }
221
+ // ignore timeout errors, they're expected when the server is not sending anything for 5 min
222
+ // these timeouts are set by expo/fetch function
223
+ if ((0, ark_1.isFetchTimeoutError)(error)) {
224
+ console.debug("Timeout error ignored");
225
+ continue;
226
+ }
227
+ console.error("Address subscription error:", error);
228
+ throw error;
229
+ }
230
+ finally {
231
+ // Clean up the abort listener
232
+ signal?.removeEventListener("abort", cleanup);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ exports.ExpoArkProvider = ExpoArkProvider;
@@ -0,0 +1,194 @@
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.ExpoIndexerProvider = void 0;
37
+ const indexer_1 = require("./indexer");
38
+ const ark_1 = require("./ark");
39
+ // Helper function to convert Vtxo to VirtualCoin (same as in indexer.ts)
40
+ function convertVtxo(vtxo) {
41
+ return {
42
+ txid: vtxo.outpoint.txid,
43
+ vout: vtxo.outpoint.vout,
44
+ value: Number(vtxo.amount),
45
+ status: {
46
+ confirmed: !vtxo.isSwept && !vtxo.isPreconfirmed,
47
+ },
48
+ virtualStatus: {
49
+ state: vtxo.isSwept
50
+ ? "swept"
51
+ : vtxo.isPreconfirmed
52
+ ? "preconfirmed"
53
+ : "settled",
54
+ commitmentTxIds: vtxo.commitmentTxids,
55
+ batchExpiry: vtxo.expiresAt
56
+ ? Number(vtxo.expiresAt) * 1000
57
+ : undefined,
58
+ },
59
+ spentBy: vtxo.spentBy ?? "",
60
+ settledBy: vtxo.settledBy,
61
+ arkTxId: vtxo.arkTxid,
62
+ createdAt: new Date(Number(vtxo.createdAt) * 1000),
63
+ isUnrolled: vtxo.isUnrolled,
64
+ isSpent: vtxo.isSpent,
65
+ };
66
+ }
67
+ /**
68
+ * Expo-compatible Indexer provider implementation using expo/fetch for streaming support.
69
+ * This provider works specifically in React Native/Expo environments where
70
+ * standard fetch streaming may not work properly but expo/fetch provides streaming capabilities.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * import { ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo';
75
+ *
76
+ * const provider = new ExpoIndexerProvider('https://indexer.example.com');
77
+ * const vtxos = await provider.getVtxos({ scripts: ['script1'] });
78
+ * ```
79
+ */
80
+ class ExpoIndexerProvider extends indexer_1.RestIndexerProvider {
81
+ constructor(serverUrl) {
82
+ super(serverUrl);
83
+ }
84
+ async *getSubscription(subscriptionId, abortSignal) {
85
+ // Detect if we're running in React Native/Expo environment
86
+ const isReactNative = typeof navigator !== "undefined" &&
87
+ navigator.product === "ReactNative";
88
+ // Dynamic import to avoid bundling expo/fetch in non-Expo environments
89
+ let expoFetch = fetch; // Default to standard fetch
90
+ try {
91
+ const expoFetchModule = await Promise.resolve().then(() => __importStar(require("expo/fetch")));
92
+ // expo/fetch returns a compatible fetch function but with different types
93
+ expoFetch = expoFetchModule.fetch;
94
+ console.debug("Using expo/fetch for indexer subscription");
95
+ }
96
+ catch (error) {
97
+ // In React Native/Expo, expo/fetch is required for proper streaming support
98
+ if (isReactNative) {
99
+ throw new Error("expo/fetch is unavailable in React Native environment. " +
100
+ "Please ensure expo/fetch is installed and properly configured. " +
101
+ "Streaming support may not work with standard fetch in React Native.");
102
+ }
103
+ // In non-RN environments, fall back to standard fetch but warn about potential streaming issues
104
+ console.warn("Using standard fetch instead of expo/fetch. " +
105
+ "Streaming may not be fully supported in some environments.", error);
106
+ }
107
+ const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
108
+ while (!abortSignal.aborted) {
109
+ try {
110
+ const res = await expoFetch(url, {
111
+ headers: {
112
+ Accept: "text/event-stream",
113
+ "Content-Type": "application/json",
114
+ },
115
+ signal: abortSignal,
116
+ });
117
+ if (!res.ok) {
118
+ throw new Error(`Unexpected status ${res.status} when subscribing to address updates`);
119
+ }
120
+ // Check if response is the expected content type
121
+ const contentType = res.headers.get("content-type");
122
+ if (contentType &&
123
+ !contentType.includes("text/event-stream") &&
124
+ !contentType.includes("application/json")) {
125
+ throw new Error(`Unexpected content-type: ${contentType}. Expected text/event-stream or application/json`);
126
+ }
127
+ if (!res.body) {
128
+ throw new Error("Response body is null");
129
+ }
130
+ const reader = res.body.getReader();
131
+ const decoder = new TextDecoder();
132
+ let buffer = "";
133
+ while (!abortSignal.aborted) {
134
+ const { done, value } = await reader.read();
135
+ if (done) {
136
+ break;
137
+ }
138
+ buffer += decoder.decode(value, { stream: true });
139
+ const lines = buffer.split("\n");
140
+ for (let i = 0; i < lines.length - 1; i++) {
141
+ const line = lines[i].trim();
142
+ if (!line)
143
+ continue;
144
+ try {
145
+ // Parse SSE format: "data: {json}"
146
+ if (line.startsWith("data:")) {
147
+ const jsonStr = line.substring(5).trim();
148
+ if (!jsonStr)
149
+ continue;
150
+ const data = JSON.parse(jsonStr);
151
+ // Handle new v8 proto format with heartbeat or event
152
+ if (data.heartbeat !== undefined) {
153
+ // Skip heartbeat messages
154
+ continue;
155
+ }
156
+ // Process event messages
157
+ if (data.event) {
158
+ yield {
159
+ txid: data.event.txid,
160
+ scripts: data.event.scripts || [],
161
+ newVtxos: (data.event.newVtxos || []).map(convertVtxo),
162
+ spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
163
+ sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
164
+ tx: data.event.tx,
165
+ checkpointTxs: data.event.checkpointTxs,
166
+ };
167
+ }
168
+ }
169
+ }
170
+ catch (parseError) {
171
+ console.error("Failed to parse subscription response:", parseError);
172
+ throw parseError;
173
+ }
174
+ }
175
+ buffer = lines[lines.length - 1];
176
+ }
177
+ }
178
+ catch (error) {
179
+ if (error instanceof Error && error.name === "AbortError") {
180
+ break;
181
+ }
182
+ // ignore timeout errors, they're expected when the server is not sending anything for 5 min
183
+ // these timeouts are set by expo/fetch function
184
+ if ((0, ark_1.isFetchTimeoutError)(error)) {
185
+ console.debug("Timeout error ignored");
186
+ continue;
187
+ }
188
+ console.error("Subscription error:", error);
189
+ throw error;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ exports.ExpoIndexerProvider = ExpoIndexerProvider;
@@ -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