"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 ServiceCollection_1 = require("./ServiceCollection");
const createDefaultWalletServicesOptions_1 = require("./createDefaultWalletServicesOptions");
const WhatsOnChain_1 = require("./providers/WhatsOnChain");
const exchangeRates_1 = require("./providers/exchangeRates");
const ARC_1 = require("./providers/ARC");
const Bitails_1 = require("./providers/Bitails");
const getBeefForTxid_1 = require("./providers/getBeefForTxid");
const WERR_errors_1 = require("../sdk/WERR_errors");
const ChaintracksChainTracker_1 = require("./chaintracker/ChaintracksChainTracker");
const WalletError_1 = require("../sdk/WalletError");
const utilityHelpers_1 = require("../utility/utilityHelpers");
const utilityHelpers_noBuffer_1 = require("../utility/utilityHelpers.noBuffer");
class Services {
    static createDefaultOptions(chain) {
        return (0, createDefaultWalletServicesOptions_1.createDefaultWalletServicesOptions)(chain);
    }
    constructor(optionsOrChain) {
        this.postBeefMode = 'UntilSuccess';
        /**
         * Soft timeout used for each provider call in `UntilSuccess` mode.
         * This bounds request latency when a provider hangs before failover.
         */
        this.postBeefUntilSuccessSoftTimeoutMs = 5000;
        /**
         * Additional soft-timeout budget (ms) per KiB of serialized Beef payload.
         * Helps avoid false timeout failover on legitimately large submissions.
         */
        this.postBeefUntilSuccessSoftTimeoutPerKbMs = 50;
        /**
         * Upper bound for adaptive soft-timeout in `UntilSuccess` mode.
         */
        this.postBeefUntilSuccessSoftTimeoutMaxMs = 30000;
        this.chain = typeof optionsOrChain === 'string' ? optionsOrChain : optionsOrChain.chain;
        if (this.chain === 'mock') {
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('chain', `'main', 'test', or 'teratest'. Use MockServices for 'mock' chain.`);
        }
        this.options = typeof optionsOrChain === 'string' ? Services.createDefaultOptions(this.chain) : optionsOrChain;
        this.whatsonchain = new WhatsOnChain_1.WhatsOnChain(this.chain, { apiKey: this.options.whatsOnChainApiKey }, this);
        this.arcTaal = new ARC_1.ARC(this.options.arcUrl, this.options.arcConfig, 'arcTaal');
        if (this.options.arcGorillaPoolUrl) {
            this.arcGorillaPool = new ARC_1.ARC(this.options.arcGorillaPoolUrl, this.options.arcGorillaPoolConfig, 'arcGorillaPool');
        }
        const hasBitails = this.chain === 'main' || this.chain === 'test';
        if (hasBitails) {
            this.bitails = new Bitails_1.Bitails(this.chain, { apiKey: this.options.bitailsApiKey });
        }
        //prettier-ignore
        this.getMerklePathServices = new ServiceCollection_1.ServiceCollection('getMerklePath')
            .add({ name: 'WhatsOnChain', service: this.whatsonchain.getMerklePath.bind(this.whatsonchain) });
        if (hasBitails && this.bitails) {
            this.getMerklePathServices.add({ name: 'Bitails', service: this.bitails.getMerklePath.bind(this.bitails) });
        }
        //prettier-ignore
        this.getRawTxServices = new ServiceCollection_1.ServiceCollection('getRawTx')
            .add({ name: 'WhatsOnChain', service: this.whatsonchain.getRawTxResult.bind(this.whatsonchain) });
        this.postBeefServices = new ServiceCollection_1.ServiceCollection('postBeef');
        if (this.arcGorillaPool) {
            //prettier-ignore
            this.postBeefServices.add({ name: 'GorillaPoolArcBeef', service: this.arcGorillaPool.postBeef.bind(this.arcGorillaPool) });
        }
        //prettier-ignore
        this.postBeefServices
            .add({ name: 'TaalArcBeef', service: this.arcTaal.postBeef.bind(this.arcTaal) });
        if (hasBitails && this.bitails) {
            this.postBeefServices.add({ name: 'Bitails', service: this.bitails.postBeef.bind(this.bitails) });
        }
        //prettier-ignore
        this.postBeefServices
            .add({ name: 'WhatsOnChain', service: this.whatsonchain.postBeef.bind(this.whatsonchain) });
        //prettier-ignore
        this.getUtxoStatusServices = new ServiceCollection_1.ServiceCollection('getUtxoStatus')
            .add({ name: 'WhatsOnChain', service: this.whatsonchain.getUtxoStatus.bind(this.whatsonchain) });
        //prettier-ignore
        this.getStatusForTxidsServices = new ServiceCollection_1.ServiceCollection('getStatusForTxids')
            .add({ name: 'WhatsOnChain', service: this.whatsonchain.getStatusForTxids.bind(this.whatsonchain) });
        //prettier-ignore
        this.getScriptHashHistoryServices = new ServiceCollection_1.ServiceCollection('getScriptHashHistory')
            .add({ name: 'WhatsOnChain', service: this.whatsonchain.getScriptHashHistory.bind(this.whatsonchain) });
        //prettier-ignore
        this.updateFiatExchangeRateServices = new ServiceCollection_1.ServiceCollection('updateFiatExchangeRate')
            .add({ name: 'exchangeratesapi', service: exchangeRates_1.updateExchangeratesapi });
    }
    getServicesCallHistory(reset) {
        return {
            version: 2,
            getMerklePath: this.getMerklePathServices.getServiceCallHistory(reset),
            getRawTx: this.getRawTxServices.getServiceCallHistory(reset),
            postBeef: this.postBeefServices.getServiceCallHistory(reset),
            getUtxoStatus: this.getUtxoStatusServices.getServiceCallHistory(reset),
            getStatusForTxids: this.getStatusForTxidsServices.getServiceCallHistory(reset),
            getScriptHashHistory: this.getScriptHashHistoryServices.getServiceCallHistory(reset),
            updateFiatExchangeRates: this.updateFiatExchangeRateServices.getServiceCallHistory(reset)
        };
    }
    async getChainTracker() {
        if (!this.options.chaintracks)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('options.chaintracks', `valid to enable 'getChainTracker' service.`);
        return new ChaintracksChainTracker_1.ChaintracksChainTracker(this.chain, this.options.chaintracks);
    }
    async getBsvExchangeRate() {
        this.options.bsvExchangeRate = await this.whatsonchain.updateBsvExchangeRate(this.options.bsvExchangeRate, this.options.bsvUpdateMsecs);
        return this.options.bsvExchangeRate.rate;
    }
    async getFiatExchangeRate(currency, base) {
        var _a, _b;
        base || (base = 'USD');
        if (currency === base)
            return 1;
        const required = base === 'USD' ? [currency] : [currency, base];
        await this.updateFiatExchangeRates(required, this.options.fiatUpdateMsecs);
        const rates = this.options.fiatExchangeRates;
        const c = (_a = rates.rates) === null || _a === void 0 ? void 0 : _a[currency];
        const b = (_b = rates.rates) === null || _b === void 0 ? void 0 : _b[base];
        if (typeof c !== 'number') {
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('currency', `valid fiat currency '${currency}' with an exchange rate.`);
        }
        if (typeof b !== 'number') {
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('base', `valid fiat currency '${base}' with an exchange rate.`);
        }
        return c / b;
    }
    async getFiatExchangeRates(targetCurrencies) {
        var _a;
        await this.updateFiatExchangeRates(targetCurrencies, this.options.fiatUpdateMsecs);
        const stored = this.options.fiatExchangeRates;
        const rates = {};
        for (const c of targetCurrencies) {
            const v = (_a = stored.rates) === null || _a === void 0 ? void 0 : _a[c];
            if (typeof v === 'number') {
                rates[c] = v;
            }
        }
        return {
            timestamp: stored.timestamp,
            base: 'USD',
            rates,
            rateTimestamps: stored.rateTimestamps
        };
    }
    get getProofsCount() {
        return this.getMerklePathServices.count;
    }
    get getRawTxsCount() {
        return this.getRawTxServices.count;
    }
    get postBeefServicesCount() {
        return this.postBeefServices.count;
    }
    get getUtxoStatsCount() {
        return this.getUtxoStatusServices.count;
    }
    async getStatusForTxids(txids, useNext) {
        const services = this.getStatusForTxidsServices;
        if (useNext)
            services.next();
        let r0 = {
            name: '<noservices>',
            status: 'error',
            error: new WERR_errors_1.WERR_INTERNAL('No services available.'),
            results: []
        };
        for (let tries = 0; tries < services.count; tries++) {
            const stc = services.serviceToCall;
            try {
                const r = await stc.service(txids);
                if (r.status === 'success') {
                    services.addServiceCallSuccess(stc);
                    r0 = r;
                    break;
                }
                else {
                    if (r.error)
                        services.addServiceCallError(stc, r.error);
                    else
                        services.addServiceCallFailure(stc);
                }
            }
            catch (eu) {
                const e = WalletError_1.WalletError.fromUnknown(eu);
                services.addServiceCallError(stc, e);
            }
            services.next();
        }
        return r0;
    }
    /**
     * @param script Output script to be hashed for `getUtxoStatus` default `outputFormat`
     * @returns script hash in 'hashLE' format, which is the default.
     */
    hashOutputScript(script) {
        const hash = sdk_1.Utils.toHex((0, utilityHelpers_1.sha256Hash)(sdk_1.Utils.toArray(script, 'hex')));
        return hash;
    }
    async isUtxo(output) {
        if (!output.lockingScript) {
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('output.lockingScript', 'validated by storage provider validateOutputScript.');
        }
        const hash = this.hashOutputScript(sdk_1.Utils.toHex(output.lockingScript));
        const or = await this.getUtxoStatus(hash, undefined, `${output.txid}.${output.vout}`);
        return or.isUtxo === true;
    }
    async getUtxoStatus(output, outputFormat, outpoint, useNext, logger) {
        const services = this.getUtxoStatusServices;
        if (useNext)
            services.next();
        let r0 = {
            name: '<noservices>',
            status: 'error',
            error: new WERR_errors_1.WERR_INTERNAL('No services available.'),
            details: []
        };
        logger === null || logger === void 0 ? void 0 : logger.group(`services getUtxoStatus`);
        for (let retry = 0; retry < 2; retry++) {
            for (let tries = 0; tries < services.count; tries++) {
                const stc = services.serviceToCall;
                try {
                    const r = await stc.service(output, outputFormat, outpoint);
                    logger === null || logger === void 0 ? void 0 : logger.log(`${stc.providerName} status ${r.status}`);
                    if (r.status === 'success') {
                        services.addServiceCallSuccess(stc);
                        r0 = r;
                        break;
                    }
                    else {
                        if (r.error)
                            services.addServiceCallError(stc, r.error);
                        else
                            services.addServiceCallFailure(stc);
                    }
                }
                catch (eu) {
                    const e = WalletError_1.WalletError.fromUnknown(eu);
                    services.addServiceCallError(stc, e);
                }
                services.next();
            }
            if (r0.status === 'success')
                break;
            await (0, utilityHelpers_1.wait)(2000);
        }
        logger === null || logger === void 0 ? void 0 : logger.groupEnd();
        return r0;
    }
    async getScriptHashHistory(hash, useNext, logger) {
        const services = this.getScriptHashHistoryServices;
        if (useNext)
            services.next();
        let r0 = {
            name: '<noservices>',
            status: 'error',
            error: new WERR_errors_1.WERR_INTERNAL('No services available.'),
            history: []
        };
        logger === null || logger === void 0 ? void 0 : logger.group(`services getScriptHashHistory`);
        for (let tries = 0; tries < services.count; tries++) {
            const stc = services.serviceToCall;
            try {
                const r = await stc.service(hash);
                logger === null || logger === void 0 ? void 0 : logger.log(`${stc.providerName} status ${r.status}`);
                if (r.status === 'success') {
                    r0 = r;
                    break;
                }
                else {
                    if (r.error)
                        services.addServiceCallError(stc, r.error);
                    else
                        services.addServiceCallFailure(stc);
                }
            }
            catch (eu) {
                const e = WalletError_1.WalletError.fromUnknown(eu);
                services.addServiceCallError(stc, e);
            }
            services.next();
        }
        logger === null || logger === void 0 ? void 0 : logger.groupEnd();
        return r0;
    }
    /**
     *
     * @param beef
     * @param chain
     * @returns
     */
    async postBeef(beef, txids, logger) {
        var _a;
        let rs = [];
        const services = this.postBeefServices;
        const stcs = services.allServicesToCall;
        const softTimeoutMs = this.getPostBeefSoftTimeoutMs(beef);
        logger === null || logger === void 0 ? void 0 : logger.group(`services postBeef`);
        switch (this.postBeefMode) {
            case 'UntilSuccess':
                {
                    for (const stc of stcs) {
                        const r = await callService(stc, softTimeoutMs);
                        logger === null || logger === void 0 ? void 0 : logger.log(`${stc.providerName} status ${r.status}`);
                        rs.push(r);
                        if (r.status === 'success')
                            break;
                        const softTimedOut = ((_a = r.notes) === null || _a === void 0 ? void 0 : _a.some(n => n.what === 'postBeefServiceTimeout')) === true;
                        if (!softTimedOut && r.txidResults && r.txidResults.every(txr => txr.serviceError)) {
                            // move this service to the end of the list
                            this.postBeefServices.moveServiceToLast(stc);
                        }
                    }
                }
                break;
            case 'PromiseAll':
                {
                    rs = await Promise.all(stcs.map(async (stc) => {
                        const r = await callService(stc);
                        return r;
                    }));
                }
                break;
        }
        logger === null || logger === void 0 ? void 0 : logger.groupEnd();
        return rs;
        async function callService(stc, timeoutMs) {
            const callPromise = stc.service(beef, txids);
            let r;
            if (!timeoutMs || timeoutMs <= 0) {
                r = await callPromise;
            }
            else {
                let timeoutHandle;
                const timeoutPromise = new Promise(resolve => {
                    timeoutHandle = setTimeout(() => resolve(makeServiceTimeoutResult(stc.providerName, txids, timeoutMs)), timeoutMs);
                });
                r = await Promise.race([callPromise, timeoutPromise]);
                if (timeoutHandle)
                    clearTimeout(timeoutHandle);
                // Avoid unhandled rejection after timeout race wins.
                void callPromise.catch(() => undefined);
            }
            if (r.status === 'success') {
                services.addServiceCallSuccess(stc);
            }
            else {
                if (r.error) {
                    services.addServiceCallError(stc, r.error);
                }
                else {
                    services.addServiceCallFailure(stc);
                }
            }
            return r;
        }
        function makeServiceTimeoutResult(providerName, txids, timeoutMs) {
            return {
                name: providerName,
                status: 'error',
                txidResults: txids.map(txid => ({
                    txid,
                    status: 'error',
                    serviceError: true,
                    data: { detail: `timeout after ${timeoutMs}ms` }
                })),
                notes: [{ when: new Date().toISOString(), what: 'postBeefServiceTimeout', providerName, timeoutMs }]
            };
        }
    }
    getPostBeefSoftTimeoutMs(beef) {
        const baseMs = Math.max(0, this.postBeefUntilSuccessSoftTimeoutMs);
        const perKbMs = Math.max(0, this.postBeefUntilSuccessSoftTimeoutPerKbMs);
        const maxMs = Math.max(baseMs, this.postBeefUntilSuccessSoftTimeoutMaxMs);
        if (perKbMs <= 0)
            return Math.min(baseMs, maxMs);
        const beefBytes = beef.toBinary().length;
        const extraMs = Math.ceil((beefBytes / 1024) * perKbMs);
        return Math.min(maxMs, baseMs + extraMs);
    }
    async getRawTx(txid, useNext) {
        const services = this.getRawTxServices;
        if (useNext)
            services.next();
        const r0 = { txid };
        for (let tries = 0; tries < services.count; tries++) {
            const stc = services.serviceToCall;
            try {
                const r = await stc.service(txid, this.chain);
                if (r.rawTx) {
                    const hash = (0, utilityHelpers_noBuffer_1.asString)((0, utilityHelpers_1.doubleSha256BE)(r.rawTx));
                    // Confirm transaction hash matches txid
                    if (hash === (0, utilityHelpers_noBuffer_1.asString)(txid)) {
                        // If we have a match, call it done.
                        r0.rawTx = r.rawTx;
                        r0.name = r.name;
                        r0.error = undefined;
                        services.addServiceCallSuccess(stc);
                        break;
                    }
                    r.error = new WERR_errors_1.WERR_INTERNAL(`computed txid ${hash} doesn't match requested value ${txid}`);
                    r.rawTx = undefined;
                }
                if (r.error)
                    services.addServiceCallError(stc, r.error);
                else if (!r.rawTx)
                    services.addServiceCallSuccess(stc, `not found`);
                else
                    services.addServiceCallFailure(stc);
                if (r.error && !r0.error && !r0.rawTx)
                    // If we have an error and didn't before...
                    r0.error = r.error;
            }
            catch (eu) {
                const e = WalletError_1.WalletError.fromUnknown(eu);
                services.addServiceCallError(stc, e);
            }
            services.next();
        }
        return r0;
    }
    async invokeChaintracksWithRetry(method) {
        if (!this.options.chaintracks)
            throw new WERR_errors_1.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 = WalletError_1.WalletError.fromUnknown(eu);
                if (e.code != 'ECONNRESET')
                    throw eu;
            }
        }
        throw new WERR_errors_1.WERR_INVALID_OPERATION('hashToHeader service unavailable');
    }
    async getHeaderForHeight(height) {
        const method = async () => {
            const header = await this.options.chaintracks.findHeaderForHeight(height);
            if (!header)
                throw new WERR_errors_1.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);
            return header;
        };
        let header = await this.invokeChaintracksWithRetry(method);
        if (!header) {
            header = await this.whatsonchain.getBlockHeaderByHash(hash);
        }
        if (!header)
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('hash', `valid blockhash '${hash}' on mined chain ${this.chain}`);
        return header;
    }
    async getMerklePath(txid, useNext, logger) {
        const services = this.getMerklePathServices;
        if (useNext)
            services.next();
        const r0 = { notes: [] };
        logger === null || logger === void 0 ? void 0 : logger.group(`services getMerklePath`);
        for (let tries = 0; tries < services.count; tries++) {
            const stc = services.serviceToCall;
            try {
                const r = await stc.service(txid, this);
                if (r.notes)
                    r0.notes.push(...r.notes);
                if (!r0.name)
                    r0.name = r.name;
                if (r.merklePath) {
                    logger === null || logger === void 0 ? void 0 : logger.log(`${stc.providerName} has merklePath`);
                    // If we have a proof, call it done.
                    r0.merklePath = r.merklePath;
                    r0.header = r.header;
                    r0.name = r.name;
                    r0.error = undefined;
                    services.addServiceCallSuccess(stc);
                    break;
                }
                else {
                    logger === null || logger === void 0 ? void 0 : logger.log(`${stc.providerName} no merklePath`);
                }
                if (r.error)
                    services.addServiceCallError(stc, r.error);
                else
                    services.addServiceCallFailure(stc);
                if (r.error && !r0.error) {
                    // If we have an error and didn't before...
                    r0.error = r.error;
                }
            }
            catch (eu) {
                const e = WalletError_1.WalletError.fromUnknown(eu);
                services.addServiceCallError(stc, e);
            }
            services.next();
        }
        return r0;
    }
    async updateFiatExchangeRates(targetCurrencies, updateMsecs) {
        var _a, _b, _c, _d, _e;
        updateMsecs || (updateMsecs = 1000 * 60 * 60 * 24);
        const freshnessDate = new Date(Date.now() - updateMsecs);
        const stored = this.options.fiatExchangeRates;
        const storedRates = (_a = stored.rates) !== null && _a !== void 0 ? _a : {};
        const toFetch = [];
        for (const c of targetCurrencies) {
            if (c === 'USD') {
                if (typeof storedRates.USD !== 'number') {
                    storedRates.USD = 1;
                }
                continue;
            }
            const v = storedRates[c];
            const ts = (_c = (_b = stored.rateTimestamps) === null || _b === void 0 ? void 0 : _b[c]) !== null && _c !== void 0 ? _c : stored.timestamp;
            const fresh = typeof v === 'number' && ts instanceof Date && ts > freshnessDate;
            if (!fresh) {
                toFetch.push(c);
            }
        }
        if (toFetch.length === 0) {
            this.options.fiatExchangeRates = {
                timestamp: stored.timestamp,
                base: stored.base,
                rates: storedRates,
                rateTimestamps: stored.rateTimestamps
            };
            return this.options.fiatExchangeRates;
        }
        const services = this.updateFiatExchangeRateServices.clone();
        let fetched;
        for (let tries = 0; tries < services.count; tries++) {
            const stc = services.serviceToCall;
            try {
                const r = await stc.service(toFetch, this.options);
                if (toFetch.every(c => { var _a; return c === 'USD' || typeof ((_a = r.rates) === null || _a === void 0 ? void 0 : _a[c]) === 'number'; })) {
                    services.addServiceCallSuccess(stc);
                    fetched = r;
                    break;
                }
                else {
                    services.addServiceCallFailure(stc);
                }
            }
            catch (eu) {
                const e = WalletError_1.WalletError.fromUnknown(eu);
                services.addServiceCallError(stc, e);
            }
            services.next();
        }
        if (!fetched) {
            if (stored && Object.keys(storedRates).length > 0) {
                return stored;
            }
            throw new WERR_errors_1.WERR_INTERNAL();
        }
        const nextRates = { ...storedRates };
        const nextTimestamps = { ...((_d = stored.rateTimestamps) !== null && _d !== void 0 ? _d : {}) };
        for (const c of toFetch) {
            const v = (_e = fetched.rates) === null || _e === void 0 ? void 0 : _e[c];
            if (typeof v === 'number') {
                nextRates[c] = v;
                nextTimestamps[c] = fetched.timestamp;
            }
        }
        const nextTimestamp = new Date(Math.max(stored.timestamp instanceof Date ? stored.timestamp.getTime() : new Date(stored.timestamp).getTime(), fetched.timestamp.getTime()));
        this.options.fiatExchangeRates = {
            timestamp: nextTimestamp,
            base: stored.base,
            rates: nextRates,
            rateTimestamps: nextTimestamps
        };
        return this.options.fiatExchangeRates;
    }
    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 WERR_errors_1.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;
    }
    async getBeefForTxid(txid) {
        const beef = await (0, getBeefForTxid_1.getBeefForTxid)(this, txid);
        return beef;
    }
}
exports.Services = Services;
function validateScriptHash(output, outputFormat) {
    let b = (0, utilityHelpers_noBuffer_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, utilityHelpers_1.sha256Hash)(b).reverse();
            break;
        default:
            throw new WERR_errors_1.WERR_INVALID_PARAMETER('outputFormat', `not be ${outputFormat}`);
    }
    return (0, utilityHelpers_noBuffer_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.writeUInt32LE(header.version);
    writer.writeReverse((0, utilityHelpers_noBuffer_1.asArray)(header.previousHash));
    writer.writeReverse((0, utilityHelpers_noBuffer_1.asArray)(header.merkleRoot));
    writer.writeUInt32LE(header.time);
    writer.writeUInt32LE(header.bits);
    writer.writeUInt32LE(header.nonce);
    const r = writer.toArray();
    return r;
}
//# sourceMappingURL=Services.js.map