@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
224 lines • 9.54 kB
JavaScript
"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