"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BtcTransaction = void 0;
const constants_1 = require("../../utils/constants");
const errors_1 = require("../../utils/errors");
const utils_1 = require("../../utils/utils");
const TransactionBase_1 = require("../TransactionBase");
const BtcAddress_1 = require("../addressObjects/BtcAddress");
const UtxoTransaction_1 = require("./UtxoTransaction");
class BtcTransaction extends UtxoTransaction_1.UtxoTransaction {
    // Btc specific transaction
    get currencyName() {
        return constants_1.BTC_NATIVE_TOKEN_NAME;
    }
    isValidPkscript(index) {
        const vout = this.extractVoutAt(index);
        if (!vout.scriptPubKey.address)
            return true; //OP_RETURN
        const address = new BtcAddress_1.BtcAddress(vout.scriptPubKey.address);
        return address.addressToPkscript() == vout.scriptPubKey.hex;
    }
    get sourceAddresses() {
        if (this.type === "coinbase") {
            // Coinbase transactions mint coins
            return [undefined];
        }
        // Regular transactions
        return this.data.vin.map((vin) => {
            if (vin.prevout) {
                return vin.prevout.scriptPubKey.address;
            }
            else {
                // TODO: we don't have info about the source addresses of this transaction, return undefined for now
                // TODO: we can extract them from additional data (old way)
                return undefined;
            }
        });
    }
    get fee() {
        const error = new errors_1.mccError(errors_1.mccErrorCode.InvalidResponse, Error("fee can't be calculated, the transaction data does not contain the prevouts field (verbosity 2)"));
        if (this.type === "coinbase") {
            // Coinbase transactions mint coins
            return (0, utils_1.toBN)(0);
        }
        let inSum = (0, utils_1.toBN)(0);
        if (this.hasPrevouts) {
            inSum = this.data.vin.reduce(this.reducerFunctionPrevouts, (0, utils_1.toBN)(0));
        }
        else {
            throw error;
        }
        const outSum = this.data.vout.reduce(this.reducerFunctionVouts, (0, utils_1.toBN)(0));
        return inSum.sub(outSum);
    }
    get spentAmounts() {
        if (this.type === "coinbase") {
            // Coinbase transactions mint coins
            return [
                {
                    amount: (0, utils_1.toBN)(0),
                },
            ];
        }
        if (this.hasPrevouts) {
            return this.data.vin.map((vin) => {
                var _a;
                const address = vin.prevout && vin.prevout.scriptPubKey.address ? vin.prevout.scriptPubKey.address : undefined;
                const value = this.toBnValue(((_a = vin === null || vin === void 0 ? void 0 : vin.prevout) === null || _a === void 0 ? void 0 : _a.value) || 0);
                const addressAmount = {
                    address: address,
                    amount: value,
                    utxo: (vin === null || vin === void 0 ? void 0 : vin.vout) || 0,
                };
                return addressAmount;
            });
        }
        else {
            const error = new errors_1.mccError(errors_1.mccErrorCode.InvalidResponse, Error("fee can't be calculated, the transaction data does not contain the prevouts field (verbosity 2)"));
            throw error;
        }
    }
    get type() {
        if (this.data.vin.length === 0 || this.data.vin[0].coinbase) {
            return "coinbase";
        }
        if (this.hasPrevouts)
            return "full_payment";
        return "payment";
    }
    // BTC transaction specific helper methods
    get hasPrevouts() {
        return this.data.vin.every((vin) => {
            return vin.prevout !== undefined || vin.coinbase !== undefined;
        });
    }
    async paymentSummary({ client, inUtxo, outUtxo }) {
        try {
            this.assertValidVinIndex(inUtxo);
        }
        catch (e) {
            return { status: TransactionBase_1.PaymentSummaryStatus.InvalidInUtxo };
        }
        try {
            this.assertValidVoutIndex(outUtxo);
        }
        catch (e) {
            return { status: TransactionBase_1.PaymentSummaryStatus.InvalidOutUtxo };
        }
        if (this.type === "coinbase") {
            return { status: TransactionBase_1.PaymentSummaryStatus.Coinbase };
        }
        const spentAmount = this.spentAmounts[inUtxo];
        if (!spentAmount.address) {
            return { status: TransactionBase_1.PaymentSummaryStatus.NoSpentAmountAddress };
        }
        const receiveAmount = this.receivedAmounts[outUtxo];
        if (!receiveAmount.address) {
            return { status: TransactionBase_1.PaymentSummaryStatus.NoReceiveAmountAddress };
        }
        // Extract addresses from input and output fields
        const sourceAddress = spentAmount.address;
        const receivingAddress = receiveAmount.address;
        // We will update this once we iterate over inputs and outputs if we have full transaction
        let oneToOne = true;
        const isFull = this.type === "full_payment";
        if (isFull) {
            let inFunds = (0, utils_1.toBN)(0);
            let returnFunds = (0, utils_1.toBN)(0);
            let outFunds = (0, utils_1.toBN)(0);
            let inFundsOfReceivingAddress = (0, utils_1.toBN)(0);
            for (const vinAmount of this.spentAmounts) {
                if (sourceAddress && vinAmount.address === sourceAddress) {
                    inFunds = inFunds.add(vinAmount.amount);
                }
                if (receivingAddress && vinAmount.address === receivingAddress) {
                    inFundsOfReceivingAddress = inFundsOfReceivingAddress.add(vinAmount.amount);
                }
                if (oneToOne && vinAmount.address != sourceAddress && vinAmount.address != receivingAddress) {
                    oneToOne = false;
                }
            }
            for (const voutAmount of this.receivedAmounts) {
                if (sourceAddress && voutAmount.address === sourceAddress) {
                    returnFunds = returnFunds.add(voutAmount.amount);
                }
                if (receivingAddress && voutAmount.address === receivingAddress) {
                    outFunds = outFunds.add(voutAmount.amount);
                }
                // Outputs without address do not break one-to-one condition
                if (oneToOne && !voutAmount.address && voutAmount.amount.gt((0, utils_1.toBN)(0))) {
                    oneToOne = false;
                }
                if (oneToOne && voutAmount.address && voutAmount.address != sourceAddress && voutAmount.address != receivingAddress) {
                    oneToOne = false;
                }
            }
            return {
                status: TransactionBase_1.PaymentSummaryStatus.Success,
                response: {
                    blockTimestamp: this.unixTimestamp,
                    transactionHash: this.stdTxid,
                    sourceAddressHash: (0, utils_1.standardAddressHash)(sourceAddress),
                    sourceAddress,
                    receivingAddress,
                    receivingAddressHash: (0, utils_1.standardAddressHash)(receivingAddress),
                    spentAmount: inFunds.sub(returnFunds),
                    receivedAmount: outFunds.sub(inFundsOfReceivingAddress),
                    transactionStatus: this.successStatus,
                    paymentReference: this.stdPaymentReference,
                    // Intended and actual amounts are the same for utxo transactions
                    intendedSourceAddressHash: (0, utils_1.standardAddressHash)(sourceAddress),
                    intendedSourceAddress: sourceAddress,
                    intendedSourceAmount: inFunds.sub(returnFunds),
                    intendedReceivingAddressHash: (0, utils_1.standardAddressHash)(receivingAddress),
                    intendedReceivingAddress: receivingAddress,
                    intendedReceivingAmount: outFunds.sub(inFundsOfReceivingAddress),
                    oneToOne,
                    isFull,
                },
            };
        }
        else {
            // Since we don't have all inputs "decoded" we can't be sure that transaction is one-to-one
            oneToOne = false;
            const spentAmount = sourceAddress && inUtxo != null ? this.spentAmounts[inUtxo].amount : (0, utils_1.toBN)(0);
            const receivedAmount = receivingAddress && outUtxo != null ? this.receivedAmounts[outUtxo].amount : (0, utils_1.toBN)(0);
            return {
                status: this.isValidPkscript(outUtxo) ? TransactionBase_1.PaymentSummaryStatus.Success : TransactionBase_1.PaymentSummaryStatus.InvalidPkscript,
                response: {
                    blockTimestamp: this.unixTimestamp,
                    transactionHash: this.stdTxid,
                    sourceAddress,
                    sourceAddressHash: (0, utils_1.standardAddressHash)(sourceAddress),
                    receivingAddress,
                    receivingAddressHash: (0, utils_1.standardAddressHash)(receivingAddress),
                    spentAmount,
                    receivedAmount,
                    paymentReference: this.stdPaymentReference,
                    transactionStatus: this.successStatus,
                    // Intended and actual amounts are the same for utxo transactions
                    intendedSourceAddressHash: (0, utils_1.standardAddressHash)(sourceAddress),
                    intendedSourceAddress: sourceAddress,
                    intendedSourceAmount: spentAmount,
                    intendedReceivingAddressHash: (0, utils_1.standardAddressHash)(receivingAddress),
                    intendedReceivingAddress: receivingAddress,
                    intendedReceivingAmount: receivedAmount,
                    oneToOne,
                    isFull,
                },
            };
        }
    }
}
exports.BtcTransaction = BtcTransaction;
//# sourceMappingURL=BtcTransaction.js.map