UNPKG

@arkade-os/sdk

Version:

Bitcoin wallet SDK with Taproot and Ark integration

102 lines (101 loc) 4.33 kB
import { RestIndexerProvider } from './indexer.js'; import { isFetchTimeoutError } from './ark.js'; import { getExpoFetch, sseStreamIterator } from './expoUtils.js'; // Helper function to convert Vtxo to VirtualCoin (same as in indexer.ts) function convertVtxo(vtxo) { return { txid: vtxo.outpoint.txid, vout: vtxo.outpoint.vout, value: Number(vtxo.amount), status: { confirmed: !vtxo.isSwept && !vtxo.isPreconfirmed, }, virtualStatus: { state: vtxo.isSwept ? "swept" : vtxo.isPreconfirmed ? "preconfirmed" : "settled", commitmentTxIds: vtxo.commitmentTxids, batchExpiry: vtxo.expiresAt ? Number(vtxo.expiresAt) * 1000 : undefined, }, spentBy: vtxo.spentBy ?? "", settledBy: vtxo.settledBy, arkTxId: vtxo.arkTxid, createdAt: new Date(Number(vtxo.createdAt) * 1000), isUnrolled: vtxo.isUnrolled, isSpent: vtxo.isSpent, }; } /** * Expo-compatible Indexer provider implementation using expo/fetch for streaming support. * This provider works specifically in React Native/Expo environments where * standard fetch streaming may not work properly but expo/fetch provides streaming capabilities. * * @example * ```typescript * import { ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'; * * const provider = new ExpoIndexerProvider('https://indexer.example.com'); * const vtxos = await provider.getVtxos({ scripts: ['script1'] }); * ``` */ export class ExpoIndexerProvider extends RestIndexerProvider { constructor(serverUrl) { super(serverUrl); } async *getSubscription(subscriptionId, abortSignal) { // Detect if we're running in React Native/Expo environment const isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative"; const expoFetch = await getExpoFetch().catch((error) => { // In React Native/Expo, expo/fetch is required for proper streaming support if (isReactNative) { throw new Error("expo/fetch is unavailable in React Native environment. " + "Please ensure expo/fetch is installed and properly configured. " + "Streaming support may not work with standard fetch in React Native."); } throw error; }); const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`; while (!abortSignal.aborted) { try { yield* sseStreamIterator(url, abortSignal, expoFetch, { "Content-Type": "application/json" }, (data) => { // Handle new v8 proto format with heartbeat or event if (data.heartbeat !== undefined) { // Skip heartbeat messages return null; } // Process event messages if (data.event) { return { txid: data.event.txid, scripts: data.event.scripts || [], newVtxos: (data.event.newVtxos || []).map(convertVtxo), spentVtxos: (data.event.spentVtxos || []).map(convertVtxo), sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo), tx: data.event.tx, checkpointTxs: data.event.checkpointTxs, }; } return null; }); } catch (error) { if (error instanceof Error && error.name === "AbortError") { break; } // ignore timeout errors, they're expected when the server is not sending anything for 5 min // these timeouts are set by expo/fetch function if (isFetchTimeoutError(error)) { console.debug("Timeout error ignored"); continue; } console.error("Subscription error:", error); throw error; } } } }