"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDust = exports.isValidScript = exports.isValidMarketInitOutput = exports.isValidOracleInitOutput = exports.getUpdateMarketSettingsTx = exports.getOracleUpdateDetailsTx = exports.getOracleBurnTx = exports.getOracleUpdateTx = exports.getNewOracleTx = exports.addValaIndex = exports.getFunctionID = exports.fundTx = exports.getSignature = exports.getOracleVoteTx = exports.getOracleCommitTx = exports.getUpdateEntryTx = exports.getAddEntryTx = exports.isValidMarketTx = exports.isValidMarketUpdateTx = exports.isValidUpdateTx = exports.getMarketFromScript = exports.getOracleRabinKeyFromScript = exports.getPreimage = exports.getUpdateMarketTx = exports.getMarketCreationTx = exports.buildNewMarketTx = exports.getAsmFromJS = exports.DUST = void 0;
const scryptlib_1 = require("scryptlib");
const pm_1 = require("./pm");
const oracle_1 = require("./oracle");
const lmsr_1 = require("./lmsr");
const merkleTree_1 = require("./merkleTree");
const sha_1 = require("./sha");
const utils_1 = require("scryptlib/dist/utils");
const rabinsig_1 = require("rabinsig");
const hex_1 = require("./hex");
const contracts_1 = require("./contracts");
const gte_1 = __importDefault(require("semver/functions/gte"));
const lt_1 = __importDefault(require("semver/functions/lt"));
const valaId = "00b0b47b2c25ca119b62172d53055f4742df365ddc";
const feeb = 0.5;
// TODO: Is at 135 now, 0 @ end 2021
exports.DUST = 546;
const Signature = scryptlib_1.bsv.crypto.Signature;
const opReturnDataLength = 4;
const liquidityPointsScalingFactor = 64;
// export function getLockingScript(market: marketInfo): bsv.Script {
//   const asm = getLockingScriptASM(market.oracles).join(" ")
//   const marketBalanceHex = getBalanceHex(market.balance) + String(market.balanceMerkleRoot)
//   const marketDetailsHex = getMarketDetailsHex(market.details)
//   const marketStatusHex = getMarketStatusHex(market.status)
//   const fullScript = `${asm} OP_RETURN ${identifier} ${marketDetailsHex} ${marketStatusHex + marketBalanceHex}`
//   return bsv.Script.fromASM(fullScript)
// }
// export function getLockingScript(market: marketInfo): bsv.Script {
//   const token = getToken(market.oracles)
//   const marketBalanceHex = getBalanceHex(market.balance) + String(market.balanceMerkleRoot)
//   const marketDetailsHex = getMarketDetailsHex(market.details)
//   const marketStatusHex = getMarketStatusHex(market.status)
//   token.setDataPartInASM(`${identifier} ${marketDetailsHex} ${marketStatusHex + marketBalanceHex}`)
//   return token.lockingScript as bsv.Script
// }
// export function getOpReturnHex(market: marketInfo): string {
//   const marketBalanceHex = getBalanceHex(market.balance) + String(market.balanceMerkleRoot)
//   const marketDetailsHex = getMarketDetailsHex(market.details)
//   const marketStatusHex = getMarketStatusHex(market.status)
//   return `${identifier} ${marketDetailsHex} ${marketStatusHex + marketBalanceHex}`
// }
function getAsmFromJS(inputs) {
    return inputs
        .map(input => {
        if (["number", "bigint"].includes(typeof input)) {
            if (input == -1)
                return "OP_1NEGATE";
            if (input >= 0 && input <= 16) {
                return "OP_" + input.toString();
            }
            // @ts-ignore FIXME:
            return new scryptlib_1.bsv.crypto.BN(input.toString()).toSM({ endian: "little" }).toString("hex");
        }
        if (input === "")
            return "OP_0";
        if (input === true)
            return "OP_TRUE";
        if (input === false)
            return "OP_FALSE";
        // const buf = Buffer.from(input, "utf8")
        // return buf.toString("hex")
        // return serialize(input)
        // Input is bytes
        return input;
    })
        .join(" ");
}
exports.getAsmFromJS = getAsmFromJS;
function removeOpReturn(script) {
    const opReturnIndex = getOpReturnPos(script);
    const newChunks = script.chunks.slice(0, opReturnIndex + 1);
    return new scryptlib_1.bsv.Script({ chunks: newChunks });
}
function getDataScriptChunks(asm) {
    const asmChunks = asm.split(" ");
    return asmChunks.map(chunk => {
        var buf = Buffer.from(chunk, "hex");
        if (buf.toString("hex") !== chunk) {
            throw new Error("invalid hex string in script");
        }
        var len = buf.length;
        let opcodenum;
        if (len >= 0 && len < 76) {
            opcodenum = len;
        }
        else if (len < Math.pow(2, 8)) {
            opcodenum = 76;
        }
        else if (len < Math.pow(2, 16)) {
            opcodenum = 77;
        }
        else if (len < Math.pow(2, 32)) {
            opcodenum = 78;
        }
        return {
            buf: buf,
            len: buf.length,
            opcodenum: opcodenum
        };
    });
}
function buildNewMarketTx(market, feePerByte = feeb) {
    const token = (0, pm_1.getToken)(market);
    const version = (0, pm_1.getMarketVersion)(market.version);
    const contractBalance = (0, lmsr_1.getLmsrSatsFixed)(market.balance, version) + market.status.liquidityFeePool;
    const satoshis = contractBalance >= 1 ? contractBalance : 1;
    const tx = new scryptlib_1.bsv.Transaction();
    let output = new scryptlib_1.bsv.Transaction.Output({ script: token.lockingScript, satoshis });
    tx.addOutput(output);
    tx.feePerKb(feePerByte * 1000);
    return tx;
}
exports.buildNewMarketTx = buildNewMarketTx;
function getMarketCreationTx(market, prevValaIndexTx, prevValaIndexOutputIndex = 0, feePerByte = feeb) {
    const tx = buildNewMarketTx(market, feePerByte);
    addValaIndex(tx, prevValaIndexTx, prevValaIndexOutputIndex);
    return tx;
}
exports.getMarketCreationTx = getMarketCreationTx;
function getUpdateMarketTx(prevTx, market, outputIndex = 0, feePerByte = feeb, unlockingScript = scryptlib_1.bsv.Script.empty()) {
    const script = removeOpReturn(prevTx.outputs[outputIndex].script);
    const opReturnData = (0, pm_1.getOpReturnData)(market);
    const chunks = getDataScriptChunks(opReturnData);
    script.chunks = script.chunks.concat(chunks);
    const version = (0, pm_1.getMarketVersion)(market.version);
    const contractBalance = (0, lmsr_1.getLmsrSatsFixed)(market.balance, version) + market.status.liquidityFeePool;
    const satoshis = contractBalance >= 1 ? contractBalance : 1;
    const tx = new scryptlib_1.bsv.Transaction();
    let output = new scryptlib_1.bsv.Transaction.Output({ script, satoshis });
    tx.addOutput(output);
    tx.feePerKb(feePerByte * 1000);
    const input = scryptlib_1.bsv.Transaction.Input.fromObject({
        prevTxId: prevTx.hash,
        outputIndex,
        script: unlockingScript,
        output: prevTx.outputs[outputIndex] // prevTx of newTx here?
    });
    input.clearSignatures = () => { };
    input.isFullySigned = () => true;
    tx.addInput(input);
    return tx;
}
exports.getUpdateMarketTx = getUpdateMarketTx;
function getPreimage(prevTx, newTx, signSighash, outputIndex = 0, inputIndex = 0) {
    // TODO: Maybe prevTx could be replaced by a market
    // script could get generated using market.oracles
    const preimageBuf = scryptlib_1.bsv.Transaction.sighash.sighashPreimage(newTx, signSighash, inputIndex, prevTx.outputs[outputIndex].script, prevTx.outputs[outputIndex].satoshisBN, utils_1.DEFAULT_FLAGS);
    // console.log(preimageBuf.toString("hex"))
    // const asm = prevTx.outputs[outputIndex].script.toASM().split(" ")
    // console.log(asm[asm.length - 1])
    return preimageBuf;
}
exports.getPreimage = getPreimage;
function getOpReturnPos(script) {
    let i = script.chunks.length - 1;
    while (i >= 0) {
        if (script.chunks[i].opcodenum === 106)
            return i;
        i--;
    }
    throw new Error("No OP_RETURN found");
}
function getOracleRabinKeyFromScript(script) {
    const opReturnIndex = getOpReturnPos(script);
    const asm = script.toASM().split(" ");
    const chunks = script.chunks.slice(opReturnIndex + 1);
    const opReturnData = new scryptlib_1.bsv.Script({ chunks }).toASM().split(" ");
    let argPos;
    try {
        const version = (0, oracle_1.getOracleVersion)(opReturnData[0]);
        argPos = version.argPos;
    }
    catch {
        // Older versions without id
        argPos = 3;
    }
    return (0, hex_1.hex2BigInt)(asm[argPos]);
}
exports.getOracleRabinKeyFromScript = getOracleRabinKeyFromScript;
function getMarketFromScript(script) {
    const opReturnIndex = getOpReturnPos(script);
    const asm = script.toASM().split(" "); // TODO: Optimize, remove
    const chunks = script.chunks.slice(opReturnIndex + 1);
    const opReturnData = new scryptlib_1.bsv.Script({ chunks }).toASM().split(" ");
    const stateData = opReturnData[opReturnData.length - 1];
    const version = (0, pm_1.getMarketVersion)(opReturnData[0]);
    let shareBytes = pm_1.sharesByteLength;
    let liquidityBytes = pm_1.liquidityByteLength;
    if ((0, lt_1.default)(version.version, "0.3.15")) {
        shareBytes = 1;
        liquidityBytes = 1;
    }
    const oracleKeyPos = (0, contracts_1.getArgPos)(version, "oracleKey");
    const oracleKeysHex = asm[oracleKeyPos];
    const oracleCount = oracleKeysHex.length / (oracle_1.oracleInfoByteLength * 2);
    const globalOptionCountPos = (0, contracts_1.getArgPos)(version, "globalOptionCount");
    const globalOptionCount = (0, hex_1.getIntFromOP)(asm[globalOptionCountPos]);
    const balanceTableRootPos = stateData.length - pm_1.balanceTableByteLength * 2;
    const balanceTableRoot = stateData.slice(balanceTableRootPos);
    const globalShareStatusPos = balanceTableRootPos - globalOptionCount * shareBytes * 2;
    const globalShareStatus = stateData.slice(globalShareStatusPos, balanceTableRootPos);
    const globalLiquidityPos = globalShareStatusPos - liquidityBytes * 2;
    const globalLiquidity = (0, hex_1.hex2Int)(stateData.slice(globalLiquidityPos, globalShareStatusPos));
    const globalLiquidityPointsPos = globalLiquidityPos - 16;
    const globalLiquidityPointsHex = stateData.slice(globalLiquidityPointsPos, globalLiquidityPos);
    const globalAccLiquidityFeePoolPos = globalLiquidityPointsPos - 10;
    const globalAccLiquidityFeePoolHex = stateData.slice(globalAccLiquidityFeePoolPos, globalLiquidityPointsPos);
    const globalLiquidityFeePoolPos = globalAccLiquidityFeePoolPos - 10;
    const globalLiquidityFeePoolHex = stateData.slice(globalLiquidityFeePoolPos, globalAccLiquidityFeePoolPos);
    const globalVotesLength = globalOptionCount * pm_1.voteCountByteLen * 2;
    const globalVotesPos = globalLiquidityFeePoolPos - globalVotesLength;
    const globalVotesHex = stateData.slice(globalVotesPos, globalLiquidityFeePoolPos);
    const oracleStatesLen = oracleCount * oracle_1.oracleStateByteLength * 2;
    const oracleStatesPos = globalVotesPos - oracleStatesLen;
    const globalOracleStates = stateData.slice(oracleStatesPos, globalVotesPos);
    // console.log(balanceTableRoot)
    // console.log()
    // console.log(globalShareStatus)
    // console.log(globalLiquidity)
    // console.log(globalVotesHex)
    // console.log(globalOracleStates)
    const gobalDecisionPos = oracleStatesPos - 4;
    const globalDecisionState = stateData.slice(gobalDecisionPos, oracleStatesPos);
    const market = {
        version: opReturnData[0],
        details: (0, pm_1.getMarketDetailsFromHex)(opReturnData[1]),
        status: (0, pm_1.getMarketStatusfromHex)(globalDecisionState, globalVotesHex, globalLiquidityFeePoolHex, globalAccLiquidityFeePoolHex, globalLiquidityPointsHex),
        oracles: (0, oracle_1.getOracleDetailsFromHex)(oracleKeysHex, globalOracleStates),
        balance: {
            liquidity: globalLiquidity,
            shares: (0, pm_1.getSharesFromHex)(globalShareStatus, version)
        },
        balanceMerkleRoot: balanceTableRoot,
        creator: {
            pubKey: scryptlib_1.bsv.PublicKey.fromHex(asm[(0, contracts_1.getArgPos)(version, "creatorPubKey")]),
            payoutAddress: scryptlib_1.bsv.Address.fromHex("00" + asm[(0, contracts_1.getArgPos)(version, "creatorPayoutAddress")])
        },
        creatorFee: (0, hex_1.getIntFromOP)(asm[(0, contracts_1.getArgPos)(version, "creatorFee")]) / 100,
        requiredVotes: (0, hex_1.getIntFromOP)(asm[(0, contracts_1.getArgPos)(version, "requiredVotes")]),
        liquidityFee: (0, hex_1.getIntFromOP)(asm[(0, contracts_1.getArgPos)(version, "liquidityFeeRate")]) / 100
    };
    if ((0, gte_1.default)(version.version, "0.4.0")) {
        market.settingsHash = opReturnData[2];
    }
    return market;
}
exports.getMarketFromScript = getMarketFromScript;
function isValidUpdateTx(tx, prevTx, outputIndex = 0, inputIndex = 0) {
    // FIXME: The interpreter creates side effects and can influence future interpreter runs!
    // const txCopy = new bsv.Transaction(tx.serialize())
    const lockingScript = prevTx.outputs[outputIndex].script;
    const unlockingScript = tx.inputs[inputIndex].script;
    const interpreter = scryptlib_1.bsv.Script.Interpreter();
    // console.log(prevTx.outputs)
    // console.log(tx.inputs)
    // console.log(DEFAULT_FLAGS)
    // console.log(tx.serialize())
    const isValidScript = interpreter.verify(unlockingScript, lockingScript, tx, inputIndex, utils_1.DEFAULT_FLAGS, prevTx.outputs[outputIndex].satoshisBN);
    if (!isValidScript) {
        console.error(interpreter.errstr);
    }
    return isValidScript;
}
exports.isValidUpdateTx = isValidUpdateTx;
function isValidMarketUpdateTx(tx, prevTx, entries, outputIndex = 0, inputIndex = 0) {
    return isValidUpdateTx(tx, prevTx, outputIndex, inputIndex) && isValidMarketTx(tx, entries, 0);
}
exports.isValidMarketUpdateTx = isValidMarketUpdateTx;
function isValidMarketTx(tx, entries, outputIndex = 0) {
    const script = tx.outputs[outputIndex].script;
    const balance = tx.outputs[outputIndex].satoshis;
    const market = getMarketFromScript(script);
    // FIXME: This is probably be removed. Script verifies it all.
    const hasValidSatBalance = (0, pm_1.getMinMarketSatBalance)(market, entries) <= balance;
    // console.log([getMinMarketSatBalance(market, entries), balance])
    // console.log(market.status)
    // console.log([
    //   tx.verify() === true,
    //   !tx.getSerializationError(),
    //   isValidMarketInfo(market),
    //   validateEntries(market, entries),
    //   hasValidSatBalance
    // ])
    return (tx.verify() === true &&
        !tx.getSerializationError() &&
        (0, pm_1.isValidMarketInfo)(market) &&
        (0, pm_1.validateEntries)(market, entries) &&
        hasValidSatBalance);
}
exports.isValidMarketTx = isValidMarketTx;
function getAddEntryTx(prevTx, prevEntries, publicKey, balance, payoutAddress, utxos, spendingPrivKey, outputIndex = 0, feePerByte = feeb) {
    const prevMarket = getMarketFromScript(prevTx.outputs[outputIndex].script);
    const optionCount = prevMarket.details.options.length;
    const version = (0, pm_1.getMarketVersion)(prevMarket.version);
    const lastEntry = prevEntries.length ? (0, pm_1.getEntryHex)(prevEntries[prevEntries.length - 1], version) : "00";
    const lastMerklePath = (0, pm_1.getMerklePath)(prevEntries, prevEntries.length - 1, version);
    const entry = {
        balance,
        publicKey,
        globalLiqidityFeePoolSave: prevMarket.status.accLiquidityFeePool,
        liquidityPoints: 0
    };
    const newEntries = prevEntries.concat([entry]);
    const newGlobalBalance = (0, pm_1.getMarketBalance)(newEntries, optionCount);
    let newGlobalLiquidityPoints = prevMarket.status.liquidityPoints;
    let newGlobalLiquidityFeePool = prevMarket.status.liquidityFeePool;
    let newGlobalAccLiquidityFeePool = prevMarket.status.accLiquidityFeePool;
    let redeemSats = 0;
    if ((0, gte_1.default)(version.version, "0.6.3")) {
        const prevGlobalSatBalance = prevTx.outputs[outputIndex].satoshis;
        const prevMarketSatBalance = prevGlobalSatBalance - prevMarket.status.liquidityFeePool;
        const newMarketSatBalance = (0, lmsr_1.getLmsrSatsFixed)(newGlobalBalance, version);
        const noShareChange = newGlobalBalance.shares.every((v, i) => v === prevMarket.balance.shares[i]);
        if (noShareChange) {
            // Do not calculate any fees if only liquidity is extracted
            redeemSats = 0;
        }
        else {
            redeemSats = prevMarketSatBalance - newMarketSatBalance;
        }
        redeemSats = Math.abs(redeemSats);
        // Calculate liquidity fee
        const liquiditySatFee = redeemSats > 0 ? Math.floor((redeemSats * prevMarket.liquidityFee) / 100) : 0;
        // Calculate new global liquidity points and fees in pool
        newGlobalLiquidityPoints = prevMarket.status.liquidityPoints + liquiditySatFee * prevMarket.balance.liquidity;
        newGlobalLiquidityFeePool = prevMarket.status.liquidityFeePool + liquiditySatFee;
        newGlobalAccLiquidityFeePool = prevMarket.status.accLiquidityFeePool + liquiditySatFee;
        entry.globalLiqidityFeePoolSave = newGlobalAccLiquidityFeePool;
    }
    const newMarket = {
        ...prevMarket,
        balance: newGlobalBalance,
        balanceMerkleRoot: (0, pm_1.getBalanceMerkleRoot)(newEntries, version),
        status: {
            ...prevMarket.status,
            liquidityPoints: newGlobalLiquidityPoints,
            accLiquidityFeePool: newGlobalAccLiquidityFeePool,
            liquidityFeePool: newGlobalLiquidityFeePool
        }
    };
    const newTx = getUpdateMarketTx(prevTx, newMarket, outputIndex, feePerByte);
    if ((0, gte_1.default)(version.version, "0.6.3")) {
        // Fees need to be payed when buying as well
        // Add fee outputs to dev and creator
        if (redeemSats > 0) {
            let developerSatFee = Math.floor((version.options.devFee * redeemSats) / 100);
            newTx.to(scryptlib_1.bsv.Address.fromHex(version.options.developerPayoutAddress), developerSatFee);
            if (prevMarket.creatorFee > 0 || (0, lt_1.default)(version.version, "0.6.2")) {
                let creatorSatFee = Math.floor((prevMarket.creatorFee * redeemSats) / 100);
                newTx.to(prevMarket.creator.payoutAddress, creatorSatFee);
                // console.log({
                //   redeemSats,
                //   developerSatFee,
                //   creatorSatFee
                // })
            }
            // console.log(newTx.outputs[1].script.toHex(), newTx.outputs[2].script.toHex())
        }
    }
    const txSize = newTx._estimateSize();
    const sizeEstimate = txSize * 2 + 200; // Tx will get bigger when sighash is added
    // FIXME: Build template tx using utxos instead
    const feeMod = sizeEstimate / txSize;
    fundTx(newTx, spendingPrivKey, payoutAddress, utxos, feePerByte * feeMod);
    const sighashType = Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID;
    const preimage = getPreimage(prevTx, newTx, sighashType, outputIndex);
    const changeOutput = newTx.getChangeOutput();
    const changeSats = changeOutput ? changeOutput.satoshis : 0;
    // const token = getToken(prevMarket)
    // token.txContext = { tx: newTx, inputIndex: 0, inputSatoshis: prevTx.outputs[outputIndex].satoshis }
    // const unlockingScriptOld = token
    //   .updateMarket(
    //     new SigHashPreimage(preimage.toString("hex")),
    //     1, // action = Add entry
    //     new Ripemd160(payoutAddress.hashBuffer.toString("hex")),
    //     changeSats,
    //     new Bytes(entry.publicKey.toString()),
    //     entry.balance.liquidity,
    //     new Bytes(getSharesHex(entry.balance.shares, version)),
    //     new Bytes(lastEntry),
    //     new Bytes(lastMerklePath),
    //     0,
    //     new Bytes(""),
    //     0,
    //     0,
    //     false,
    //     new Sig("00"),
    //     new Bytes(""),
    //     0,
    //     0n,
    //     0,
    //     0,
    //     newTx.outputs[0].satoshis,
    //     new Bytes("").toLiteral()
    //   )
    //   .toScript()
    const unlockArgs = [
        preimage.toString("hex"),
        1,
        payoutAddress.hashBuffer.toString("hex"),
        changeSats,
        entry.publicKey.toString(),
        entry.balance.liquidity,
        (0, pm_1.getSharesHex)(entry.balance.shares, version),
        lastEntry,
        lastMerklePath,
        0,
        "",
        0,
        0,
        false,
        "00",
        "",
        0,
        0n,
        0,
        0,
        newTx.outputs[0].satoshis
    ];
    if ((0, gte_1.default)(version.version, "0.4.0")) {
        unlockArgs.push("00");
    }
    const unlockingScriptASM = getAsmFromJS(unlockArgs);
    const unlockingScript = scryptlib_1.bsv.Script.fromASM(unlockingScriptASM);
    // console.log(getScryptTokenParams(prevMarket))
    // console.log(new SigHashPreimage(preimage.toString("hex")).toLiteral())
    // console.log([
    //   // new SigHashPreimage(preimage.toString("hex")).toLiteral(),
    //   1, // action = Add entry
    //   new Ripemd160(payoutAddress.hashBuffer.toString("hex")).toLiteral(),
    //   changeSats,
    //   new Bytes(entry.publicKey.toString()).toLiteral(),
    //   entry.balance.liquidity,
    //   new Bytes(getSharesHex(entry.balance.shares, version)).toLiteral(),
    //   new Bytes(lastEntry).toLiteral(),
    //   new Bytes(lastMerklePath).toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   0,
    //   0,
    //   false,
    //   new Sig("00").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   0,
    //   0n,
    //   0,
    //   0,
    //   newTx.outputs[0].satoshis,
    //   new Bytes("").toLiteral()
    // ])
    // console.log("new", unlockingScript.toASM().split(" ").slice(1))
    // console.log("old", unlockingScriptOld.toASM().split(" ").slice(1))
    newTx.inputs[0].setScript(unlockingScript);
    // console.log(newTx.serialize())
    // console.log(outputIndex)
    // console.log(prevTx.outputs[outputIndex].satoshis)
    // const asm = prevTx.outputs[outputIndex].script.toASM().split(" ")
    // console.log(asm.slice(asm.length - opReturnDataLength, asm.length).join(" "))
    return newTx;
}
exports.getAddEntryTx = getAddEntryTx;
function getUpdateEntryTx(prevTx, prevEntries, newBalance, redeemLiquidityPoints, privateKey, payoutAddress, utxos, spendingPrivKey, feePerByte = feeb) {
    const prevMarket = getMarketFromScript(prevTx.outputs[0].script);
    const version = (0, pm_1.getMarketVersion)(prevMarket.version);
    const optionCount = prevMarket.details.options.length;
    const publicKey = privateKey.publicKey;
    const entryIndex = prevEntries.findIndex(entry => entry.publicKey.toString() === publicKey.toString());
    if (entryIndex === -1)
        throw new Error("No entry with this publicKey found.");
    const oldEntry = prevEntries[entryIndex];
    const prevGlobalSatBalance = prevTx.outputs[0].satoshis;
    const prevMarketSatBalance = prevGlobalSatBalance - prevMarket.status.liquidityFeePool;
    const updatedBalanceEntries = [...prevEntries];
    updatedBalanceEntries[entryIndex] = {
        ...oldEntry,
        balance: newBalance
    };
    // Determine new contract balance
    let redeemSats = 0;
    let newMarketSatBalance = prevMarketSatBalance;
    let newGlobalBalance;
    if (prevMarket.status.decided) {
        const decision = prevMarket.status.decision;
        const redeemedShares = oldEntry.balance.shares[decision] - newBalance.shares[decision];
        const liquidityChange = newBalance.liquidity - oldEntry.balance.liquidity;
        if (redeemedShares < 0)
            throw new Error("Can't add shares after market is resolved");
        if (liquidityChange > 0)
            throw new Error("Can't add liquidity after market is resolved");
        if (!newBalance.shares.every((newShares, index) => index === decision || newShares === 0))
            throw new Error("Loosing shares must be set to 0");
        redeemSats = redeemedShares * lmsr_1.SatScaling;
        newGlobalBalance = {
            liquidity: prevMarket.balance.liquidity + liquidityChange,
            shares: prevMarket.balance.shares.map((shares, index) => (index === decision ? shares - redeemedShares : shares))
        };
        // Allow liquidity extraction after market is resolved
        // Does not charge fees
        // All money not in fee pool or in winnig shares is given to LPs
        let extractedLiquiditySats = 0;
        if ((0, gte_1.default)(version.version, "0.6.0")) {
            if (liquidityChange < 0) {
                const prevGlobalWinningShares = prevMarket.balance.shares[prevMarket.status.decision];
                const prevWinningSharesBalance = prevGlobalWinningShares * lmsr_1.SatScaling;
                const prevTotalLiquiditySats = prevGlobalSatBalance - prevWinningSharesBalance - prevMarket.status.liquidityFeePool;
                // Uses bigint for contract compatibility
                const liquidityPercentChange = (BigInt(liquidityChange) << 32n) / BigInt(prevMarket.balance.liquidity);
                extractedLiquiditySats = Number(-(liquidityPercentChange * BigInt(prevTotalLiquiditySats)) >> 32n);
            }
        }
        if ((0, lt_1.default)(version.version, "0.6.0")) {
            if (publicKey.toString() === prevMarket.creator.pubKey.toString() && redeemedShares === 0) {
                // Redeem invalid shares
                newGlobalBalance = (0, pm_1.getMarketBalance)(updatedBalanceEntries, optionCount);
                const onlyValidShares = newGlobalBalance.shares.map((shares, i) => i === prevMarket.status.decision ? shares : 0);
                newGlobalBalance.shares = onlyValidShares;
            }
            else if (redeemedShares === 0) {
                newGlobalBalance = (0, pm_1.getMarketBalance)(updatedBalanceEntries, optionCount);
                newGlobalBalance.shares = prevMarket.balance.shares;
            }
        }
        if ((0, lt_1.default)(version.version, "0.6.0") && redeemedShares === 0) {
            newMarketSatBalance = (0, lmsr_1.getLmsrSatsFixed)(newGlobalBalance, version);
            const noShareChange = newGlobalBalance.shares.every((v, i) => v === prevMarket.balance.shares[i]);
            if (noShareChange && (0, gte_1.default)(version.version, "0.3.11")) {
                // Do not calculate any fees if only liquidity is extracted
                redeemSats = 0;
            }
            else {
                redeemSats = prevMarketSatBalance - newMarketSatBalance;
            }
        }
        else {
            newMarketSatBalance = prevMarketSatBalance - redeemSats - extractedLiquiditySats;
        }
    }
    else {
        // Market is open and user is buying, selling or changing liquidity
        // FIXME: Use only entry change to calculate new balance. Check entries at some other point.
        newGlobalBalance = (0, pm_1.getMarketBalance)(updatedBalanceEntries, optionCount);
        newMarketSatBalance = (0, lmsr_1.getLmsrSatsFixed)(newGlobalBalance, version);
        const noShareChange = newGlobalBalance.shares.every((v, i) => v === prevMarket.balance.shares[i]);
        if (noShareChange && (0, gte_1.default)(version.version, "0.3.11")) {
            // Do not calculate any fees if only liquidity is extracted
            redeemSats = 0;
        }
        else {
            redeemSats = prevMarketSatBalance - newMarketSatBalance;
        }
    }
    if ((0, gte_1.default)(version.version, "0.6.3"))
        redeemSats = Math.abs(redeemSats);
    // Calculate liquidity fee
    const liquiditySatFee = redeemSats > 0 ? Math.floor((redeemSats * prevMarket.liquidityFee) / 100) : 0;
    // Calculate new global liquidity points and fees in pool
    let newGlobalLiquidityPoints = prevMarket.status.liquidityPoints + liquiditySatFee * prevMarket.balance.liquidity;
    let newGlobalLiquidityFeePool = prevMarket.status.liquidityFeePool + liquiditySatFee;
    let newGlobalAccLiquidityFeePool = prevMarket.status.accLiquidityFeePool + liquiditySatFee;
    // Calculate new entry liquidity points: Contributed liquidity * new fees since last change
    const feesSinceLastChange = newGlobalAccLiquidityFeePool - oldEntry.globalLiqidityFeePoolSave;
    let newEntryLiquidityPoints = oldEntry.liquidityPoints + feesSinceLastChange * oldEntry.balance.liquidity;
    // Handle redeeming of liquidity points
    if (redeemLiquidityPoints) {
        const redeemedLiquidityPoints = newEntryLiquidityPoints;
        newEntryLiquidityPoints = 0;
        // Adjust new global balances
        if (redeemedLiquidityPoints > 0 && newGlobalLiquidityPoints > 0) {
            const scaledLiquidityPointsShare = (BigInt(redeemedLiquidityPoints) << BigInt(liquidityPointsScalingFactor)) / BigInt(newGlobalLiquidityPoints);
            const redeemedLiquidityPoolSats = Number((BigInt(newGlobalLiquidityFeePool) * BigInt(scaledLiquidityPointsShare)) >> BigInt(liquidityPointsScalingFactor));
            newGlobalLiquidityPoints = newGlobalLiquidityPoints - redeemedLiquidityPoints;
            newGlobalLiquidityFeePool = newGlobalLiquidityFeePool - redeemedLiquidityPoolSats;
        }
    }
    const newEntry = {
        balance: newBalance,
        publicKey,
        globalLiqidityFeePoolSave: newGlobalAccLiquidityFeePool,
        liquidityPoints: newEntryLiquidityPoints
    };
    // console.log({prevShares: prevMarket.balance.shares, newShares: newGlobalBalance.shares, redeemedShares, redeemSats, newAccLiquidityFeePool, newEntryLiquidityPoints})
    const newEntries = [...prevEntries];
    newEntries[entryIndex] = newEntry;
    const merklePath = (0, pm_1.getMerklePath)(prevEntries, entryIndex, version);
    const newMerklePath = (0, pm_1.getMerklePath)(newEntries, entryIndex, version);
    // console.log({
    //   prevEntryBalance: oldEntry.balance,
    //   prevEntryLiquidityPoints: oldEntry.liquidityPoints,
    //   prevEntryGlobalLiquidityPoolSave: oldEntry.globalLiqidityFeePoolSave,
    //   prevMarketBalance: prevMarket.balance,
    //   newGlobalBalance,
    //   feesSinceLastChange,
    //   newEntryBalance: newEntry.balance,
    //   newEntryLiquidityPoints,
    //   prevLiquidityFeePool: prevMarket.status.liquidityFeePool,
    //   prevMarketLiquidityPoints: prevMarket.status.liquidityPoints,
    //   liquidityFee,
    //   redeemedLiquidityPoolSats,
    //   newLiquidityFeePool,
    //   newLiquidityPoints,
    //   newAccLiquidityFeePool,
    //   newMarketSatBalance
    // })
    const sha = (0, lt_1.default)(version.version, "0.6.5") ? sha_1.sha256 : sha_1.sha256d;
    const newMarket = {
        ...prevMarket,
        balance: newGlobalBalance,
        balanceMerkleRoot: (0, merkleTree_1.getMerkleRootByPath)(sha((0, pm_1.getEntryHex)(newEntry, version)), newMerklePath),
        status: {
            ...prevMarket.status,
            liquidityPoints: newGlobalLiquidityPoints,
            liquidityFeePool: newGlobalLiquidityFeePool,
            accLiquidityFeePool: newGlobalAccLiquidityFeePool
        }
    };
    const newTx = getUpdateMarketTx(prevTx, newMarket, 0, feePerByte);
    newTx.outputs[0].satoshis = newMarketSatBalance + newGlobalLiquidityFeePool;
    if (1 > newTx.outputs[0].satoshis) {
        newTx.outputs[0].satoshis = 1;
    }
    // Add fee outputs to dev and creator
    if (redeemSats > 0) {
        let developerSatFee = Math.floor((version.options.devFee * redeemSats) / 100);
        if (developerSatFee < exports.DUST && (0, lt_1.default)(version.version, "0.6.2")) {
            developerSatFee = exports.DUST;
        }
        newTx.to(scryptlib_1.bsv.Address.fromHex(version.options.developerPayoutAddress), developerSatFee);
        if (prevMarket.creatorFee > 0 || (0, lt_1.default)(version.version, "0.6.2")) {
            let creatorSatFee = Math.floor((prevMarket.creatorFee * redeemSats) / 100);
            if (creatorSatFee < exports.DUST && (0, lt_1.default)(version.version, "0.6.2")) {
                creatorSatFee = exports.DUST;
            }
            newTx.to(prevMarket.creator.payoutAddress, creatorSatFee);
        }
        // console.log(newTx.outputs[1].script.toHex(), newTx.outputs[2].script.toHex())
    }
    const txSize = newTx._estimateSize();
    const sizeEstimate = txSize * 2 + 200; // Tx will get bigger when sighash is added
    // FIXME: Build template tx using utxos instead
    const feeMod = sizeEstimate / txSize;
    // console.log({
    //   txSize,
    //   feeMod,
    //   feePerByte
    // })
    fundTx(newTx, spendingPrivKey, payoutAddress, utxos, feePerByte * feeMod);
    const sighashType = Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID;
    // console.log({sighashType})
    const preimage = getPreimage(prevTx, newTx, sighashType);
    const signature = getSignature(preimage, privateKey, sighashType);
    const changeOutput = newTx.getChangeOutput();
    const changeSats = changeOutput ? changeOutput.satoshis : 0;
    // console.log(
    //   {newLiquidityFeePool,
    //   newAccLiquidityFeePool,
    //   newLiquidityPoints,
    //   newGlobalBalance,
    //   balanceMerkleRoot: newMarket.balanceMerkleRoot,
    //   newSatBalance: newTx.outputs[0].satoshis}
    // )
    // const token = getToken(prevMarket)
    // token.txContext = { tx: newTx, inputIndex: 0, inputSatoshis: prevTx.outputs[0].satoshis }
    // const unlockingScriptOld = token
    //   .updateMarket(
    //     new SigHashPreimage(preimage.toString("hex")),
    //     2, // action = Update entry
    //     new Ripemd160(payoutAddress.hashBuffer.toString("hex")),
    //     changeSats,
    //     new Bytes(publicKey.toString()),
    //     newBalance.liquidity,
    //     new Bytes(getSharesHex(newBalance.shares, version)),
    //     new Bytes(""),
    //     new Bytes(""),
    //     oldEntry.balance.liquidity,
    //     new Bytes(getSharesHex(oldEntry.balance.shares, version)),
    //     oldEntry.globalLiqidityFeePoolSave,
    //     oldEntry.liquidityPoints,
    //     redeemLiquidityPoints,
    //     new Sig(signature.toString("hex")),
    //     new Bytes(merklePath),
    //     0,
    //     0n,
    //     0,
    //     0,
    //     newTx.outputs[0].satoshis,
    //    new Bytes("").toLiteral()
    //   )
    // .toScript()
    const unlockArgs = [
        preimage.toString("hex"),
        2,
        payoutAddress.hashBuffer.toString("hex"),
        changeSats,
        publicKey.toString(),
        newBalance.liquidity,
        (0, pm_1.getSharesHex)(newBalance.shares, version),
        "",
        "",
        oldEntry.balance.liquidity,
        (0, pm_1.getSharesHex)(oldEntry.balance.shares, version),
        oldEntry.globalLiqidityFeePoolSave,
        oldEntry.liquidityPoints,
        redeemLiquidityPoints,
        signature.toString("hex"),
        merklePath,
        0,
        0n,
        0,
        0,
        newTx.outputs[0].satoshis
    ];
    if ((0, gte_1.default)(version.version, "0.4.0")) {
        unlockArgs.push("00");
    }
    const unlockingScriptASM = getAsmFromJS(unlockArgs);
    const unlockingScript = scryptlib_1.bsv.Script.fromASM(unlockingScriptASM);
    // console.log(getScryptTokenParams(prevMarket))
    // console.log(new SigHashPreimage(preimage.toString("hex")).toLiteral())
    // console.log([
    //   // new SigHashPreimage(preimage.toString("hex")),
    //   2, // action = Update entry
    //   new Ripemd160(payoutAddress.hashBuffer.toString("hex")).toLiteral(),
    //   changeSats,
    //   new Bytes(publicKey.toString()).toLiteral(),
    //   newBalance.liquidity,
    //   new Bytes(getSharesHex(newBalance.shares, version)).toLiteral(),
    //   new Bytes("").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   oldEntry.balance.liquidity,
    //   new Bytes(getSharesHex(oldEntry.balance.shares, version)).toLiteral(),
    //   oldEntry.globalLiqidityFeePoolSave,
    //   oldEntry.liquidityPoints,
    //   redeemLiquidityPoints,
    //   new Sig(signature.toString("hex")).toLiteral(),
    //   new Bytes(merklePath).toLiteral(),
    //   0,
    //   0n,
    //   0,
    //   0,
    //   newTx.outputs[0].satoshis,
    //   new Bytes("00").toLiteral()
    // ])
    // console.log(newTx.outputs[0].script.toHex())
    // console.log("changeSats", changeSats, newTx.outputs[3].satoshis)
    // console.log([
    //   newTx.outputs[1].script.toHex(), // dev
    //   version.options.developerPayoutAddress,
    //   newTx.outputs[2].script.toHex(), // creator
    //   newTx.outputs[3].script.toHex(),
    // ])
    // console.log("new", unlockingScript.toASM().split(" ").slice(1))
    // console.log("old", unlockingScriptOld.toASM().split(" ").slice(1))
    newTx.inputs[0].setScript(unlockingScript);
    // console.log(newTx.serialize())
    // console.log(prevTx.outputs[0].satoshis)
    // const asm = prevTx.outputs[0].script.toASM().split(" ")
    // console.log(asm.slice(asm.length - opReturnDataLength, asm.length).join(" "))
    return newTx;
}
exports.getUpdateEntryTx = getUpdateEntryTx;
function getOracleCommitTx(prevTx, rabinPrivKey, payoutAddress, utxos, spendingPrivKey, outputIndex = 0, feePerByte = feeb) {
    // TODO: Offer option to fund transaction separately?
    const prevMarket = getMarketFromScript(prevTx.outputs[outputIndex].script);
    const version = (0, pm_1.getMarketVersion)(prevMarket.version);
    // const token = getToken(prevMarket)
    const rabin = new rabinsig_1.RabinSignature();
    const rabinPubKey = rabin.privKeyToPubKey(rabinPrivKey.p, rabinPrivKey.q);
    const oracleIndex = prevMarket.oracles.findIndex(oracle => oracle.pubKey === rabinPubKey);
    const oracle = prevMarket.oracles[oracleIndex];
    if (!oracle)
        throw new Error("Oracle not found.");
    const newOracles = prevMarket.oracles;
    newOracles[oracleIndex] = {
        ...oracle,
        committed: true
    };
    const newMarket = {
        ...prevMarket,
        oracles: newOracles
    };
    const newTx = getUpdateMarketTx(prevTx, newMarket, outputIndex, feePerByte);
    const sigContent = oracle_1.commitmentHash + (0, hex_1.reverseHex)(prevTx.hash);
    const signature = (0, oracle_1.getSignature)(sigContent, rabinPrivKey);
    const signatureBytes = (0, hex_1.int2Hex)(signature.signature);
    const sighashType = Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_SINGLE | Signature.SIGHASH_FORKID;
    const preimage = getPreimage(prevTx, newTx, sighashType, outputIndex);
    // token.txContext = { tx: newTx, inputIndex: 0, inputSatoshis: prevTx.outputs[outputIndex].satoshis }
    // const unlockingScriptOld = token
    //   .updateMarket(
    //     new SigHashPreimage(preimage.toString("hex")),
    //     3, // action = Update entry
    //     new Ripemd160("00"),
    //     0,
    //     new Bytes(""),
    //     0,
    //     new Bytes(""),
    //     new Bytes(""),
    //     new Bytes(""),
    //     0,
    //     new Bytes(""),
    //     0,
    //     0,
    //     false,
    //     new Sig("00"),
    //     new Bytes(""),
    //     oracleIndex,
    //     signature.signature,
    //     signature.paddingByteCount,
    //     0,
    //     newTx.outputs[0].satoshis,
    //    new Bytes("").toLiteral()
    //   )
    //   .toScript()
    const unlockArgs = [
        preimage.toString("hex"),
        3,
        "00",
        0,
        "",
        0,
        "",
        "",
        "",
        0,
        "",
        0,
        0,
        false,
        "00",
        "",
        oracleIndex,
        signature.signature,
        signature.paddingByteCount,
        0,
        newTx.outputs[0].satoshis
    ];
    if ((0, gte_1.default)(version.version, "0.4.0")) {
        unlockArgs.push("00");
    }
    const unlockingScriptASM = getAsmFromJS(unlockArgs);
    const unlockingScript = scryptlib_1.bsv.Script.fromASM(unlockingScriptASM);
    // console.log("new", unlockingScript.toASM().split(" ").slice(1))
    // console.log("old", unlockingScriptOld.toASM().split(" ").slice(1))
    // console.log([
    //   new SigHashPreimage(preimage.toString("hex")).toLiteral(),
    //   3, // action = Update entry
    //   new Ripemd160("00").toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   new Sig("00").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   oracleIndex,
    //   signature.signature.toString(),
    //   signature.paddingByteCount,
    //   0,
    //    new Bytes("").toLiteral()
    // ])
    newTx.inputs[0].setScript(unlockingScript);
    fundTx(newTx, spendingPrivKey, payoutAddress, utxos, feePerByte);
    // console.log(newTx.serialize())
    // console.log(prevTx.outputs[0].satoshis)
    // const asm = prevTx.outputs[0].script.toASM().split(" ")
    // console.log(asm.slice(asm.length - opReturnDataLength, asm.length).join(" "))
    return newTx;
}
exports.getOracleCommitTx = getOracleCommitTx;
function getOracleVoteTx(prevTx, vote, rabinPrivKey, payoutAddress, utxos, spendingPrivKey, feePerByte = feeb) {
    // TODO: Offer option to fund transaction separately?
    const prevMarket = getMarketFromScript(prevTx.outputs[0].script);
    const version = (0, pm_1.getMarketVersion)(prevMarket.version);
    // const token = getToken(prevMarket)
    const rabin = new rabinsig_1.RabinSignature();
    const rabinPubKey = rabin.privKeyToPubKey(rabinPrivKey.p, rabinPrivKey.q);
    const oracleIndex = prevMarket.oracles.findIndex(oracle => oracle.pubKey === rabinPubKey);
    const oracle = prevMarket.oracles[oracleIndex];
    if (!oracle)
        throw new Error("Oracle not found.");
    if (!oracle.committed)
        throw new Error("Oracle not committed to this market yet.");
    if (oracle.voted)
        throw new Error("Oracle already voted.");
    const newOracles = prevMarket.oracles;
    newOracles[oracleIndex] = {
        ...oracle,
        voted: true
    };
    const newVotes = prevMarket.status.votes;
    newVotes[vote] = newVotes[vote] + oracle.votes;
    const newMarketStatus = {
        ...prevMarket.status,
        votes: newVotes
    };
    if (newVotes[vote] >= prevMarket.requiredVotes) {
        newMarketStatus.decided = true;
        newMarketStatus.decision = vote;
    }
    const newMarket = {
        ...prevMarket,
        oracles: newOracles,
        status: newMarketStatus
    };
    const newTx = getUpdateMarketTx(prevTx, newMarket, 0, feePerByte);
    const sigContent = (0, hex_1.int2Hex)(vote, 1) + (0, hex_1.reverseHex)(prevTx.hash);
    const signature = (0, oracle_1.getSignature)(sigContent, rabinPrivKey);
    const sighashType = Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_SINGLE | Signature.SIGHASH_FORKID;
    const preimage = getPreimage(prevTx, newTx, sighashType);
    // token.txContext = { tx: newTx, inputIndex: 0, inputSatoshis: prevTx.outputs[0].satoshis }
    // const unlockingScriptOld = token
    //   .updateMarket(
    //     new SigHashPreimage(preimage.toString("hex")),
    //     4, // action = Update entry
    //     new Ripemd160("00"),
    //     0,
    //     new Bytes(""),
    //     0,
    //     new Bytes(""),
    //     new Bytes(""),
    //     new Bytes(""),
    //     0,
    //     new Bytes(""),
    //     0,
    //     0,
    //     false,
    //     new Sig("00"),
    //     new Bytes(""),
    //     oracleIndex,
    //     signature.signature,
    //     signature.paddingByteCount,
    //     vote,
    //     newTx.outputs[0].satoshis,
    //    new Bytes("").toLiteral()
    //   )
    //   .toScript()
    const unlockArgs = [
        preimage.toString("hex"),
        4,
        "00",
        0,
        "",
        0,
        "",
        "",
        "",
        0,
        "",
        0,
        0,
        false,
        "00",
        "",
        oracleIndex,
        signature.signature,
        signature.paddingByteCount,
        vote,
        newTx.outputs[0].satoshis
    ];
    if ((0, gte_1.default)(version.version, "0.4.0")) {
        unlockArgs.push("00");
    }
    const unlockingScriptASM = getAsmFromJS(unlockArgs);
    const unlockingScript = scryptlib_1.bsv.Script.fromASM(unlockingScriptASM);
    // console.log("new", unlockingScript.toASM().split(" ").slice(1))
    // console.log("old", unlockingScriptOld.toASM().split(" ").slice(1))
    // console.log([
    //   new SigHashPreimage(preimage.toString("hex")).toLiteral(),
    //   4, // action = Update entry
    //   new Ripemd160("00").toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   new Sig("00").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   oracleIndex,
    //   signature.signature.toString(),
    //   signature.paddingByteCount,
    //   vote,
    //    0,
    //    new Bytes("").toLiteral()
    // ])
    newTx.inputs[0].setScript(unlockingScript);
    fundTx(newTx, spendingPrivKey, payoutAddress, utxos, feePerByte);
    // console.log(newTx.serialize())
    // console.log(prevTx.outputs[0].satoshis)
    // const asm = prevTx.outputs[0].script.toASM().split(" ")
    // console.log(asm.slice(asm.length - opReturnDataLength, asm.length).join(" "))
    return newTx;
}
exports.getOracleVoteTx = getOracleVoteTx;
function getSignature(preimage, privateKey, sighashType) {
    const preimageHash = scryptlib_1.bsv.crypto.Hash.sha256sha256(preimage);
    const buffer = new scryptlib_1.bsv.encoding.BufferReader(preimageHash).readReverse();
    return scryptlib_1.bsv.crypto.ECDSA.sign(buffer, privateKey, "little")
        .set({
        nhashtype: sighashType
    })
        .toTxFormat();
}
exports.getSignature = getSignature;
function fundTx(tx, privateKey, changeAddress, utxos, satPerByte = feeb) {
    const inputCount = tx.inputs.length;
    // const marketOutput = tx.outputs[0]
    // if (marketOutput.satoshis < DUST) {
    //   marketOutput.satoshis = DUST
    // }
    tx.from(utxos);
    tx.feePerKb(satPerByte * 1000);
    tx.change(changeAddress);
    // Remove outputs smaller then dust limit
    // TODO: Should be implemented in contract
    // tx.outputs = tx.outputs.filter(output => output.satoshis >= DUST)
    // Check amount
    if (tx.inputAmount < tx.outputAmount)
        throw new Error(`Input amount needs to be at least ${tx.outputAmount}`);
    const fundingInputs = tx.inputs.slice(inputCount, tx.inputs.length);
    fundingInputs.forEach((input, index) => {
        const [signature] = input.getSignatures(tx, privateKey, index + inputCount);
        if (!signature)
            throw new Error("Invalid privateKey");
        input.addSignature(tx, signature);
    });
    return tx;
}
exports.fundTx = fundTx;
function getFunctionID(script) {
    //@ts-ignore
    const asm = script._chunkToString(script.chunks[1], "asm").trim();
    return (0, hex_1.getIntFromOP)(asm);
}
exports.getFunctionID = getFunctionID;
// export function getDebugParams(tx: bsv.Transaction): string {
//   function getBytes(asm: string): string {
//     const bytes = asm === "0" ? "" : asm
//     return new Bytes(bytes).toLiteral()
//   }
//   const unlockingScript = tx.inputs[0].script.toASM().split(" ")
//   const tokenParams = [
//     // new SigHashPreimage(unlockingScript[0]).toLiteral(),
//     getIntFromOP(unlockingScript[1]), // action = Add entry
//     new Ripemd160(unlockingScript[2]).toLiteral(),
//     getIntFromOP(unlockingScript[3]),
//     getBytes(unlockingScript[4]),
//     getBytes(unlockingScript[5]),
//     getBytes(unlockingScript[6]),
//     getIntFromOP(unlockingScript[7]),
//     getBytes(unlockingScript[8]),
//     new Sig(unlockingScript[9]).toLiteral(),
//     getBytes(unlockingScript[10]),
//     getIntFromOP(unlockingScript[11]),
//     hex2BigInt(unlockingScript[12]).toString(),
//     getIntFromOP(unlockingScript[13]),
//     getIntFromOP(unlockingScript[14])
//   ]
//   return JSON.stringify(tokenParams)
// }
/**
 * Adds Vala Index input and output to the transaction
 */
