UNPKG

@bsv/wallet-toolbox-client

Version:
224 lines 9.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Bitails = void 0; const sdk_1 = require("@bsv/sdk"); const utilityHelpers_1 = require("../../utility/utilityHelpers"); const WalletError_1 = require("../../sdk/WalletError"); const tscProofToMerklePath_1 = require("../../utility/tscProofToMerklePath"); /** * */ class Bitails { constructor(chain = 'main', config = {}) { const { apiKey, httpClient } = config; this.chain = chain; this.URL = chain === 'main' ? `https://api.bitails.io/` : `https://test-api.bitails.io/`; this.httpClient = httpClient !== null && httpClient !== void 0 ? httpClient : (0, sdk_1.defaultHttpClient)(); this.apiKey = apiKey !== null && apiKey !== void 0 ? apiKey : ''; } getHttpHeaders() { const headers = { Accept: 'application/json' }; if (typeof this.apiKey === 'string' && this.apiKey.trim() !== '') { headers.Authorization = this.apiKey; } return headers; } /** * Bitails does not natively support a postBeef end-point aware of multiple txids of interest in the Beef. * * Send rawTx in `txids` order from beef. * * @param beef * @param txids * @returns */ async postBeef(beef, txids) { const nn = () => ({ name: 'BitailsPostBeef', when: new Date().toISOString() }); const nne = () => ({ ...nn(), beef: beef.toHex(), txids: txids.join(',') }); const note = { ...nn(), what: 'postBeef' }; const raws = []; for (const txid of txids) { const rawTx = sdk_1.Utils.toHex(beef.findTxid(txid).rawTx); raws.push(rawTx); } const r = await this.postRaws(raws, txids); r.notes.unshift(note); if (r.status !== 'success') r.notes.push({ ...nne(), what: 'postBeefError' }); else r.notes.push({ ...nn(), what: 'postBeefSuccess' }); return r; } /** * @param raws Array of raw transactions to broadcast as hex strings * @param txids Array of txids for transactions in raws for which results are requested, remaining raws are supporting only. * @returns */ async postRaws(raws, txids) { const r = { name: 'BitailsPostRaws', status: 'success', txidResults: [], notes: [] }; const rawTxids = []; for (const raw of raws) { const txid = sdk_1.Utils.toHex((0, utilityHelpers_1.doubleSha256BE)(sdk_1.Utils.toArray(raw, 'hex'))); // Results aren't always identified by txid. rawTxids.push(txid); if (!txids || txids.indexOf(txid) >= 0) { r.txidResults.push({ txid, status: 'success', notes: [] }); } } const headers = this.getHttpHeaders(); headers['Content-Type'] = 'application/json'; //headers['Accept'] = 'text/json' const data = { raws: raws }; const requestOptions = { method: 'POST', headers, data }; const url = `${this.URL}tx/broadcast/multi`; const nn = () => ({ name: 'BitailsPostRawTx', when: new Date().toISOString() }); const nne = () => ({ ...nn(), raws: raws.join(','), txids: r.txidResults.map(r => r.txid).join(','), url }); try { const response = await this.httpClient.request(url, requestOptions); if (response.ok) { // status: 201, statusText: 'Created' const btrs = response.data; if (btrs.length !== raws.length) { r.status = 'error'; r.notes.push({ ...nne(), what: 'postRawsErrorResultsCount' }); } else { // Check that each response result has a txid that matches corresponding rawTxids let i = -1; for (const btr of btrs) { i++; if (!btr.txid) { btr.txid = rawTxids[i]; r.notes.push({ ...nn(), what: 'postRawsResultMissingTxids', i, rawsTxid: rawTxids[i] }); } else if (btr.txid !== rawTxids[i]) { r.status = 'error'; r.notes.push({ ...nn(), what: 'postRawsResultTxids', i, txid: btr.txid, rawsTxid: rawTxids[i] }); } } if (r.status === 'success') { // btrs has correct number of results and each one has expected txid. // focus on results for requested txids for (const rt of r.txidResults) { const btr = btrs.find(btr => btr.txid === rt.txid); const txid = rt.txid; if (btr.error) { // code: -25, message: 'missing-inputs' // code: -27, message: 'already-in-mempool' const { code, message } = btr.error; if (code === -27) { rt.notes.push({ ...nne(), what: 'postRawsSuccessAlreadyInMempool' }); } else { rt.status = 'error'; if (code === -25) { rt.doubleSpend = true; // this is a possible double spend attempt rt.competingTxs = undefined; // not provided with any data for this. rt.notes.push({ ...nne(), what: 'postRawsErrorMissingInputs' }); } else if (btr['code'] === 'ECONNRESET') { rt.notes.push({ ...nne(), what: 'postRawsErrorECONNRESET', txid, message }); } else { rt.notes.push({ ...nne(), what: 'postRawsError', txid, code, message }); } } } else { rt.notes.push({ ...nn(), what: 'postRawsSuccess' }); } if (rt.status !== 'success' && r.status === 'success') r.status = 'error'; } } } } else { r.status = 'error'; const n = { ...nne(), what: 'postRawsError' }; r.notes.push(n); } } catch (eu) { r.status = 'error'; const e = WalletError_1.WalletError.fromUnknown(eu); const { code, description } = e; r.notes.push({ ...nne(), what: 'postRawsCatch', code, description }); } return r; } /** * * @param txid * @param services * @returns */ async getMerklePath(txid, services) { const r = { name: 'BitailsTsc', notes: [] }; const url = `${this.URL}tx/${txid}/proof/tsc`; const nn = () => ({ name: 'BitailsProofTsc', when: new Date().toISOString(), txid, url }); const headers = this.getHttpHeaders(); const requestOptions = { method: 'GET', headers }; try { const response = await this.httpClient.request(url, requestOptions); const nne = () => ({ ...nn(), txid, url, status: response.status, statusText: response.statusText }); if (response.status === 404 && response.statusText === 'Not Found') { r.notes.push({ ...nn(), what: 'getMerklePathNotFound' }); } else if (!response.ok || response.status !== 200 || response.statusText !== 'OK') { r.notes.push({ ...nne(), what: 'getMerklePathBadStatus' }); } else if (!response.data) { r.notes.push({ ...nne(), what: 'getMerklePathNoData' }); } else { const p = response.data; const header = await services.hashToHeader(p.target); if (header) { const proof = { index: p.index, nodes: p.nodes, height: header.height }; r.merklePath = (0, tscProofToMerklePath_1.convertProofToMerklePath)(txid, proof); r.header = header; r.notes.push({ ...nne(), what: 'getMerklePathSuccess' }); } else { r.notes.push({ ...nne(), what: 'getMerklePathNoHeader', target: p.target }); } } } catch (eu) { const e = WalletError_1.WalletError.fromUnknown(eu); const { code, description } = e; r.notes.push({ ...nn(), what: 'getMerklePathCatch', code, description }); r.error = e; } return r; } } exports.Bitails = Bitails; //# sourceMappingURL=Bitails.js.map