UNPKG

wallet-storage-client

Version:
307 lines 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Services = void 0; exports.validateScriptHash = validateScriptHash; exports.toBinaryBaseBlockHeader = toBinaryBaseBlockHeader; const sdk_1 = require("@bsv/sdk"); const index_client_1 = require("../index.client"); const ServiceCollection_1 = require("./ServiceCollection"); const createDefaultWalletServicesOptions_1 = require("./createDefaultWalletServicesOptions"); const chaintracker_1 = require("./chaintracker"); const arcServices_1 = require("./providers/arcServices"); const whatsonchain_1 = require("./providers/whatsonchain"); const echangeRates_1 = require("./providers/echangeRates"); class Services { static createDefaultOptions(chain) { return (0, createDefaultWalletServicesOptions_1.createDefaultWalletServicesOptions)(chain); } constructor(optionsOrChain) { this.targetCurrencies = ['USD', 'GBP', 'EUR']; this.chain = (typeof optionsOrChain === 'string') ? optionsOrChain : optionsOrChain.chain; this.options = (typeof optionsOrChain === 'string') ? Services.createDefaultOptions(this.chain) : optionsOrChain; this.getMerklePathServices = new ServiceCollection_1.ServiceCollection() .add({ name: 'WhatsOnChainTsc', service: whatsonchain_1.getMerklePathFromWhatsOnChainTsc }); //.add({ name: 'Taal', service: makeGetMerklePathFromTaalARC(getTaalArcServiceConfig(this.chain, this.options.taalApiKey!)) }) this.getRawTxServices = new ServiceCollection_1.ServiceCollection() .add({ name: 'WhatsOnChain', service: whatsonchain_1.getRawTxFromWhatsOnChain }); this.postTxsServices = new ServiceCollection_1.ServiceCollection() .add({ name: 'TaalArcTxs', service: (0, arcServices_1.makePostTxsToTaalARC)((0, arcServices_1.getTaalArcServiceConfig)(this.chain, this.options.taalApiKey)) }); this.postBeefServices = new ServiceCollection_1.ServiceCollection() .add({ name: 'TaalArcBeef', service: (0, arcServices_1.makePostBeefToTaalARC)((0, arcServices_1.getTaalArcServiceConfig)(this.chain, this.options.taalApiKey)) }); this.getUtxoStatusServices = new ServiceCollection_1.ServiceCollection() .add({ name: 'WhatsOnChain', service: whatsonchain_1.getUtxoStatusFromWhatsOnChain }); this.updateFiatExchangeRateServices = new ServiceCollection_1.ServiceCollection() .add({ name: 'ChaintracksService', service: echangeRates_1.updateChaintracksFiatExchangeRates }) .add({ name: 'exchangeratesapi', service: echangeRates_1.updateExchangeratesapi }); } async getChainTracker() { if (!this.options.chaintracks) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('options.chaintracks', `valid to enable 'getChainTracker' service.`); return new chaintracker_1.ChaintracksChainTracker(this.chain, this.options.chaintracks); } async getBsvExchangeRate() { this.options.bsvExchangeRate = await (0, whatsonchain_1.updateBsvExchangeRate)(this.options.bsvExchangeRate, this.options.bsvUpdateMsecs); return this.options.bsvExchangeRate.rate; } async getFiatExchangeRate(currency, base) { const rates = await this.updateFiatExchangeRates(this.options.fiatExchangeRates, this.options.fiatUpdateMsecs); this.options.fiatExchangeRates = rates; base || (base = 'USD'); const rate = rates.rates[currency] / rates.rates[base]; return rate; } get getProofsCount() { return this.getMerklePathServices.count; } get getRawTxsCount() { return this.getRawTxServices.count; } get postTxsServicesCount() { return this.postTxsServices.count; } get postBeefServicesCount() { return this.postBeefServices.count; } get getUtxoStatsCount() { return this.getUtxoStatusServices.count; } async getUtxoStatus(output, outputFormat, useNext) { const services = this.getUtxoStatusServices; if (useNext) services.next(); let r0 = { name: "<noservices>", status: "error", error: new index_client_1.sdk.WERR_INTERNAL('No services available.'), details: [] }; for (let retry = 0; retry < 2; retry++) { for (let tries = 0; tries < services.count; tries++) { const service = services.service; const r = await service(output, this.chain, outputFormat); if (r.status === 'success') { r0 = r; break; } services.next(); } if (r0.status === 'success') break; await (0, index_client_1.wait)(2000); } return r0; } /** * The beef must contain at least each rawTx for each txid. * Some services may require input transactions as well. * These will be fetched if missing, greatly extending the service response time. * @param beef * @param txids * @returns */ async postTxs(beef, txids) { const rs = await Promise.all(this.postTxsServices.allServices.map(async (service) => { const r = await service(beef, txids, this); return r; })); return rs; } /** * * @param beef * @param chain * @returns */ async postBeef(beef, txids) { let rs = await Promise.all(this.postBeefServices.allServices.map(async (service) => { const r = await service(beef, txids, this); return r; })); if (rs.every(r => r.status !== 'success')) { rs = await this.postTxs(beef, txids); } return rs; } async getRawTx(txid, useNext) { if (useNext) this.getRawTxServices.next(); const r0 = { txid }; for (let tries = 0; tries < this.getRawTxServices.count; tries++) { const service = this.getRawTxServices.service; const r = await service(txid, this.chain); if (r.rawTx) { const hash = (0, index_client_1.asString)((0, index_client_1.doubleSha256BE)(r.rawTx)); // Confirm transaction hash matches txid if (hash === (0, index_client_1.asString)(txid)) { // If we have a match, call it done. r0.rawTx = r.rawTx; r0.name = r.name; r0.error = undefined; break; } r.error = new index_client_1.sdk.WERR_INTERNAL(`computed txid ${hash} doesn't match requested value ${txid}`); r.rawTx = undefined; } if (r.error && !r0.error && !r0.rawTx) // If we have an error and didn't before... r0.error = r.error; this.getRawTxServices.next(); } return r0; } async invokeChaintracksWithRetry(method) { if (!this.options.chaintracks) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('options.chaintracks', 'valid for this service operation.'); for (let retry = 0; retry < 3; retry++) { try { const r = await method(); return r; } catch (eu) { const e = index_client_1.sdk.WalletError.fromUnknown(eu); if (e.code != 'ECONNRESET') throw eu; } } throw new index_client_1.sdk.WERR_INVALID_OPERATION('hashToHeader service unavailable'); } async getHeaderForHeight(height) { const method = async () => { const header = await this.options.chaintracks.findHeaderForHeight(height); if (!header) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('hash', `valid height '${height}' on mined chain ${this.chain}`); return toBinaryBaseBlockHeader(header); }; return this.invokeChaintracksWithRetry(method); } async getHeight() { const method = async () => { return await this.options.chaintracks.currentHeight(); }; return this.invokeChaintracksWithRetry(method); } async hashToHeader(hash) { const method = async () => { const header = await this.options.chaintracks.findHeaderForBlockHash(hash); if (!header) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('hash', `valid blockhash '${hash}' on mined chain ${this.chain}`); return header; }; return this.invokeChaintracksWithRetry(method); } async getMerklePath(txid, useNext) { if (useNext) this.getMerklePathServices.next(); const r0 = {}; for (let tries = 0; tries < this.getMerklePathServices.count; tries++) { const service = this.getMerklePathServices.service; const r = await service(txid, this.chain, this); if (r.merklePath) { // If we have a proof, call it done. r0.merklePath = r.merklePath; r0.header = r.header; r0.name = r.name; r0.error = undefined; break; } else if (r.error && !r0.error) // If we have an error and didn't before... r0.error = r.error; this.getMerklePathServices.next(); } return r0; } async updateFiatExchangeRates(rates, updateMsecs) { updateMsecs || (updateMsecs = 1000 * 60 * 15); const freshnessDate = new Date(Date.now() - updateMsecs); if (rates) { // Check if the rate we know is stale enough to update. updateMsecs || (updateMsecs = 1000 * 60 * 15); if (rates.timestamp > freshnessDate) return rates; } // Make sure we always start with the first service listed (chaintracks aggregator) const services = this.updateFiatExchangeRateServices.clone(); let r0; for (let tries = 0; tries < services.count; tries++) { const service = services.service; try { const r = await service(this.targetCurrencies, this.options); if (this.targetCurrencies.every(c => typeof r.rates[c] === 'number')) { r0 = r; break; } } catch (eu) { const e = index_client_1.sdk.WalletError.fromUnknown(eu); console.error(`updateFiatExchangeRates servcice name ${service.name} error ${e.message}`); } services.next(); } if (!r0) { console.error('Failed to update fiat exchange rates.'); if (!rates) throw new index_client_1.sdk.WERR_INTERNAL(); return rates; } return r0; } async nLockTimeIsFinal(tx) { const MAXINT = 0xffffffff; const BLOCK_LIMIT = 500000000; let nLockTime; if (typeof tx === 'number') nLockTime = tx; else { if (typeof tx === 'string') { tx = sdk_1.Transaction.fromHex(tx); } else if (Array.isArray(tx)) { tx = sdk_1.Transaction.fromBinary(tx); } if (tx instanceof sdk_1.Transaction) { if (tx.inputs.every(i => i.sequence === MAXINT)) { return true; } nLockTime = tx.lockTime; } else { throw new index_client_1.sdk.WERR_INTERNAL('Should be either @bsv/sdk Transaction or babbage-bsv Transaction'); } } if (nLockTime >= BLOCK_LIMIT) { const limit = Math.floor(Date.now() / 1000); return nLockTime < limit; } const height = await this.getHeight(); return nLockTime < height; } } exports.Services = Services; function validateScriptHash(output, outputFormat) { let b = (0, index_client_1.asArray)(output); if (!outputFormat) { if (b.length === 32) outputFormat = 'hashLE'; else outputFormat = 'script'; } switch (outputFormat) { case 'hashBE': break; case 'hashLE': b = b.reverse(); break; case 'script': b = (0, index_client_1.sha256Hash)(b).reverse(); break; default: throw new index_client_1.sdk.WERR_INVALID_PARAMETER('outputFormat', `not be ${outputFormat}`); } return (0, index_client_1.asString)(b); } /** * Serializes a block header as an 80 byte array. * The exact serialized format is defined in the Bitcoin White Paper * such that computing a double sha256 hash of the array computes * the block hash for the header. * @returns 80 byte array * @publicbody */ function toBinaryBaseBlockHeader(header) { const writer = new sdk_1.Utils.Writer(); writer.writeUInt32BE(header.version); writer.writeReverse((0, index_client_1.asArray)(header.previousHash)); writer.writeReverse((0, index_client_1.asArray)(header.merkleRoot)); writer.writeUInt32BE(header.time); writer.writeUInt32BE(header.bits); writer.writeUInt32BE(header.nonce); const r = writer.toArray(); return r; } //# sourceMappingURL=Services.js.map