function addValaIndex(tx, prevValaIndexTx, prevValaIndexOutputIndex = 0) {
    // console.log("index script length", prevValaIndexTx.outputs[prevValaIndexOutputIndex].script.toASM().length)
    // console.log("index script index", prevValaIndexOutputIndex)
    const input = scryptlib_1.bsv.Transaction.Input.fromObject({
        prevTxId: prevValaIndexTx.hash,
        outputIndex: prevValaIndexOutputIndex,
        script: scryptlib_1.bsv.Script.empty(),
        output: prevValaIndexTx.outputs[prevValaIndexOutputIndex]
    });
    input.clearSignatures = () => { }; // eslint-disable-line
    input.isFullySigned = () => true;
    tx.addInput(input);
    const inputIndex = tx.inputs.length - 1;
    const satoshis = prevValaIndexTx.outputs[prevValaIndexOutputIndex].satoshis;
    const indexLockingScript = prevValaIndexTx.outputs[0].script;
    const output = new scryptlib_1.bsv.Transaction.Output({ script: indexLockingScript, satoshis });
    if (inputIndex < tx.outputs.length) {
        // With SIGHASH_SINGLE, output needs to be same index as input
        tx.outputs.splice(inputIndex, 0, output);
        tx._outputAmount = undefined;
        tx._updateChangeOutput();
    }
    else {
        tx.addOutput(output);
    }
    const sighashType = Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_SINGLE | Signature.SIGHASH_FORKID;
    // console.log(prevValaIndexOutputIndex)
    // console.log(inputIndex)
    const preimage = getPreimage(prevValaIndexTx, tx, sighashType, prevValaIndexOutputIndex, inputIndex);
    // console.log("Preimage length:", preimage.toString("hex").length)
    // console.log(preimage.toString("hex"))
    const unlockingScript = scryptlib_1.bsv.Script.fromASM(getAsmFromJS([preimage.toString("hex")]));
    tx.inputs[inputIndex].setScript(unlockingScript);
    return tx;
}
exports.addValaIndex = addValaIndex;
function getNewOracleTx(pubKey, prevValaIndexTx, prevValaIndexOutputIndex = 0, relayFee = feeb, version = contracts_1.currentOracleContract) {
    const token = (0, oracle_1.getOracleToken)(pubKey, version);
    // const asm = token.lockingScript.toASM().split(" ")
    // console.log(asm[asm.length - 1])
    const tx = new scryptlib_1.bsv.Transaction();
    const output = new scryptlib_1.bsv.Transaction.Output({ script: token.lockingScript, satoshis: 1 });
    tx.addOutput(output);
    tx.feePerKb(relayFee * 1000);
    addValaIndex(tx, prevValaIndexTx, prevValaIndexOutputIndex);
    // console.log(tx.inputs)
    // console.log(tx.outputs)
    // console.log(tx.serialize())
    return tx;
}
exports.getNewOracleTx = getNewOracleTx;
function getOracleUpdateTx(prevTx, outputIndex = 0, burnSats = 0, details, rabinPrivKey, relayFee = feeb) {
    const satoshis = prevTx.outputs[outputIndex].satoshis + burnSats;
    const detailsHex = details ? (0, hex_1.toHex)(JSON.stringify(details)) : "00";
    const detailsHash = (0, sha_1.sha256)(detailsHex);
    // const token = getOracleToken(pubKey)
    // @ts-ignore
    const prevChunks = prevTx.outputs[outputIndex].script.chunks;
    const newScript = new scryptlib_1.bsv.Script({ chunks: prevChunks.slice(0, prevChunks.length - 1) });
    const chunks = getDataScriptChunks(detailsHash);
    newScript.chunks = newScript.chunks.concat(chunks);
    // console.log(token.lockingScript.toASM())
    // token.setDataPartInASM(detailsHash)
    // console.log(token.lockingScript.toASM())
    const newTx = new scryptlib_1.bsv.Transaction();
    newTx.addOutput(new scryptlib_1.bsv.Transaction.Output({ script: newScript, satoshis }));
    newTx.feePerKb(relayFee * 1000);
    // console.log(prevTx.outputs)
    // console.log(outputIndex)
    const input = scryptlib_1.bsv.Transaction.Input.fromObject({
        prevTxId: prevTx.hash,
        outputIndex: outputIndex,
        script: scryptlib_1.bsv.Script.empty(),
        output: prevTx.outputs[outputIndex] // prevTx of newTx here?
    });
    input.clearSignatures = () => { }; // eslint-disable-line
    input.isFullySigned = () => true;
    newTx.addInput(input);
    const sighashType = Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_SINGLE | Signature.SIGHASH_FORKID;
    const preimage = getPreimage(prevTx, newTx, sighashType, outputIndex);
    // token.txContext = { tx: newTx, inputIndex: 0, inputSatoshis: prevTx.outputs[outputIndex].satoshis }
    let unlockingScriptASM;
    if (details) {
        if (!rabinPrivKey)
            throw new Error("Missing rabin private key");
        const sigContent = detailsHash;
        const signature = (0, oracle_1.getSignature)(sigContent, rabinPrivKey);
        unlockingScriptASM = getAsmFromJS([
            preimage.toString("hex"),
            details ? 1 : 2,
            detailsHex,
            signature.signature,
            signature.paddingByteCount,
            burnSats
        ]);
        // const unlockingScript = token
        //   .update(
        //     new SigHashPreimage(preimage.toString("hex")),
        //     details ? 1 : 2,
        //     new Bytes(detailsHex),
        //     signature.signature,
        //     signature.paddingByteCount,
        //     burnSats
        //   )
        //   .toScript()
        // console.log([
        //   new SigHashPreimage(preimage.toString("hex")).toLiteral(),
        //   details ? 1 : 2,
        //   new Bytes(detailsHex).toLiteral(),
        //   signature.signature.toString(),
        //   signature.paddingByteCount,
        //   burnSats
        // ])
    }
    else {
        // const unlockingScript = token
        //   .update(new SigHashPreimage(preimage.toString("hex")), 2, new Bytes(""), 0n, 0, burnSats)
        //   .toScript()
        unlockingScriptASM = getAsmFromJS([preimage.toString("hex"), 2, "", 0n, 0, burnSats]);
        // console.log([
        //   new SigHashPreimage(preimage.toString("hex")).toLiteral(),
        //   2,
        //   new Bytes("").toLiteral(),
        //   0n,
        //   0,
        //   burnSats
        // ])
    }
    const unlockingScript = scryptlib_1.bsv.Script.fromASM(unlockingScriptASM);
    newTx.inputs[0].setScript(unlockingScript);
    // console.log(newTx.serialize())
    // console.log(prevTx.outputs[outputIndex].satoshis)
    // const asm = prevTx.outputs[outputIndex].script.toASM().split(" ")
    // console.log(asm.slice(asm.length - 1, asm.length).join(" "))
    return newTx;
}
exports.getOracleUpdateTx = getOracleUpdateTx;
function getOracleBurnTx(prevTx, burnSats, outputIndex = 0) {
    return getOracleUpdateTx(prevTx, outputIndex, burnSats);
}
exports.getOracleBurnTx = getOracleBurnTx;
function getOracleUpdateDetailsTx(prevTx, outputIndex = 0, details, rabinPrivKey) {
    return getOracleUpdateTx(prevTx, outputIndex, 0, details, rabinPrivKey);
}
exports.getOracleUpdateDetailsTx = getOracleUpdateDetailsTx;
function getUpdateMarketSettingsTx(prevTx, settings, privateKey, feePerByte = feeb, outputIndex = 0) {
    const prevMarket = getMarketFromScript(prevTx.outputs[outputIndex].script);
    const version = (0, pm_1.getMarketVersion)(prevMarket.version);
    if ((0, lt_1.default)(version.version, "0.4.0")) {
        throw new Error("Market version does not support settings");
    }
    const settingsHex = (0, hex_1.toHex)(JSON.stringify(settings));
    const settingsHash = (0, sha_1.sha256)(settingsHex);
    if (privateKey.publicKey.toString() !== prevMarket.creator.pubKey.toString()) {
        throw new Error("Only market creator can update settings");
    }
    const newMarket = { ...prevMarket, settingsHash };
    const newTx = getUpdateMarketTx(prevTx, newMarket, outputIndex, feePerByte);
    const sighashType = Signature.SIGHASH_ANYONECANPAY | Signature.SIGHASH_SINGLE | Signature.SIGHASH_FORKID;
    const preimage = getPreimage(prevTx, newTx, sighashType, outputIndex);
    const signature = getSignature(preimage, privateKey, sighashType);
    const unlockingScriptASM = getAsmFromJS([
        preimage.toString("hex"),
        5,
        "00",
        0,
        "",
        0,
        "",
        "",
        "",
        0,
        "",
        0,
        0,
        false,
        signature.toString("hex"),
        "",
        0,
        0n,
        0,
        0,
        newTx.outputs[0].satoshis,
        settingsHex
    ]);
    const unlockingScript = scryptlib_1.bsv.Script.fromASM(unlockingScriptASM);
    newTx.inputs[0].setScript(unlockingScript);
    // console.log(getScryptTokenParams(prevMarket))
    // console.log(new SigHashPreimage(preimage.toString("hex")).toLiteral())
    // console.log([
    //   // new SigHashPreimage(preimage.toString("hex")).toLiteral(),
    //   5, // action = Update settings
    //   new Ripemd160("00").toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   new Bytes("").toLiteral(),
    //   0,
    //   new Bytes("").toLiteral(),
    //   0,
    //   0,
    //   false,
    //   new Sig(signature.toString("hex")).toLiteral(),
    //   new Bytes("").toLiteral(),
    //   0,
    //   0n,
    //   0,
    //   0,
    //   newTx.outputs[0].satoshis,
    //   new Bytes(settingsHex).toLiteral()
    // ])
    // console.log(newTx.serialize())
    // console.log(prevTx.outputs[outputIndex].satoshis)
    // const asm = prevTx.outputs[outputIndex].script.toASM().split(" ")
    // console.log(asm.slice(asm.length - opReturnDataLength, asm.length).join(" "))
    return newTx;
}
exports.getUpdateMarketSettingsTx = getUpdateMarketSettingsTx;
function isValidOracleInitOutput(tx, outputIndex = 0) {
    const script = tx.outputs[outputIndex].script;
    const asm = script.toASM().split(" ");
    let opReturnPos;
    try {
        opReturnPos = getOpReturnPos(script);
    }
    catch (e) {
        return false;
    }
    let version;
    if (asm.slice(opReturnPos + 1).length > 1) {
        const versionId = asm[opReturnPos + 1];
        try {
            version = (0, oracle_1.getOracleVersion)(versionId);
        }
        catch (e) {
            return false;
        }
        if ((0, gte_1.default)(version.version, "0.1.3")) {
            if (script.chunks[0].buf.toString("hex") !== valaId)
                return false;
        }
    }
    else {
        version = (0, oracle_1.getOracleVersion)("02fbca51c5c8820b884bcc3d4481a252");
    }
    return isValidScript(script, version) && asm[asm.length - 1] === (0, sha_1.sha256)("00");
}
exports.isValidOracleInitOutput = isValidOracleInitOutput;
function isValidMarketInitOutput(tx, outputIndex = 0) {
    const script = tx.outputs[outputIndex].script;
    let market;
    let version;
    try {
        market = getMarketFromScript(script);
        version = (0, pm_1.getMarketVersion)(market.version);
    }
    catch (e) {
        return false;
    }
    if ((0, gte_1.default)(version.version, "0.4.0")) {
        if (market.settingsHash !== (0, sha_1.sha256)("00"))
            return false;
        let opReturnPos;
        try {
            opReturnPos = getOpReturnPos(script);
        }
        catch (e) {
            return false;
        }
        const opReturnLength = script.chunks.length - opReturnPos - 1;
        if (opReturnLength !== 4)
            return false;
        if (script.chunks[0].buf.toString("hex") !== valaId)
            return false;
    }
    if (!isValidScript(script, version))
        return false;
    return (0, pm_1.isValidMarketInit)(market);
}
exports.isValidMarketInitOutput = isValidMarketInitOutput;
function isValidScript(script, version) {
    return (0, contracts_1.getMd5)(script, version.length, version.argPos, version.args.length) === version.md5;
}
exports.isValidScript = isValidScript;
function getDust(scriptPubKeySize, relayFee = 0.5) {
    return 1;
}
exports.getDust = getDust;
//# sourceMappingURL=transaction.js.map