wallet-storage-client
Version:
Client only Wallet Storage
353 lines • 13.9 kB
JavaScript
;
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