UNPKG

wallet-storage-client

Version:
353 lines 13.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTaalArcServiceConfig = getTaalArcServiceConfig; exports.makePostTxsToTaalARC = makePostTxsToTaalARC; exports.makePostBeefToTaalARC = makePostBeefToTaalARC; exports.makeGetMerklePathFromTaalARC = makeGetMerklePathFromTaalARC; exports.getMerklePathFromTaalARC = getMerklePathFromTaalARC; exports.postTxsToTaalArcMiner = postTxsToTaalArcMiner; exports.postBeefToTaalArcMiner = postBeefToTaalArcMiner; exports.postBeefToArcMiner = postBeefToArcMiner; exports.makePostBeefResult = makePostBeefResult; exports.makeErrorResult = makeErrorResult; const sdk_1 = require("@bsv/sdk"); const index_client_1 = require("../../index.client"); const axios_1 = __importDefault(require("axios")); const stream_1 = require("stream"); const BEEF_V1 = 4022206465; // 0100BEEF in LE order const BEEF_V2 = 4022206466; // 0200BEEF in LE order function getTaalArcServiceConfig(chain, apiKey) { return { name: 'TaalArc', url: chain === 'main' ? 'https://api.taal.com/arc' : 'https://arc-test.taal.com', arcConfig: { apiKey, deploymentId: `WalletServices-${(0, index_client_1.randomBytesHex)(16)}` }, }; } function makePostTxsToTaalARC(config) { return (beef, txids, services) => { return postTxsToTaalArcMiner(beef, txids, config, services); }; } function makePostBeefToTaalARC(config) { return (beef, txids, services) => { return postBeefToTaalArcMiner(beef, txids, config, services); }; } function makeGetMerklePathFromTaalARC(config) { return (txid, chain, services) => { return getMerklePathFromTaalARC(txid, config, services); }; } class ArcServices { /** * Constructs an instance of the ARC broadcaster. * * @param {string} URL - The URL endpoint for the ARC API. * @param {ArcConfig} config - Configuration options for the ARC broadcaster. */ constructor(URL, config) { this.URL = URL; const { apiKey, deploymentId, httpClient, callbackToken, callbackUrl, headers } = config; this.apiKey = apiKey; this.httpClient = httpClient !== null && httpClient !== void 0 ? httpClient : (0, sdk_1.defaultHttpClient)(); this.deploymentId = deploymentId || `WalletServices-${(0, index_client_1.randomBytesHex)(16)}`; this.callbackToken = callbackToken; this.callbackUrl = callbackUrl; this.headers = headers; } /** * Unfortunately this seems to only work for recently submitted txids... * @param txid * @returns */ async getTxStatus(txid) { const requestOptions = { method: 'GET', headers: this.requestHeaders(), }; const response = await this.httpClient.request(`${this.URL}/v1/tx/${txid}`, requestOptions); return response.data; } requestHeaders() { const headers = { 'Content-Type': 'application/json', 'XDeployment-ID': this.deploymentId }; if (this.apiKey) { headers.Authorization = `Bearer ${this.apiKey}`; } if (this.callbackUrl) { headers['X-CallbackUrl'] = this.callbackUrl; } if (this.callbackToken) { headers['X-CallbackToken'] = this.callbackToken; } if (this.headers) { for (const key in this.headers) { headers[key] = this.headers[key]; } } return headers; } } async function getMerklePathFromTaalARC(txid, config, services) { const r = { name: config.name }; try { const arc = new ArcServices(config.url, config.arcConfig); const rr = await arc.getTxStatus(txid); if (rr.status === 200 && rr.merklePath) { const mp = sdk_1.MerklePath.fromHex(rr.merklePath); r.merklePath = mp; r.header = await services.hashToHeader(rr.blockHash); } } catch (eu) { r.error = index_client_1.sdk.WalletError.fromUnknown(eu); } return r; } /** * * @param txs All transactions must have source transactions. Will just source locking scripts and satoshis do?? toHexEF() is used. * @param config */ async function postTxsToTaalArcMiner(beef, txids, config, services) { const r = { name: config.name, status: 'error', txidResults: [] }; try { const arc = new sdk_1.ARC(config.url, config.arcConfig); /** * This service requires an array of EF serialized transactions: * Pull the transactions matching txids array out of the Beef and fill in input sourceTransations, * either from Beef or by external service lookup. */ const txs = []; for (const txid of txids) { const btx = beef.findTxid(txid); if (btx) { const tx = btx.tx; for (const input of tx.inputs) { if (!input.sourceTXID || input.sourceTXID === '0'.repeat(64)) continue; // all zero txid is a coinbase input. let itx = beef.findTxid(input.sourceTXID); if (!itx) { const rawTx = await services.getRawTx(input.sourceTXID); if (!rawTx || !rawTx.rawTx) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('sourceTXID', `contained in beef or found on chain. ${input.sourceTXID}`); beef.mergeRawTx(rawTx.rawTx); itx = beef.findTxid(input.sourceTXID); } input.sourceTransaction = itx.tx; } txs.push(tx); } else throw new index_client_1.sdk.WERR_INVALID_PARAMETER('beef', `merged with rawTxs matching txids. Missing ${txid}`); } const rrs = await arc.broadcastMany(txs); r.status = 'success'; for (const txid of txids) { const txr = { txid, status: 'success' }; const rr = rrs.find(r => r.txid === txid); if (!rr) { r.status = txr.status = 'error'; } else { txr.data = rr; if (rr.status !== 200) r.status = txr.status = 'error'; else { txr.blockHash = rr.blockHash; txr.blockHeight = rr.blockHeight; txr.merklePath = !rr.merklePath ? undefined : sdk_1.MerklePath.fromHex(rr.merklePath); } } r.txidResults.push(txr); } r.data = rrs; } catch (eu) { r.error = index_client_1.sdk.WalletError.fromUnknown(eu); } return r; } async function postBeefToTaalArcMiner(beef, txids, config, services) { const r1 = await postBeefToArcMiner(beef, txids, config); return r1; if (r1.status === 'success') return r1; const datas = { r1: r1.data }; const r2 = await services.postTxs(beef, txids); const r3 = { name: config.name, status: 'success', data: {}, txidResults: [] }; for (const txid of txids) { const rawTx = beef.findTxid(txid).rawTx; const rt = await postBeefToArcMiner(rawTx, [txid], config); if (rt.status === 'error') r3.status = 'error'; r3.data[txid] = rt.data; r3.txidResults.push(rt.txidResults[0]); } datas['r3'] = r3.data; r3.data = datas; return r3; } async function postBeefToArcMiner(beef, txids, config) { const m = { ...config }; let url = ''; let r = undefined; // HACK to resolve ARC error when row has zero leaves. // beef.addComputedLeaves() let beefBinary; if (Array.isArray(beef)) beefBinary = beef; else { // HACK to resolve ARC not handling V2 Beef after change Beef class to always serialize as V2 if default constructed: beef.version = BEEF_V1; beefBinary = beef.toBinary(); } try { const length = beefBinary.length; const makeRequestHeaders = () => { const headers = { 'Content-Type': 'application/octet-stream', 'Content-Length': length.toString(), 'XDeployment-ID': m.arcConfig.deploymentId || `WalletServices-${(0, index_client_1.randomBytesHex)(16)}`, }; if (m.arcConfig.apiKey) { headers['Authorization'] = `Bearer ${m.arcConfig.apiKey}`; } return headers; }; const headers = makeRequestHeaders(); const stream = new stream_1.Readable({ read() { this.push(Buffer.from(beefBinary)); this.push(null); } }); url = `${config.url}/v1/tx`; const data = await axios_1.default.post(url, stream, { headers, maxBodyLength: Infinity, validateStatus: () => true, }); if (!data || !data.data) throw new index_client_1.sdk.WERR_BAD_REQUEST('no response data'); if (data.data === 'No Authorization' || data.status === 403 || data.statusText === 'Forbiden') throw new index_client_1.sdk.WERR_BAD_REQUEST('No Authorization'); if (typeof data.data !== 'object') throw new index_client_1.sdk.WERR_BAD_REQUEST('no response data object'); const dd = data.data; r = makePostBeefResult(dd, config, beefBinary, txids); } catch (err) { console.error(err); const error = new index_client_1.sdk.WERR_INTERNAL(`service: ${url}, error: ${JSON.stringify(index_client_1.sdk.WalletError.fromUnknown(err))}`); r = makeErrorResult(error, config, beefBinary, txids); } return r; } function makePostBeefResult(dd, miner, beef, txids) { let r; switch (dd.status) { case 200: // Success r = makeSuccessResult(dd, miner, beef, txids); break; case 400: // Bad Request r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(), miner, beef, txids, dd); break; case 401: // Security Failed r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`Security Failed (401)`), miner, beef, txids, dd); break; case 409: // Generic Error case 422: // RFC 7807 Error case 460: // Not Extended Format case 467: // Mined Ancestor Missing case 468: // Invalid BUMPs r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`status ${dd.status}, title ${dd.title}`), miner, beef, txids, dd); break; case 461: // Malformed Transaction case 463: // Malformed Transaction case 464: // Invalid Outputs r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`status ${dd.status}, title ${dd.title}`), miner, beef, txids, dd); break; case 462: // Invalid Inputs if (dd.txid) r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`status ${dd.status}, title ${dd.title}`), miner, beef, txids, dd); else r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`status ${dd.status}, title ${dd.title}`), miner, beef, txids, dd); break; case 465: // Fee Too Low case 473: // Cumulative Fee Validation Failed r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`status ${dd.status}, title ${dd.title}`), miner, beef, txids, dd); break; case 469: // Invalid Merkle Root r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`status ${dd.status}, title ${dd.title}`), miner, beef, txids, dd); break; default: r = makeErrorResult(new index_client_1.sdk.WERR_BAD_REQUEST(`status ${dd.status}, title ${dd.title}`), miner, beef, txids, dd); break; } return r; } function makeSuccessResult(dd, miner, // eslint-disable-next-line @typescript-eslint/no-unused-vars beef, // eslint-disable-next-line @typescript-eslint/no-unused-vars txids) { const r = { status: 'success', name: miner.name, data: dd, txidResults: [] }; for (let i = 0; i < txids.length; i++) { const rt = { txid: txids[i], status: 'success' }; if (dd.txid === txids[i]) { rt.alreadyKnown = !!dd.txStatus && ['SEEN_ON_NETWORK', 'MINED'].indexOf(dd.txStatus) >= 0; rt.txid = dd.txid; rt.blockHash = dd.blockHash; rt.blockHeight = dd.blockHeight; rt.merklePath = sdk_1.MerklePath.fromBinary((0, index_client_1.asArray)(dd.merklePath)); } r.txidResults.push(rt); } return r; } function makeErrorResult(error, miner, // eslint-disable-next-line @typescript-eslint/no-unused-vars beef, txids, dd) { const r = { status: 'error', name: miner.name, error, data: dd, txidResults: [] }; for (let i = 0; i < txids.length; i++) { const rt = { txid: txids[i], status: 'error' }; if ((dd === null || dd === void 0 ? void 0 : dd.txid) === txids[i]) { rt.alreadyKnown = !!dd.txStatus && ['SEEN_ON_NETWORK', 'MINED'].indexOf(dd.txStatus) >= 0; rt.txid = dd.txid; rt.blockHash = dd.blockHash; rt.blockHeight = dd.blockHeight; rt.merklePath = sdk_1.MerklePath.fromBinary((0, index_client_1.asArray)(dd.merklePath)); } r.txidResults.push(rt); } return r; } //# sourceMappingURL=arcServices.js.map