"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultElectrsAPI = exports.REGTEST_ESPLORA_BASE_PATH = exports.TESTNET_ESPLORA_BASE_PATH = exports.MAINNET_ESPLORA_BASE_PATH = void 0;
const esplora_btc_api_1 = require("@interlay/esplora-btc-api");
const bitcoinjs = __importStar(require("bitcoinjs-lib"));
const types_1 = require("@polkadot/types");
const monetary_js_1 = require("@interlay/monetary-js");
const utils_1 = require("../utils");
exports.MAINNET_ESPLORA_BASE_PATH = "https://btc-mainnet.interlay.io";
exports.TESTNET_ESPLORA_BASE_PATH = "https://btc-testnet.interlay.io";
exports.REGTEST_ESPLORA_BASE_PATH = "http://localhost:3002";
class DefaultElectrsAPI {
    constructor(network = "mainnet") {
        let basePath = "";
        switch (network) {
            case "mainnet":
                basePath = exports.MAINNET_ESPLORA_BASE_PATH;
                break;
            case "testnet":
                basePath = exports.TESTNET_ESPLORA_BASE_PATH;
                break;
            case "regtest":
                basePath = exports.REGTEST_ESPLORA_BASE_PATH;
                break;
            default:
                basePath = network;
        }
        const conf = new esplora_btc_api_1.Configuration({ basePath });
        this.blockApi = new esplora_btc_api_1.BlockApi(conf);
        this.txApi = new esplora_btc_api_1.TxApi(conf);
        this.scripthashApi = new esplora_btc_api_1.ScripthashApi(conf);
        this.addressApi = new esplora_btc_api_1.AddressApi(conf);
    }
    getLatestBlock() {
        return this.getData(this.blockApi.getLastBlockHash());
    }
    getLatestBlockHeight() {
        return this.getData(this.blockApi.getLastBlockHeight());
    }
    getMerkleProof(txid) {
        return this.getData(this.txApi.getTxMerkleBlockProof(txid));
    }
    getTx(txid) {
        return this.getData(this.txApi.getTx(txid));
    }
    getUtxoAmount(txid, recipient) {
        return __awaiter(this, void 0, void 0, function* () {
            let amount = 0;
            if (!txid) {
                return amount;
            }
            const tx = yield this.getTx(txid);
            if (!tx.vout) {
                return amount;
            }
            tx.vout.forEach((vout) => {
                if (vout.scriptpubkey_address === recipient && vout.value) {
                    amount = vout.value;
                }
            });
            return amount;
        });
    }
    getLargestPaymentToRecipientAddressTxId(recipientAddress) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                // TODO: this should be paged
                const txs = yield this.getData(this.addressApi.getAddressTxHistory(recipientAddress));
                if (txs.length >= 25) {
                    throw new Error("Too many transactions");
                }
                let txid = undefined;
                let amount = 0;
                for (const tx of txs) {
                    if (tx.vout === undefined) {
                        continue;
                    }
                    for (const vout of tx.vout) {
                        if (vout.value && vout.scriptpubkey_address === recipientAddress) {
                            if (vout.value > amount) {
                                amount = vout.value;
                                txid = tx.txid;
                            }
                        }
                    }
                }
                if (txid)
                    return txid;
            }
            catch (e) {
                return Promise.reject(new Error(`Error during tx lookup by address: ${e}`));
            }
            return Promise.reject(new Error("No transaction found for recipient"));
        });
    }
    getEarliestPaymentToRecipientAddressTxId(recipientAddress, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                // TODO: this should be paged
                const txs = yield this.getData(this.addressApi.getAddressTxHistory(recipientAddress));
                if (txs.length >= 25) {
                    throw new Error("Over 25 transactions returned; this is either a highly non-standard vault, or not a vault address");
                }
                const oldestTx = txs.pop();
                if (!oldestTx || !oldestTx.vout) {
                    throw new Error("No transaction found for recipient and amount");
                }
                for (const txo of oldestTx.vout) {
                    if (this.txOutputHasRecipientAndAmount(txo, recipientAddress, amount)) {
                        return oldestTx.txid;
                    }
                }
            }
            catch (e) {
                return Promise.reject(new Error(`Error during tx lookup by address: ${e}`));
            }
            return Promise.reject(new Error("No transaction found for recipient and amount"));
        });
    }
    /**
     * Check if a given UTXO has at least `amountAsBTC`
     *
     * @param vout UTXO object
     * @param amountAsBTC (Optional) Amount the recipient must receive
     * @returns Boolean value
     */
    txoHasAtLeastAmount(txo, amount) {
        if (amount) {
            if (txo.value === undefined) {
                return false;
            }
            else {
                const atomicValue = utils_1.atomicToBaseAmount(txo.value, monetary_js_1.Bitcoin);
                const utxoValue = new monetary_js_1.BitcoinAmount(atomicValue);
                return utxoValue.gte(amount);
            }
        }
        return true;
    }
    getTxIdByOpReturn(opReturn, recipientAddress, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            const data = Buffer.from(opReturn, "hex");
            if (data.length !== 32) {
                return Promise.reject(new Error("Requires a 32 byte hash as OP_RETURN"));
            }
            const opReturnBuffer = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, data]);
            const hash = bitcoinjs.crypto.sha256(opReturnBuffer).toString("hex");
            let txs = [];
            try {
                // TODO: this should be paged
                txs = yield this.getData(this.scripthashApi.getTxsByScripthash(hash));
            }
            catch (e) {
                return Promise.reject(new Error(`Error during tx lookup by OP_RETURN: ${e}`));
            }
            for (const tx of txs) {
                if (tx.vout === undefined) {
                    continue;
                }
                for (const vout of tx.vout) {
                    if (this.txOutputHasRecipientAndAmount(vout, recipientAddress, amount)) {
                        return tx.txid;
                    }
                }
            }
            return Promise.reject(new Error("No transaction id found"));
        });
    }
    waitForOpreturn(data, timeoutMs, retryIntervalMs) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                this.getTxIdByOpReturn(data)
                    .then(resolve)
                    .catch((_error) => {
                    setTimeout(() => {
                        console.log("Did not find opreturn, retrying...");
                        if (timeoutMs < retryIntervalMs) {
                            reject(new Error("Timeout elapsed"));
                        }
                        else {
                            this.waitForOpreturn(data, timeoutMs - retryIntervalMs, retryIntervalMs)
                                .then(resolve)
                                .catch(reject);
                        }
                    }, retryIntervalMs);
                });
            });
        });
    }
    waitForTxInclusion(txid, timeoutMs, retryIntervalMs) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                this.getTransactionStatus(txid).then((txStatus) => {
                    if (txStatus.blockHeight !== undefined) {
                        resolve(txStatus);
                    }
                    else {
                        setTimeout(() => {
                            console.log(`Tx ${txid} not yet included in a block...`);
                            if (timeoutMs < retryIntervalMs) {
                                reject(new Error("Timeout elapsed"));
                            }
                            else {
                                this.waitForTxInclusion(txid, timeoutMs - retryIntervalMs, retryIntervalMs)
                                    .then(resolve)
                                    .catch(reject);
                            }
                        }, retryIntervalMs);
                    }
                });
            });
        });
    }
    /**
     * Check if a given UTXO sends at least `amountAsBTC` to a certain `recipientAddress`
     *
     * @param vout UTXO object
     * @param recipientAddress (Optional) Address of recipient
     * @param amountAsBTC (Optional) Amount the recipient must receive. This parameter is only considered if the
     * `recipientAddress` is defined too
     * @returns Boolean value
     */
    txOutputHasRecipientAndAmount(vout, recipientAddress, amount) {
        if (recipientAddress) {
            if (recipientAddress !== vout.scriptpubkey_address) {
                return false;
            }
            return this.txoHasAtLeastAmount(vout, amount);
        }
        return true;
    }
    /**
     * Broadcasts a transaction to the Bitcoin network configured in the constructor
     * @param hex A hex-encoded raw transaction to be broadcast to the Bitcoin blockchain
     * @returns The txid of the transaction
     */
    broadcastRawTransaction(hex) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.txApi.postTx(hex);
        });
    }
    getTransactionStatus(txid) {
        return __awaiter(this, void 0, void 0, function* () {
            const status = {
                confirmed: false,
                confirmations: 0,
            };
            const txStatus = yield this.getTxStatus(txid);
            const latest_block_height = yield this.getLatestBlockHeight();
            status.confirmed = txStatus.confirmed;
            // NOTE: the second part of the check is only to ensure that the -1 we sometimes see
            // in the UI is not caused by the lib
            if (txStatus.block_height && latest_block_height - txStatus.block_height >= 0) {
                // use Bitoin Core definition of confirmations (= block depth)
                status.confirmations = latest_block_height - txStatus.block_height + 1;
                status.blockHeight = txStatus.block_height;
                status.blockHash = txStatus.block_hash;
                // note that block_height will only be set if confirmed == true, i.e. block
                // depth is at least 1. So confirmations 0 will only be returned while unconfirmed.
                // This is correct.
            }
            return status;
        });
    }
    getTransactionBlockHeight(txid) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.getTxStatus(txid)).block_height;
        });
    }
    getParsedExecutionParameters(txid) {
        return __awaiter(this, void 0, void 0, function* () {
            const [unparsedMerkleProof, unparsedRawTx] = yield Promise.all([
                this.getMerkleProof(txid),
                this.getRawTransaction(txid),
            ]);
            // To avoid taking an ApiPromise object as a constructor parameter,
            // use the default TypeRegistry (without custom type metadata),
            // because the Bytes type instantiated is provided by default.
            const registry = new types_1.TypeRegistry();
            const merkleProof = registry.createType("Bytes", "0x" + unparsedMerkleProof);
            const rawTx = registry.createType("Bytes", "0x" + unparsedRawTx);
            return [merkleProof, rawTx];
        });
    }
    getRawTransaction(txid) {
        return this.getData(this.txApi.getTxHex(txid));
    }
    /**
     * Use the TxAPI to get the confirmationation
     * @param txid The ID of a Bitcoin transaction
     * @returns A Status object, containing transaction settlement information
     */
    getTxStatus(txid) {
        return this.getData(this.txApi.getTxStatus(txid));
    }
    /**
     * Parse an AxiosResponse Promise
     * @param response A generic AxiosResponse Promise
     * @returns The data in the response
     */
    getData(response) {
        return response.then((v) => v.data);
    }
}
exports.DefaultElectrsAPI = DefaultElectrsAPI;
//# sourceMappingURL=electrs.js.map