anyhedge.js

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnyHedgeManager = void 0;
const price_oracle_1 = require("@generalprotocols/price-oracle");
const cashscript_1 = require("cashscript");
const node_fetch_1 = __importDefault(require("node-fetch"));
const constants_1 = require("./constants");
const interfaces_1 = require("./interfaces");
const javascript_util_1 = require("./util/javascript-util");
const anyhedge_util_1 = require("./util/anyhedge-util");
const bitcoincash_util_1 = require("./util/bitcoincash-util");
const libauth_1 = require("@bitauth/libauth");
const mutual_redemption_util_1 = require("./util/mutual-redemption-util");
const anyhedge_contracts_1 = require("@generalprotocols/anyhedge-contracts");
const errors_1 = require("./errors");
/**
 * Class that manages AnyHedge contract operations, such as creation, validation, and payout of AnyHedge contracts.
 */
// Disable ESLint prefer-default-export rule because 'export default' does not play well with commonjs
// eslint-disable-next-line import/prefer-default-export
class AnyHedgeManager {
    /**
     * Initializes an AnyHedge Manager using the specified config options. Note that the `networkProvider` and `electrumCluster`
     * options are mutually exclusive and the `networkProvider` takes precedence. The default network provider automatically
     * connects and disconnects between network requests, so if you need a persistent connection, please use a custom provider.
     *
     * @param [config]                       config object containing configuration options for the AnyHedge Manager.
     * @param [config.authenticationToken]   authentication token used to authenticate network requests to the settlement service
     * @param [config.contractVersion]       string denoting which AnyHedge contract version to use.
     * @param [config.serviceDomain]         fully qualified domain name for the settlement service provider.
     * @param [config.servicePort]           network port number for the settlement service provider.
     * @param [config.serviceScheme]         network scheme for the settlement service provider, either 'http' or 'https'.
     * @param [config.electrumCluster]       electrum cluster to use in BCH network operations.
     * @param [config.networkProvider]       network provider to use for BCH network operations.
     *
     * @see {@link https://gitlab.com/GeneralProtocols/anyhedge/contracts|AnyHedge contracts repository} for a list of contract versions.
     *
     * @example const anyHedgeManager = new AnyHedgeManager({ authenticationToken: '<token>' });
     * @example
     * const config =
     * {
     * 	authenticationToken: '<token>',
     * 	serviceDomain: 'localhost',
     * 	servicePort: 6572,
     * 	serviceScheme: 'http',
     * 	networkProvider: new ElectrumNetworkProvider('mainnet')
     * };
     * const anyHedgeManager = new AnyHedgeManager(config);
     */
    constructor(config = {}) {
        // Extract service URL components from the config object
        this.serviceScheme = config.serviceScheme || constants_1.DEFAULT_SERVICE_SCHEME;
        this.serviceDomain = config.serviceDomain || constants_1.DEFAULT_SERVICE_DOMAIN;
        this.servicePort = config.servicePort || constants_1.DEFAULT_SERVICE_PORT;
        // Store the contract version
        this.contractVersion = config.contractVersion || constants_1.DEFAULT_CONTRACT_VERSION;
        // Store the authentication token
        this.authenticationToken = config.authenticationToken;
        if (config.networkProvider) {
            // Use the provided network provider for BCH network operations if one is provided.
            this.networkProvider = config.networkProvider;
        }
        else {
            // If a custom cluster is provided, connection management is handled by the user.
            const manualConnectionManagement = config.electrumCluster !== undefined;
            // Create a new ElectrumNetworkProvider for BCH network operations, optionally using the provided electrum cluster.
            this.networkProvider = new cashscript_1.ElectrumNetworkProvider('mainnet', config.electrumCluster, manualConnectionManagement);
        }
    }
    get serviceUrl() {
        return `${this.serviceScheme}://${this.serviceDomain}:${this.servicePort}`;
    }
    /*
    // External library functions
    */
    /**
     * Request an authentication token from the settlement service. This token
     * is used to authenticate all requests to the settlement services. Only a single
     * token needs to be generated per consuming application.
     *
     * @param name   name to communicate an identity to the settlement service.
     *
     * @throws {Error} if the request failed.
     * @returns a new authentication token
     */
    async requestAuthenticationToken(name) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['requestAuthenticationToken() <=', arguments]);
        // Request a new authentication token from the settlement service
        const requestParameters = {
            method: 'post',
            body: JSON.stringify({ name }),
            headers: { 'Content-Type': 'application/json' },
        };
        const requestResponse = await node_fetch_1.default(`${this.serviceUrl}/token`, requestParameters);
        // Throw the returned error if the request failed
        if (!requestResponse.ok) {
            throw (new Error(await requestResponse.text()));
        }
        // Retrieve the authentication from the response if the request succeeded
        const authenticationToken = await requestResponse.text();
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['requestAuthenticationToken() =>', authenticationToken]);
        return authenticationToken;
    }
    /**
     * Register a new contract for external management.
     *
     * @param contractAddress            contract address to submit funding for.
     * @param transactionHex             funding transaction as a hex-encoded string.
     * @param [dependencyTransactions]   list of transaction hex strings of transactions that the funding transaction depends on.
     *
     * @throws {Error} if no authentication token is provided or the authentication token is invalid.
     * @throws {Error} if the API call failed in any way (e.g. the transaction failed to broadcast).
     * @returns funding information for the registered contract.
     */
    async submitFundingTransaction(contractAddress, transactionHex, dependencyTransactions) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['submitFundingTransaction() <=', arguments]);
        // Check that an authentication token is set
        if (!this.authenticationToken) {
            throw (new errors_1.MissingAuthenticationTokenError());
        }
        // Submit the funding transaction to the automatic settlement service provider.
        const requestParameters = {
            method: 'post',
            body: JSON.stringify({ contractAddress, transactionHex, dependencyTransactions }),
            headers: { 'Content-Type': 'application/json', Authorization: this.authenticationToken },
        };
        const requestResponse = await node_fetch_1.default(`${this.serviceUrl}/funding`, requestParameters);
        // If the API call was not successful..
        if (!requestResponse.ok) {
            // .. pass through the error to the developers.
            throw (new Error(await requestResponse.text()));
        }
        // If no error is returned, the response can be parsed as JSON.
        const contractFunding = await JSON.parse(await requestResponse.json());
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['submitFundingTransaction() =>', contractFunding]);
        // Return the funding information.
        return contractFunding;
    }
    /**
     * Register a new contract for external management.
     *
     * @param oraclePublicKey                 compressed public key hex string for the oracle that the contract trusts for price messages.
     * @param hedgePublicKey                  compressed public key hex string for the hedge party.
     * @param longPublicKey                   compressed public key hex string for the long party.
     * @param hedgeUnits                      amount in units that the hedge party wants to protect against volatility.
     * @param startPrice                      starting price (units/BCH) of the contract.
     * @param startBlockHeight                blockHeight at which the contract is considered to have been started at.
     * @param earliestLiquidationModifier     minimum number of blocks from the starting height before the contract can be liquidated.
     * @param maturityModifier                exact number of blocks from the starting height that the contract should mature at.
     * @param highLiquidationPriceMultiplier  multiplier for the startPrice determining the upper liquidation price boundary.
     * @param lowLiquidationPriceMultiplier   multiplier for the startPrice determining the lower liquidation price boundary.
     * @param [feeAddress]                    optional fee address acquired from the settlement service to use for this contract.
     * @param [allowAddressReuse]             optional flag to indicate if fee addresses should be allowed to be reused across contracts.
     *
     * @throws {Error} if no authentication token is provided or the authentication token is invalid.
     * @returns contract information for the registered contract.
     */
    async registerContractForSettlement(oraclePublicKey, hedgePublicKey, longPublicKey, hedgeUnits, startPrice, startBlockHeight, earliestLiquidationModifier, maturityModifier, highLiquidationPriceMultiplier, lowLiquidationPriceMultiplier, feeAddress, allowAddressReuse) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['registerContractForSettlement() <=', arguments]);
        // Check that an authentication token is set
        if (!this.authenticationToken) {
            throw (new errors_1.MissingAuthenticationTokenError());
        }
        const contractCreationParameters = {
            // Public keys
            oraclePublicKey,
            hedgePublicKey,
            longPublicKey,
            // Contract parameters
            hedgeUnits,
            startPrice,
            startBlockHeight,
            earliestLiquidationModifier,
            maturityModifier,
            highLiquidationPriceMultiplier,
            lowLiquidationPriceMultiplier,
            feeAddress,
            allowAddressReuse,
        };
        // Register the contract with the automatic settlement service provider.
        const requestParameters = {
            method: 'post',
            body: JSON.stringify(contractCreationParameters),
            headers: { 'Content-Type': 'application/json', Authorization: this.authenticationToken },
        };
        const contractResponse = await node_fetch_1.default(`${this.serviceUrl}/contract`, requestParameters);
        // If the API call was not successful..
        if (!contractResponse.ok) {
            // .. pass through the error to the developers.
            throw (new Error(await contractResponse.text()));
        }
        // If no error is returned, the response can be parsed as JSON.
        const contractData = await JSON.parse(await contractResponse.json());
        // Write log entry for easier debugging.
        javascript_util_1.debug.action(`Registered contract '${contractData.address}' with ${this.serviceDomain} for automatic settlement.`);
        // Validate that the contract returned is identical to a contract created locally.
        const contractValidity = await this.validateContract(contractData.address, oraclePublicKey, hedgePublicKey, longPublicKey, hedgeUnits, startPrice, startBlockHeight, earliestLiquidationModifier, maturityModifier, highLiquidationPriceMultiplier, lowLiquidationPriceMultiplier);
        // If the contract is invalid..
        if (!contractValidity) {
            // Write a log entry explaining the problem and throw the error.
            const errorMsg = `Contract registration for '${contractData.address}' with ${this.serviceDomain} resulted in an invalid contract.`;
            javascript_util_1.debug.errors(errorMsg);
            throw (new Error(errorMsg));
        }
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['registerContractForSettlement() =>', contractData]);
        // Return the contract information.
        return contractData;
    }
    /**
     * Request a new fee address from the settlement service.
     *
     * @throws {Error} if no authentication token is provided or the authentication token is invalid.
     * @returns a new fee address that can be used with the register() call.
     */
    async getFeeAddress() {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['getFeeAddress() <=', arguments]);
        // Check that an authentication token is set
        if (!this.authenticationToken) {
            throw (new errors_1.MissingAuthenticationTokenError());
        }
        // Request a new fee address from the settlement service
        const requestParameters = {
            method: 'get',
            headers: { Authorization: this.authenticationToken },
        };
        const feeAddressResponse = await node_fetch_1.default(`${this.serviceUrl}/fee`, requestParameters);
        // If the API call was not successful..
        if (!feeAddressResponse.ok) {
            // .. pass through the error to the developers.
            throw (new Error(await feeAddressResponse.text()));
        }
        // If no error is returned, the response can be parsed as JSON.
        const { feeAddress } = await JSON.parse(await feeAddressResponse.json());
        // Log the outcome of the request.
        javascript_util_1.debug.action(`Retrieved new fee address '${feeAddress}' from ${this.serviceDomain}.`);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['getFeeAddress() =>', feeAddress]);
        // Return the fee new address.
        return feeAddress;
    }
    /**
     * Request the contract data and status of a contract with the settlement service.
     *
     * @param contractAddress   address to retrieve status for
     * @param [privateKeyWIF]   private key WIF of one of the contract's parties.
     *
     * @throws {Error} if no authentication token is provided or the authentication token is invalid.
     * @throws {Error} if no private key WIF was provided *and* the authentication token is different than the one used for registration.
     * @throws {Error} if an invalid WIF was provided.
     * @throws {Error} if a private key WIF was provided that does not belong to either of the contract parties.
     * @throws {Error} if no contract is registered at the settlement service for the given address.
     * @throws {Error} if the API call is unsuccessful.
     * @returns the contract data and status of the contract
     */
    async getContractStatus(contractAddress, privateKeyWIF) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['getContractStatus() <=', arguments]);
        // Check that an authentication token is set.
        if (!this.authenticationToken) {
            throw (new errors_1.MissingAuthenticationTokenError());
        }
        // Initialize empty public key and signature.
        let publicKey = '';
        let signature = '';
        // Generate a public key and signature if a private key WIF was provided.
        if (privateKeyWIF) {
            // Decode the private key WIF if it was provided.
            const privateKey = await bitcoincash_util_1.decodeWIF(privateKeyWIF);
            // Derive the corresponding public key.
            publicKey = await bitcoincash_util_1.derivePublicKey(privateKey);
            // Hash the contract address to be signed.
            const messageHash = await bitcoincash_util_1.sha256(libauth_1.utf8ToBin(contractAddress));
            // Sign the message hash using the provided private key.
            const secp256k1 = await libauth_1.instantiateSecp256k1();
            const signatureBin = secp256k1.signMessageHashSchnorr(libauth_1.hexToBin(privateKey), messageHash);
            // Convert the signature to a hex string.
            signature = libauth_1.binToHex(signatureBin);
        }
        // Request the contract's status from the settlement service
        const queryParameters = `contractAddress=${contractAddress}&signature=${signature}&publicKey=${publicKey}`;
        const requestParameters = {
            method: 'get',
            headers: { Authorization: this.authenticationToken },
        };
        const contractResponse = await node_fetch_1.default(`${this.serviceUrl}/status?${queryParameters}`, requestParameters);
        // If the API call was not successful..
        if (!contractResponse.ok) {
            // .. pass through the error to the developers.
            throw (new Error(await contractResponse.text()));
        }
        // If no error is returned, the response can be parsed as JSON.
        const contractData = await JSON.parse(await contractResponse.json());
        javascript_util_1.debug.action(`Retrieved contract status for '${contractData.address}' from ${this.serviceDomain}.`);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['getContractStatus() =>', contractData]);
        return contractData;
    }
    /**
     * Validates that a given contract address matches specific contract parameters.
     *
     * @param contractAddress                  contract address encoded according to the cashaddr specification.
     * @param oraclePublicKey                  compressed public key hex string for the oracle that the contract trusts for price messages.
     * @param hedgePublicKey                   compressed public key hex string for the hedge party.
     * @param longPublicKey                    compressed public key hex string for the long party.
     * @param hedgeUnits                       amount in units that the hedge party wants to protect against volatility.
     * @param startPrice                       starting price (units/BCH) of the contract.
     * @param startBlockHeight                 blockHeight at which the contract is considered to have been started at.
     * @param earliestLiquidationModifier      minimum number of blocks from the starting height before the contract can be liquidated.
     * @param maturityModifier                 exact number of blocks from the starting height that the contract should mature at.
     * @param highLiquidationPriceMultiplier   multiplier for the startPrice determining the upper liquidation price boundary.
     * @param lowLiquidationPriceMultiplier    multiplier for the startPrice determining the lower liquidation price boundary.
     *
     * @returns true if the contract address and parameters match, otherwise false.
     */
    async validateContract(contractAddress, oraclePublicKey, hedgePublicKey, longPublicKey, hedgeUnits, startPrice, startBlockHeight, earliestLiquidationModifier, maturityModifier, highLiquidationPriceMultiplier, lowLiquidationPriceMultiplier) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['validateContract() <=', arguments]);
        // Prepare the contract.
        const contractData = await this.createContract(oraclePublicKey, hedgePublicKey, longPublicKey, hedgeUnits, startPrice, startBlockHeight, earliestLiquidationModifier, maturityModifier, highLiquidationPriceMultiplier, lowLiquidationPriceMultiplier);
        // Build the contract.
        const contract = await this.compileContract(contractData.parameters);
        // Calculate contract validity.
        const contractValidity = (contractAddress === contract.address);
        if (contractValidity) {
            // Write log entry for easier debugging.
            javascript_util_1.debug.action(`Validated a contract address (${contractAddress}) against provided contract parameters.`);
            // eslint-disable-next-line prefer-rest-params
            javascript_util_1.debug.object(arguments);
        }
        else {
            // Write log entry for easier debugging.
            javascript_util_1.debug.errors(`Failed to validate the provided contract address (${contractAddress}) against provided contract parameters generated address (${contract.address}).`);
            // eslint-disable-next-line prefer-rest-params
            javascript_util_1.debug.object(arguments);
        }
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['validateContract() =>', contractValidity]);
        // Return the validity of the contract.
        return contractValidity;
    }
    /**
     * Build and broadcast a custodial mutual redemption transaction with arbitrary transaction details.
     *
     * @param hedgePrivateKeyWIF    hedge's private key WIF.
     * @param longPrivateKeyWIF     long's private key WIF.
     * @param transactionProposal   unsigned transaction proposal for the mutual redemption.
     * @param contractParameters    contract parameters of the relevant contract.
     *
     * @throws {Error} if any of the private key WIF strings is not valid.
     * @throws {Error} if any of the private key WIFs does not belong to a party of the contract.
     * @throws {Error} if the generated transaction could not successfully be broadcasted.
     * @returns transaction ID of the broadcasted mutual redemption transaction.
     */
    async custodialMutualArbitraryPayout(hedgePrivateKeyWIF, longPrivateKeyWIF, transactionProposal, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['custodialMutualArbitraryPayout() <=', arguments]);
        // Sign the proposal using both keys.
        const hedgeProposal = await this.signMutualArbitraryPayout(hedgePrivateKeyWIF, transactionProposal, contractParameters);
        const longProposal = await this.signMutualArbitraryPayout(longPrivateKeyWIF, transactionProposal, contractParameters);
        // Build and broadcast the mutual redemption transaction with both signed proposals.
        const transactionID = await this.completeMutualRedemption(hedgeProposal, longProposal, contractParameters);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['custodialMutualArbitraryPayout() =>', transactionID]);
        return transactionID;
    }
    /**
     * Build and broadcast a custodial mutual redemption transaction that mimics a
     * maturation before the actual maturation block height.
     *
     * @param hedgePrivateKeyWIF   hedge's private key WIF.
     * @param longPrivateKeyWIF    long's private key WIF.
     * @param contractFunding      the specific Contract Funding to use in the custodial early maturation.
     * @param settlementPrice      price to use in settlement.
     * @param contractParameters   contract parameters of the relevant contract.
     *
     * @throws {Error} if any of the private key WIF strings is not valid.
     * @throws {Error} if any of the private key WIFs does not belong to a party of the contract.
     * @throws {Error} if the generated transaction could not successfully be broadcasted.
     * @returns transaction ID of the broadcasted mutual redemption transaction.
     */
    async custodialMutualEarlyMaturation(hedgePrivateKeyWIF, longPrivateKeyWIF, contractFunding, settlementPrice, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['custodialMutualEarlyMaturation() <=', arguments]);
        // Sign the settlement using both keys.
        const hedgeProposal = await this.signMutualEarlyMaturation(hedgePrivateKeyWIF, contractFunding, settlementPrice, contractParameters);
        const longProposal = await this.signMutualEarlyMaturation(longPrivateKeyWIF, contractFunding, settlementPrice, contractParameters);
        // Build and broadcast the mutual redemption transaction with both signed proposals.
        const transactionID = await this.completeMutualRedemption(hedgeProposal, longProposal, contractParameters);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['custodialMutualEarlyMaturation() =>', transactionID]);
        return transactionID;
    }
    /**
     * Build and broadcast a custodial mutual redemption transaction that refunds
     * the contract's funds based on the provided contract metadata. Optionally
     * allows you to provide separate refund addresses. If these are omitted,
     * the mutual redemption public keys are used to receive the refunds.
     *
     * @param hedgePrivateKeyWIF     hedge's private key WIF.
     * @param longPrivateKeyWIF      long's private key WIF.
     * @param contractFunding        the specific Contract Funding to use in the custodial refund.
     * @param contractParameters     contract parameters of the relevant contract.
     * @param contractMetadata       contract metadata of the relevant contract.
     * @param [hedgeRefundAddress]   hedge's address to receive the refund.
     * @param [longRefundAddress]    long's address to receive the refund.
     *
     * @throws {Error} if any of the private key WIF strings is not valid.
     * @throws {Error} if any of the private key WIFs does not belong to a party of the contract.
     * @throws {Error} if the generated transaction could not successfully be broadcasted.
     * @returns transaction ID of the broadcasted mutual redemption transaction.
     */
    async custodialMutualRefund(hedgePrivateKeyWIF, longPrivateKeyWIF, contractFunding, contractParameters, contractMetadata, hedgeRefundAddress, longRefundAddress) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['custodialMutualRefund() <=', arguments]);
        // Sign the refund using hedge key.
        const hedgeProposal = await this.signMutualRefund(hedgePrivateKeyWIF, contractFunding, contractParameters, contractMetadata, hedgeRefundAddress, longRefundAddress);
        // Sign the refund using long key.
        const longProposal = await this.signMutualRefund(longPrivateKeyWIF, contractFunding, contractParameters, contractMetadata, hedgeRefundAddress, longRefundAddress);
        // Build and broadcast the mutual redemption transaction with both signed proposals.
        const transactionID = await this.completeMutualRedemption(hedgeProposal, longProposal, contractParameters);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['custodialMutualRefund() =>', transactionID]);
        return transactionID;
    }
    /**
     * Sign a mutual redemption transaction that mimics a maturation before the
     * actual maturation block height. Both parties need to call this function with
     * the same input and settlement price. Both signed transaction proposals must then
     * be passed into the completeMutualRedemption() function to broadcast the transaction.
     *
     * @param privateKeyWIF        private key WIF of one of the contract's parties.
     * @param contractFunding      the specific Contract Funding to use in the mutual early maturation.
     * @param settlementPrice      price to use in settlement.
     * @param contractParameters   contract parameters of the relevant contract.
     *
     * @throws {Error} if the private key WIF string is not valid.
     * @throws {Error} if the private key WIF does not belong to a party of the contract.
     * @returns a signed settlement transaction proposal.
     */
    async signMutualEarlyMaturation(privateKeyWIF, contractFunding, settlementPrice, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['signMutualEarlyMaturation() <=', arguments]);
        // Check that the provided settlement price is within the contract's bounds.
        if (settlementPrice < contractParameters.lowLiquidationPrice || settlementPrice > contractParameters.highLiquidationPrice) {
            throw (new Error('Settlement price is out of liquidation bounds, which is unsupported by a mutual early maturation.'));
        }
        // Calculate settlement outcome.
        const outcome = await this.calculateSettlementOutcome(contractParameters, contractFunding.fundingSatoshis, settlementPrice);
        // Derive hedge/long settlement addresses.
        const hedgeAddress = bitcoincash_util_1.lockScriptToAddress(contractParameters.hedgeLockScript.slice(2));
        const longAddress = bitcoincash_util_1.lockScriptToAddress(contractParameters.longLockScript.slice(2));
        // Build transaction proposal based on these parameters.
        const inputs = [anyhedge_util_1.contractFundingToCoin(contractFunding)];
        const outputs = [
            { to: hedgeAddress, amount: outcome.hedgePayoutSats },
            { to: longAddress, amount: outcome.longPayoutSats },
        ];
        const unsignedProposal = { inputs, outputs };
        // Sign the proposal.
        const signedProposal = await this.signMutualArbitraryPayout(privateKeyWIF, unsignedProposal, contractParameters);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['signMutualEarlyMaturation() =>', signedProposal]);
        return signedProposal;
    }
    /**
     * Sign a mutual redemption transaction that refunds the contract's funds based on
     * the provided contract metadata. Optionally allows you to provide separate
     * refund addresses. If these are omitted, the mutual redemption public keys
     * are used to receive the refunds. Both parties need to call this function with
     * the same contract funding, contract metadata and refund addresses. Both signed
     * transaction proposals must then be passed into the completeMutualRedemption()
     * function to broadcast the transaction.
     *
     * @param privateKeyWIF          private key WIF of one of the contract's parties.
     * @param contractFunding        the specific Contract Funding to use in the mutual refund.
     * @param contractParameters     contract parameters of the relevant contract.
     * @param contractMetadata       contract metadata of the relevant contract.
     * @param [hedgeRefundAddress]   hedge's address to receive the refund.
     * @param [longRefundAddress]    long's address to receive the refund.
     *
     * @throws {Error} if the private key WIF string is not valid.
     * @throws {Error} if the private key WIF does not belong to a party of the contract.
     * @returns a signed refund transaction proposal.
     */
    async signMutualRefund(privateKeyWIF, contractFunding, contractParameters, contractMetadata, hedgeRefundAddress, longRefundAddress) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['signMutualRefund() <=', arguments]);
        // If no refund addresses are provided, derive them from the private keys
        const hedgeAddress = hedgeRefundAddress || await bitcoincash_util_1.encodeCashAddressP2PKH(contractParameters.hedgeMutualRedeemPubk);
        const longAddress = longRefundAddress || await bitcoincash_util_1.encodeCashAddressP2PKH(contractParameters.longMutualRedeemPubk);
        // To ensure the transaction is valid, we put the minimum output value to DUST
        const hedgeAmount = Math.max(contractMetadata.hedgeInputSats, constants_1.DUST_LIMIT);
        const longAmount = Math.max(contractMetadata.longInputSats, constants_1.DUST_LIMIT);
        // Build transaction proposal based on the provided parameters.
        const inputs = [anyhedge_util_1.contractFundingToCoin(contractFunding)];
        const outputs = [
            { to: hedgeAddress, amount: hedgeAmount },
            { to: longAddress, amount: longAmount },
        ];
        const unsignedProposal = { inputs, outputs };
        // Sign the proposal.
        const signedProposal = await this.signMutualArbitraryPayout(privateKeyWIF, unsignedProposal, contractParameters);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['signMutualRefund() =>', signedProposal]);
        return signedProposal;
    }
    /**
     * Sign a mutual redemption transaction proposal with arbitrary transaction details.
     * Both parties need to call this function with the same transaction details.
     * Both signed transaction proposals must then be passed into the
     * completeMutualRedemption() function to broadcast the transaction.
     *
     * @param privateKeyWIF         private key WIF of one of the contract's parties.
     * @param transactionProposal   An unsigned proposal for a transaction.
     * @param contractParameters    contract parameters for the relevant contract.
     *
     * @throws {Error} if the private key WIF string is not valid.
     * @throws {Error} if the private key WIF does not belong to a party of the contract.
     * @throws {Error} if a valid transaction is generated during the preparation.
     * @returns updated transaction proposal with the resolved variables added in.
     */
    async signMutualArbitraryPayout(privateKeyWIF, transactionProposal, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['signMutualArbitraryPayout() <=', arguments]);
        // Check that there are no obvious errors in the transaction proposal.
        // Check that there are no outputs below the DUST amount.
        if (transactionProposal.outputs.find((output) => output.amount < constants_1.DUST_LIMIT)) {
            throw (new Error(`One of the outputs in the transaction proposal is below the DUST amount of ${constants_1.DUST_LIMIT}.`));
        }
        // Get the contract's redeem script.
        const contract = await this.compileContract(contractParameters);
        const redeemScriptHex = contract.getRedeemScriptHex();
        // Generate unlocking data from the private key WIF
        const unlockingData = await mutual_redemption_util_1.unlockingDataFromWIF(privateKeyWIF, contractParameters);
        // Create a list of unlocking data for every input (same data for every input).
        const unlockingDataPerInput = transactionProposal.inputs.map(() => unlockingData);
        // Attempt to generate a transaction using the passed parameters.
        const attempt = await mutual_redemption_util_1.attemptTransactionGeneration(transactionProposal, redeemScriptHex, unlockingDataPerInput);
        // Check that the attempt was not successful (should never happen).
        if (attempt.success) {
            throw (new Error('Internal Error: should not be able to generate valid mutual redemption without both parties'));
        }
        // Extract the relevant redemption data list from the transaction generation attempt.
        const redemptionDataList = mutual_redemption_util_1.extractRedemptionDataList(attempt);
        // Update the transaction proposal by adding the new redemption data list.
        const signedProposal = Object.assign(Object.assign({}, transactionProposal), { redemptionDataList });
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['signMutualArbitraryPayout() =>', signedProposal]);
        return signedProposal;
    }
    /**
     * Complete a mutual redemption by generating a valid transaction from both parties'
     * signed proposals and broadcasting it. Both parties need to generate and sign the same
     * transaction proposal using signMutualEarlyMaturation(), signMutualRefund() or
     * signMutualArbitraryPayout().
     *
     * @param signedProposal1      transaction proposal signed by one of the two parties.
     * @param signedProposal2      transaction proposal signed by the other party.
     * @param contractParameters   contract parameters for the relevant contract.
     *
     * @throws {Error} if any proposal is unsigned.
     * @throws {Error} if the transaction details of both proposals don't match.
     * @throws {Error} if the redemption data lists of the proposals have different lengths.
     * @throws {Error} if both proposals are signed by the same party.
     * @throws {Error} if the generated transaction could not successfully be broadcasted.
     * @returns transaction ID of the broadcasted mutual redemption transaction.
     */
    async completeMutualRedemption(signedProposal1, signedProposal2, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['completeMutualRedemption() <=', arguments]);
        // Check that the transaction proposal includes redemption data
        if (!signedProposal1.redemptionDataList || !signedProposal2.redemptionDataList) {
            const errorMsg = 'Transaction proposal does not include any redemption data. '
                + 'Make sure that both parties signed their transaction proposals using '
                + 'signMutualRedemption(), signSettlement() or signRefund().';
            throw (new Error(errorMsg));
        }
        // Merge both proposals, combining their redemption data.
        const mergedProposal = mutual_redemption_util_1.mergeSignedProposals(signedProposal1, signedProposal2);
        // Get the contract's redeem script.
        const contract = await this.compileContract(contractParameters);
        const redeemScriptHex = contract.getRedeemScriptHex();
        // Generate unlocking data for all inputs of the transaction using the passed redemption data
        const unlockingDataPerInput = mergedProposal.redemptionDataList.map(mutual_redemption_util_1.unlockingDataFromRedemptionData);
        // Attempt to generate a transaction using the passed parameters.
        const attempt = await mutual_redemption_util_1.attemptTransactionGeneration(mergedProposal, redeemScriptHex, unlockingDataPerInput);
        // Check that the transaction generation didn't fail (happens if the proposal was only signed by a single party)
        if (!attempt.success) {
            const errorMsg = 'Mutual redemption could not successfully be completed. '
                + 'Make sure that the passed proposals are signed by different parties.';
            throw (new Error(errorMsg));
        }
        // Hex encode the generated transaction.
        const transactionHex = libauth_1.binToHex(libauth_1.encodeTransaction(attempt.transaction));
        // Broadcast the transaction.
        const broadcastResult = await bitcoincash_util_1.broadcastTransaction(transactionHex, this.networkProvider);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['completeMutualRedemption() =>', broadcastResult]);
        return broadcastResult;
    }
    /**
     * Liquidates a contract.
     *
     * @param oraclePublicKey      compressed public key hex string of the oracle that provided the price message.
     * @param oracleMessage        price message hex string to use for liquidation.
     * @param oracleSignature      signature hex string for the price message.
     * @param hedgePublicKey       compressed public key hex string of the hedge party.
     * @param longPublicKey        compressed public key hex string of the long party.
     * @param contractFunding      the specific Contract Funding to use in the payout.
     * @param contractMetadata     contract metadata required to determine available satoshis.
     * @param contractParameters   contract parameters required to simulate liquidation outcome.
     *
     * @returns ContractSettlement object containing the details of the liquidation.
     */
    async liquidateContractFunding(oraclePublicKey, oracleMessage, oracleSignature, contractFunding, contractMetadata, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['liquidateContractFunding() <=', arguments]);
        // Write log entry for easier debugging.
        javascript_util_1.debug.action(`Attempting to liquidate contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}'.`);
        // Parse the oracle message.
        const oracleData = await price_oracle_1.OracleData.parsePriceMessage(libauth_1.hexToBin(oracleMessage));
        // Validate that the oracle message block height is not at the maturation height.
        if (oracleData.blockHeight === contractParameters.maturityHeight) {
            // Define an error message
            const errorMessage = `Cannot liquidate contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}' at its maturation height.`;
            // Log the error message.
            javascript_util_1.debug.errors(errorMessage);
            // Throw the error.
            throw (new Error(errorMessage));
        }
        // Validate that the oracle price is strictly outside liquidation boundaries.
        if (oracleData.price > contractParameters.lowLiquidationPrice && oracleData.price < contractParameters.highLiquidationPrice) {
            // Define an error message
            const errorMessage = `Cannot liquidate contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}' at a price within the contract boundaries.`;
            // Log the error message.
            javascript_util_1.debug.errors(errorMessage);
            // Throw the error.
            throw (new Error(errorMessage));
        }
        // Settle the contract funding.
        const settlementData = await this.settleContractFunding(oraclePublicKey, oracleMessage, oracleSignature, contractFunding, contractMetadata, contractParameters);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['liquidateContractFunding() =>', settlementData]);
        // Return the liquidation result.
        return settlementData;
    }
    /**
     * Matures a contract.
     *
     * @param oraclePublicKey      compressed public key hex string of the oracle that provided the price message.
     * @param oracleMessage        price message hex string to use for maturation.
     * @param oracleSignature      signature hex string for the price message.
     * @param hedgePublicKey       compressed public key hex string of the hedge party.
     * @param longPublicKey        compressed public key hex string of the long party.
     * @param contractFunding      the specific Contract Funding to use in the maturation.
     * @param contractMetadata     contract metadata required to determine available satoshis.
     * @param contractParameters   contract parameters required to simulate maturation outcome.
     *
     * @returns ContractSettlement object containing the details of the maturation.
     */
    async matureContractFunding(oraclePublicKey, oracleMessage, oracleSignature, contractFunding, contractMetadata, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['matureContractFunding() <=', arguments]);
        // Write log entry for easier debugging.
        javascript_util_1.debug.action(`Attempting to mature contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}'.`);
        // Parse the oracle message.
        const oracleData = await price_oracle_1.OracleData.parsePriceMessage(libauth_1.hexToBin(oracleMessage));
        // Validate that the oracle messages block height is equal to the contracts maturity height.
        if (oracleData.blockHeight !== contractParameters.maturityHeight) {
            // Define an error message
            const errorMessage = `Cannot mature contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}' before its maturity height.`;
            // Log the error message.
            javascript_util_1.debug.errors(errorMessage);
            // Throw the error.
            throw (new Error(errorMessage));
        }
        // NOTE: We let the miners validate that the current block height is after the maturity height, to avoid network lookups.
        // Validate the that oracle messages block sequence number is 1.
        if (oracleData.blockSequence !== 1) {
            // Define an error message
            const errorMessage = `Cannot mature contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}' with an oracle block sequence other than 1.`;
            // Log the error message.
            javascript_util_1.debug.errors(errorMessage);
            // Throw the error.
            throw (new Error(errorMessage));
        }
        // Settle the contract funding.
        const settlementData = await this.settleContractFunding(oraclePublicKey, oracleMessage, oracleSignature, contractFunding, contractMetadata, contractParameters);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['matureContractFunding() =>', settlementData]);
        // Return the liquidation result.
        return settlementData;
    }
    /**
     * Settles a contract (this includes both maturation and liquidation).
     *
     * @param oraclePublicKey      compressed public key hex string of the oracle that provided the price message.
     * @param oracleMessage        price message hex string to use for settlement.
     * @param oracleSignature      signature hex string for the price message.
     * @param hedgePublicKey       compressed public key hex string of the hedge party.
     * @param longPublicKey        compressed public key hex string of the long party.
     * @param contractFunding      the specific Contract Funding to use in the settlement.
     * @param contractMetadata     contract metadata required to determine available satoshis.
     * @param contractParameters   contract parameters required to simulate settlement outcome.
     *
     * @returns ContractSettlement object containing the details of the settlement.
     */
    async settleContractFunding(oraclePublicKey, oracleMessage, oracleSignature, contractFunding, contractMetadata, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['settleContractFunding() <=', arguments]);
        // Write log entry for easier debugging.
        javascript_util_1.debug.action(`Attempting to settle contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}'.`);
        // Parse the oracle message.
        const oracleData = await price_oracle_1.OracleData.parsePriceMessage(libauth_1.hexToBin(oracleMessage));
        // Validate that the oracle message block height is equal or higher than the contracts earliest liquidate height.
        if (oracleData.blockHeight < contractParameters.earliestLiquidationHeight) {
            // Define an error message
            const errorMessage = `Cannot settle contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}' before its earliest liquidation height.`;
            // Log the error message.
            javascript_util_1.debug.errors(errorMessage);
            // Throw the error.
            throw (new Error(errorMessage));
        }
        // Validate that the oracle message block height is not after the maturation height.
        if (oracleData.blockHeight > contractParameters.maturityHeight) {
            // Define an error message
            const errorMessage = `Cannot settle contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}' after its maturation height.`;
            // Log the error message.
            javascript_util_1.debug.errors(errorMessage);
            // Throw the error.
            throw (new Error(errorMessage));
        }
        // Validate that the oracles price is not zero or negative.
        if (oracleData.price <= 0) {
            // Define an error message
            const errorMessage = `Cannot settle contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}' at a price of <= 0.`;
            // Log the error message.
            javascript_util_1.debug.errors(errorMessage);
            // Throw the error.
            throw (new Error(errorMessage));
        }
        // Calculate contract outcomes.
        const totalSats = anyhedge_util_1.calculateTotalSats(contractMetadata);
        const outcome = await this.calculateSettlementOutcome(contractParameters, totalSats, oracleData.price);
        // Redeem the contract.
        const settlementTransaction = await this.automatedPayout(oraclePublicKey, oracleMessage, oracleSignature, outcome.hedgePayoutSats, outcome.longPayoutSats, contractFunding, contractParameters);
        // Mark the settlement as LIQUIDATION by default.
        let settlementType = interfaces_1.SettlementType.LIQUIDATION;
        // If the oracle message is the first message of the maturity block, it is a MATURATION.
        if (oracleData.blockHeight === contractParameters.maturityHeight && oracleData.blockSequence === 1) {
            settlementType = interfaces_1.SettlementType.MATURATION;
        }
        // Assemble a ContractSettlement object representing the settlement.
        const settlementData = {
            spendingTransaction: settlementTransaction,
            settlementType: settlementType,
            hedgeSatoshis: outcome.hedgePayoutSats,
            longSatoshis: outcome.longPayoutSats,
            oracleMessage: oracleMessage,
            oraclePublicKey: oraclePublicKey,
            oracleSignature: oracleSignature,
            oraclePrice: oracleData.price,
        };
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['settleContractFunding() =>', settlementData]);
        // Return the settlement result.
        return settlementData;
    }
    /**
     * Redeems the contract with arbitrary numbers.
     *
     * @param oraclePublicKey      compressed public key hex string of the oracle that provided the price message.
     * @param oracleMessage        price message hex string to use for payout.
     * @param oracleSignature      signature hex string for the price message.
     * @param hedgePublicKey       compressed public key hex string of the hedge party.
     * @param longPublicKey        compressed public key hex string of the long party.
     * @param hedgePayoutSats      number of satoshis to pay out to the hedge party.
     * @param longPayoutSats       number of satoshis to pay out to the long party.
     * @param contractFunding      the specific Contract Funding to use in the payout.
     * @param contractParameters   contract parameters required to unlock the redemption function.
     *
     * @returns the transaction ID of a successful redemption.
     *
     * @private
     */
    async automatedPayout(oraclePublicKey, oracleMessage, oracleSignature, hedgePayoutSats, longPayoutSats, contractFunding, contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['automatedPayout() <=', arguments]);
        // Write log entry for easier debugging.
        javascript_util_1.debug.action(`Attempting to payout contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}'.`);
        try {
            // Build the contract instance.
            const contract = await this.compileContract(contractParameters);
            // Generate a redeem private key.
            const redeemPrivateKey = await anyhedge_util_1.deriveRedemptionKeyFromAddress(contract.address);
            // Store the redeem wallet's public key as the preimage public key.
            const preimagePublicKey = await bitcoincash_util_1.derivePublicKey(redeemPrivateKey);
            const preimageSigTemplate = new cashscript_1.SignatureTemplate(libauth_1.hexToBin(redeemPrivateKey));
            // Build the payout transaction.
            const payoutTransaction = await anyhedge_util_1.buildPayoutTransaction(contract, contractParameters, contractFunding, preimageSigTemplate, preimagePublicKey, oracleMessage, oracleSignature, hedgePayoutSats, longPayoutSats);
            // Broadcast the transaction.
            const broadcastResult = await this.broadcastTransaction(payoutTransaction);
            // Output function result for easier collection of test data.
            javascript_util_1.debug.result(['automatedPayout() =>', broadcastResult]);
            // Return the broadcast result.
            return broadcastResult;
        }
        catch (error) {
            // Define a base error message.
            const baseErrorMessage = `Failed to payout contract funding '${anyhedge_util_1.contractFundingToOutpoint(contractFunding)}'`;
            // Log an error message.
            javascript_util_1.debug.errors(`${baseErrorMessage}: `, error);
            // If the error includes a meep command, we remove it before passing it on.
            const errorMessageExcludingMeep = error.message ? error.message.split('\nmeep')[0] : error;
            // Throw the error.
            throw (new Error(`${baseErrorMessage}: ${errorMessageExcludingMeep}`));
        }
    }
    /*
    // Internal library functions
    */
    /**
     * Wrapper that broadcasts a prepared transaction using the CashScript SDK.
     *
     * @param transactionBuilder   fully prepared transaction builder ready to execute its broadcast() function.
     *
     * @returns the transaction ID of a successful transaction.
     *
     * @private
     */
    async broadcastTransaction(transactionBuilder) {
        try {
            // Broadcast the raw transaction
            const { txid } = await transactionBuilder.send();
            return txid;
        }
        catch (error) {
            // Log an error message.
            javascript_util_1.debug.errors('Failed to broadcast transaction: ', error);
            // Build and log raw transaction hex
            const rawTransactionHex = await transactionBuilder.build();
            javascript_util_1.debug.errors(rawTransactionHex);
            // Throw the error.
            throw (error);
        }
    }
    /**
     * Retrieve a list of all ContractFunding instances for a contract.
     *
     * @param contractParameters   Contract parameters for the relevant contract.
     *
     * @returns list of contract fundings for a contract.
     */
    async getContractFundings(contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['getContractFundings() <=', arguments]);
        // Build the contract.
        const contract = await this.compileContract(contractParameters);
        // Retrieve contract's coins as CashScript UTXOs.
        const coins = await contract.getUtxos();
        // Format the CashScript UTXOs as ContractFunding interfaces.
        const fundings = coins.map(anyhedge_util_1.contractCoinToFunding);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['getContractFundings() =>', fundings]);
        return fundings;
    }
    /**
     * Simulates contract settlement outcome based on contract parameters, total satoshis in the contract and the redemption price.
     *
     * @param contractParameters   contract parameters including price boundaries and truncation information.
     * @param totalSats            total number of satoshis to simulate distribution of.
     * @param redeemPrice          price (units/BCH) to base the redemption simulation on.
     *
     * @returns the TXID of a successful transaction.
     *
     * @private
     */
    async calculateSettlementOutcome(contractParameters, totalSats, redeemPrice) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['calculateSettlementOutcome() <=', arguments]);
        // Throw an error if provided parameters are not integers
        if (!javascript_util_1.isInt(totalSats) || !javascript_util_1.isInt(redeemPrice)) {
            throw (new Error('Provided parameters must be integers'));
        }
        // Store truncation level information in local variables for brevity
        const highLowDelta = libauth_1.hexToBin(contractParameters.highLowDeltaTruncatedZeroes).byteLength;
        const lowTruncSize = libauth_1.hexToBin(contractParameters.lowTruncatedZeroes).byteLength;
        // calculate the clamped price for the contract outcomes.
        const clampedPrice = Math.max(Math.min(redeemPrice, contractParameters.highLiquidationPrice), contractParameters.lowLiquidationPrice);
        // Divide the untruncated hedge payout sats with the clamped price
        // and untruncate it to the low truncation level.
        const hedgeDivHighTrunc = Math.floor(contractParameters.hedgeUnitsXSatsPerBchHighTrunc / clampedPrice);
        const hedgeDivLowTrunc = anyhedge_util_1.untruncScriptNum(hedgeDivHighTrunc, highLowDelta);
        // Mod the untruncated hedge payout sats with the clamped price.
        const hedgeModHighTrunc = contractParameters.hedgeUnitsXSatsPerBchHighTrunc % clampedPrice;
        // Calculate mod extension size and apply mod extension.
        const modExtensionSize = Math.min(4 - anyhedge_util_1.scriptNumSize(hedgeModHighTrunc), highLowDelta);
        const hedgeModExt = anyhedge_util_1.untruncScriptNum(hedgeModHighTrunc, modExtensionSize);
        // Calculate price truncation size and apply price truncation.
        const priceTruncSize = highLowDelta - modExtensionSize;
        const truncatedPrice = anyhedge_util_1.truncScriptNum(clampedPrice, priceTruncSize);
        // Throw an error on division by zero
        if (truncatedPrice === 0) {
            throw (new Error('This configuration results in a division by zero'));
        }
        // Calculate the hedge LowTrunc payout from hedge DIV and MOD parts.
        const hedgeSatsLowTrunc = Math.min(hedgeDivLowTrunc + Math.floor(hedgeModExt / truncatedPrice), contractParameters.payoutSatsLowTrunc);
        // Calculate the long LowTrunc payout.
        const longSatsLowTrunc = contractParameters.payoutSatsLowTrunc - hedgeSatsLowTrunc;
        // Untruncate both payout values and add the dust protection.
        const hedgePayoutSats = anyhedge_util_1.dustsafe(anyhedge_util_1.untruncScriptNum(hedgeSatsLowTrunc, lowTruncSize));
        const longPayoutSats = anyhedge_util_1.dustsafe(anyhedge_util_1.untruncScriptNum(longSatsLowTrunc, lowTruncSize));
        // Calculate the total payout sats and consider the remainder to be miner fees.
        const payoutSats = hedgePayoutSats + longPayoutSats;
        const minerFeeSats = totalSats - payoutSats;
        const result = { hedgePayoutSats, longPayoutSats, payoutSats, minerFeeSats };
        // Write log entry for easier debugging.
        javascript_util_1.debug.action('Simulating contract outcomes.');
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['calculateSettlementOutcome() =>', result]);
        // Return the results of the calculation.
        return result;
    }
    /**
     * Builds a contract instance from contract parameters.
     *
     * @param contractParameters   contract parameters required to build the contract instance.
     *
     * @returns a contract instance.
     *
     * @private
     */
    async compileContract(contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['compileContract() <=', arguments]);
        // Write log entry for easier debugging.
        javascript_util_1.debug.action('Creating contract instance.');
        // Compute contract hashes from contract parameters
        const contractHashes = await this.createContractHashes(contractParameters);
        // Retrieve the correct artifact.
        const artifact = anyhedge_contracts_1.AnyHedgeArtifacts[this.contractVersion];
        // Construct correct constructor parameters.
        const parameters = [
            contractHashes.mutualRedemptionDataHash,
            contractHashes.payoutDataHash,
            contractParameters.hedgeUnitsXSatsPerBchHighTrunc,
            contractParameters.payoutSatsLowTrunc,
            contractParameters.highLowDeltaTruncatedZeroes,
            contractParameters.lowTruncatedZeroes,
            contractParameters.lowLiquidationPrice,
            contractParameters.highLiquidationPrice,
            contractParameters.earliestLiquidationHeight,
            contractParameters.maturityHeight,
        ];
        // Instantiate the contract
        const contract = new cashscript_1.Contract(artifact, parameters, this.networkProvider);
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['compileContract() =>', `${contract.name} contract with address ${contract.address}`]);
        // Pass back the contract to the caller.
        return contract;
    }
    /**
     * Creates a new contract.
     *
     * @param oraclePublicKey                  compressed public key hex string for the oracle that the contract trusts for price messages.
     * @param hedgePublicKey                   compressed public key hex string for the hedge party.
     * @param longPublicKey                    compressed public key hex string for the long party.
     * @param hedgeUnits                       amount in units that the hedge party wants to protect against volatility.
     * @param startPrice                       starting price (units/BCH) of the contract.
     * @param startBlockHeight                 blockHeight at which the contract is considered to have been started at.
     * @param earliestLiquidationModifier      minimum number of blocks from the starting height before the contract can be liquidated.
     * @param maturityModifier                 exact number of blocks from the starting height that the contract should mature at.
     * @param highLiquidationPriceMultiplier   multiplier for the startPrice determining the upper liquidation price boundary.
     * @param lowLiquidationPriceMultiplier    multiplier for the startPrice determining the lower liquidation price boundary.
     *
     * @returns the contract parameters, metadata and hashes.
     */
    async createContract(oraclePublicKey, hedgePublicKey, longPublicKey, hedgeUnits, startPrice, startBlockHeight, earliestLiquidationModifier, maturityModifier, highLiquidationPriceMultiplier, lowLiquidationPriceMultiplier) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['createContract() <=', arguments]);
        // If the hedge units are larger than MAX_HEDGE_UNITS, the contract is
        // at risk of being unredeemable with certain configurations and prices
        // which is why we disallow it in the safe create() function (#84).
        if (hedgeUnits > constants_1.MAX_HEDGE_UNITS) {
            throw (new Error(`Hedge units (${hedgeUnits}) cannot be > ${constants_1.MAX_HEDGE_UNITS}. `
                + 'The current value might result in unredeemable contracts.'));
        }
        // If the hedge units are smaller than MIN_HEDGE_UNITS, the contract runs
        // into precision errors according to Karol's math analysis (#93).
        if (hedgeUnits < constants_1.MIN_HEDGE_UNITS) {
            throw (new Error(`Hedge units (${hedgeUnits}) cannot be < ${constants_1.MIN_HEDGE_UNITS}. `
                + 'The current value might result in a high level of imprecision in calculations.'));
        }
        // If the start price is too high or too low, the contract runs into precision errors (#93).
        if ((startPrice < constants_1.MIN_START_PRICE) || (constants_1.MAX_START_PRICE < startPrice)) {
            throw (new Error(`Start price (${startPrice}) must be in the inclusive range [${constants_1.MIN_START_PRICE}, ${constants_1.MAX_START_PRICE}]. `
                + 'The current value might result in a high level of imprecision in calculations. '
                + 'An alternative is to use an oracle and related calculations that scale the price, for example JPY x 10^-3 (milli-JPY) instead of plain JPY.'));
        }
        const contractData = await this.createContractUnsafe(oraclePublicKey, hedgePublicKey, longPublicKey, hedgeUnits, startPrice, startBlockHeight, earliestLiquidationModifier, maturityModifier, highLiquidationPriceMultiplier, lowLiquidationPriceMultiplier);
        // Check that delta is not larger than 2 (should not happen because of the hedge units check) (#84)
        const truncationSizeDelta = libauth_1.hexToBin(contractData.parameters.highLowDeltaTruncatedZeroes).byteLength;
        const maxDelta = 2;
        if (truncationSizeDelta > maxDelta) {
            throw (new Error(`Truncation delta amount (${truncationSizeDelta}) cannot be > ${maxDelta}). `
                + 'The current value might result in a high level of imprecision in calculations. '
                + 'The current value may cause the contract to be unredeemable. '
                + `Reducing hedge units (${hedgeUnits}) or reducing start price (${startPrice}) will have the most impact on this value. `
                + 'An alternative is to use an oracle and related calculations that scale the price, for example JPY x 10^-3 (milli-JPY) instead of plain JPY.'));
        }
        // Disallow low truncation levels higher than 3, since the only way these can work
        // is if more than 21M BCH exist.
        const truncationSizeLow = libauth_1.hexToBin(contractData.parameters.lowTruncatedZeroes).byteLength;
        const maxTruncationSizeLow = 3;
        if (truncationSizeLow > maxTruncationSizeLow) {
            throw (new Error(`Low truncation amount (${truncationSizeLow}) cannot be > ${maxTruncationSizeLow}). `
                + 'The current value may cause the contract to be unredeemable. '
                + `Reducing hedge units (${hedgeUnits}) or increasing start price (${startPrice}) will have the most impact on this value. `
                + 'An alternative is to use an oracle and related calculations that scale the price, for example JPY x 10^-3 (milli-JPY) instead of plain JPY.'));
        }
        // If the liquidation range is too large on either side, we run into precision errors (#93)
        const { lowLiquidationPrice, highLiquidationPrice } = contractData.parameters;
        if (highLiquidationPrice > constants_1.MAX_HIGH_LIQUIDATION_PRICE) {
            throw (new Error(`High liquidation price (${highLiquidationPrice}) cannot be > ${constants_1.MAX_HIGH_LIQUIDATION_PRICE}. `
                + 'The current value might result in a high level of imprecision in calculations. '
                + `Reducing start price ${startPrice} might fix this.`
                + `Reducing high liquidation price multiplier ${highLiquidationPriceMultiplier} might fix this.`
                + 'An alternative is to use an oracle and related calculations that scale the price, for example JPY x 10^+3 (mega-JPY) instead of plain JPY.'));
        }
        if (lowLiquidationPrice < constants_1.MIN_LOW_LIQUIDATION_PRICE) {
            throw (new Error(`Low liquidation price (${lowLiquidationPrice}) cannot be < ${constants_1.MIN_LOW_LIQUIDATION_PRICE}. `
                + 'The current value might result in a high level of imprecision in calculations. '
                + `Increasing start price ${startPrice} might fix this.`
                + `Increasing low liquidation price multiplier ${lowLiquidationPriceMultiplier} might fix this.`
                + 'An alternative is to use an oracle and related calculations that scale the price, for example JPY x 10^-3 (milli-JPY) instead of plain JPY.'));
        }
        // If the liquidation range is inverted or equal to start price, we get immediate liquidation
        if (highLiquidationPrice <= startPrice) {
            throw (new Error(`High liquidation price (${highLiquidationPrice}) cannot be <= start price (${startPrice}). `
                + 'The current value will cause immediate liquidation. '
                + 'Making high liquidation price multiplier sufficiently > 1 will fix this.'));
        }
        if (lowLiquidationPrice >= startPrice) {
            throw (new Error(`Low liquidation price (${lowLiquidationPrice}) cannot be >= start price (${startPrice}). `
                + 'The current value will cause immediate liquidation. '
                + 'Making low liquidation price multiplier sufficiently < 1 will fix this.'));
        }
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['createContract() =>', contractData]);
        return contractData;
    }
    /**
     * Creates a new contract without extra safety checks.
     *
     * @param oraclePublicKey                  compressed public key hex string for the oracle that the contract trusts for price messages.
     * @param hedgePublicKey                   compressed public key hex string for the hedge party.
     * @param longPublicKey                    compressed public key hex string for the long party.
     * @param hedgeUnits                       amount in units that the hedge party wants to protect against volatility.
     * @param startPrice                       starting price (units/BCH) of the contract.
     * @param startBlockHeight                 blockHeight at which the contract is considered to have been started at.
     * @param earliestLiquidationModifier      minimum number of blocks from the starting height before the contract can be liquidated.
     * @param maturityModifier                 exact number of blocks from the starting height that the contract should mature at.
     * @param highLiquidationPriceMultiplier   multiplier for the startPrice determining the upper liquidation price boundary.
     * @param lowLiquidationPriceMultiplier    multiplier for the startPrice determining the lower liquidation price boundary.
     *
     * @returns the contract parameters, metadata and hashes.
     *
     * @private
     */
    async createContractUnsafe(oraclePublicKey, hedgePublicKey, longPublicKey, hedgeUnits, startPrice, startBlockHeight, earliestLiquidationModifier, maturityModifier, highLiquidationPriceMultiplier, lowLiquidationPriceMultiplier) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['createContractUnsafe() <=', arguments]);
        // Write log entry for easier debugging.
        javascript_util_1.debug.action('Preparing a new contract.');
        // There are 4 root numbers from which other values are derived.
        // 1. Low liquidation price.
        // 2. High liquidation price
        // 3. Hedge input satoshis.
        // 4. Composite value representing Hedge's unit value.
        // 1. Low liquidation price: the low price that triggers liquidation.
        // The value is rounded to achieve a result as close as possible to intent.
        // More strict terms such as floor and ceiling are imposed on derivative values.
        const lowLiquidationPrice = Math.round(lowLiquidationPriceMultiplier * startPrice);
        // Low liquidation price <= zero may cause the contract to be unredeemable
        if (lowLiquidationPrice <= 0) {
            throw (new Error(`Low liquidation price (${lowLiquidationPrice}) cannot be <= 0. `
                + 'The current value may cause the contract to be unredeemable. '
                + 'Making low liquidation price multiplier sufficiently > 0 will fix this.'));
        }
        // 2. High liquidation price: the high price that triggers liquidation.
        // The value is rounded to achieve a result as close as possible to intent.
        const highLiquidationPrice = Math.round(highLiquidationPriceMultiplier * startPrice);
        // High liquidation price greater than SCRIPT_INT_MAX causes the contract to be unredeemable.
        if (highLiquidationPrice > constants_1.SCRIPT_INT_MAX) {
            throw (new Error(`High liquidation price (${highLiquidationPrice}) cannot be > maximum script integer (${constants_1.SCRIPT_INT_MAX}). `
                + 'The current value may cause the contract to be unredeemable. '
                + 'Making high liquidation price multiplier sufficiently closer to 1 will fix this.'));
        }
        // High liquidation has a constraint regarding payout in extreme cases where only a small amount of satoshis
        // are needed to payout Hedge. The most extreme case is 1 satoshi. To retain a reasonable step-precision
        // between values, and also to match the reality of dust requirements, we choose the dust limit as the
        // minimum hedge payout. The result of the calculation is that for a requested set of parameters, there is
        // a minimum Hedge units that will satisfy the extreme end of high liquidation conditions.
        // One method of calculation, focused on the input modifier is as follows:
        //   hedgePayoutSats @ highLiquidationPrice >= MIN_HEDGE_PAYOUT_SATS
        //   hedgeUnits * SATS_PER_BCH / highLiquidationPrice >= MIN_HEDGE_PAYOUT_SATS
        //   hedgeUnits >= MIN_HEDGE_PAYOUT_SATS * highLiquidationPrice / SATS_PER_BCH
        //     and for MIN_HEDGE_PAYOUT_SATS = DUST:
        //   hedgeUnits >= DUST * highLiquidationPrice / SATS_PER_BCH
        const dynamicMinHedgeUnits = (constants_1.DUST_LIMIT * highLiquidationPrice) / constants_1.SATS_PER_BCH;
        if (hedgeUnits < dynamicMinHedgeUnits) {
            // Recalculating around the price multiplier to provide an actionable error message:
            //   highLiquidationPrice <= hedgeUnits * SATS_PER_BCH / DUST
            //     substituting with the high liquidation modifier:
            //   highLiquidationPriceMultiplier * startPrice <= hedgeUnits * SATS_PER_BCH / DUST
            //   highLiquidationPriceMultiplier <= (hedgeUnits * SATS_PER_BCH) / (DUST * startPrice)
            const maxHighLiquidationPriceMultiplier = (hedgeUnits * constants_1.SATS_PER_BCH) / (constants_1.DUST_LIMIT * startPrice);
            throw (new Error(`Hedge units (${hedgeUnits}) cannot be < ${dynamicMinHedgeUnits} and high liquidation price multiplier (${highLiquidationPriceMultiplier}) cannot be > ${maxHighLiquidationPriceMultiplier}. `
                + 'The current values may cause the contract to be unredeemable at high prices.'));
        }
        // 3. Hedge input satoshis.
        // Hedge: Satoshis equal to the hedged unit value at the start price.
        //        The value is rounded to achieve a result as close as possible to intent.
        //        More strict terms such as floor and ceiling are imposed on derivative values.
        // For readability, we also derive the naive values of total and long satoshis which will be adjusted later.
        // Total: Satoshis equal to the hedged unit value at the low liquidation price. I.e. long gets about zero.
        //        The value is ceiling to ensure that the result is *at least* enough to cover hedge value, never less.
        // Long:  Satoshis equal to difference between the total satoshis and hedge satoshis.
        //        The value is recorded for metadata purposes only.
        const hedgeInputSats = Math.round((hedgeUnits * constants_1.SATS_PER_BCH) / startPrice);
        const naiveTotalInputSats = Math.ceil((hedgeUnits * constants_1.SATS_PER_BCH) / lowLiquidationPrice);
        const naiveLongInputSats = naiveTotalInputSats - hedgeInputSats;
        // Regarding the truncation operations in the remaining code:
        // Due to current limitations of Bitcoin Cash script, calculations can only be performed with 32-bit numbers.
        // Combined with other current limitations, 32 bits is not enough to handle meaningful amounts of value
        // in AnyHedge. Therefore some mathematical tricks are required to get around the limitation. The trick that
        // AnyHedge currently uses is to truncate bytes from large values and remember how much has been removed
        // so it can be added back later.
        // Specifically, AnyHedge has two levels of truncation required to handle two specific large numbers:
        //   HighTrunc) Required for the contract to do calculations with the composite number calculated below in 4.
        //   LowTrunc)  Required for the contract to do calculations with the payoutSats calculated below.
        // 4. Composite number representing Hedge's unit value.
        // The number is calculated as hedge units * 1e8 sats/bch.
        // This overcomes the current limits of BCH scripting where we have division but no multiplication.
        // The value divided by the price in BCH directly yields satoshis for hedge value at said price.
        // The value is naive because it may require truncation for storage and calculations in the contract.
        // The value is rounded to achieve a result as close as possible to intent.
        // More strict terms such as floor and ceiling are imposed on derivative values.
        const naiveHedgeUnitsXSatsPerBch = Math.round(hedgeUnits * constants_1.SATS_PER_BCH);
        // Calculate the required amount of truncation and record the truncated value.
        const truncationSizeHigh = anyhedge_util_1.calculateRequiredScriptNumTruncation(naiveHedgeUnitsXSatsPerBch);
        const hedgeUnitsXSatsPerBchHighTrunc = anyhedge_util_1.truncScriptNum(naiveHedgeUnitsXSatsPerBch, truncationSizeHigh);
        // After the 4 root values, we derive the remaining money-related numbers for the contract and metadata.
        // Total sats are truncated as described above if necessary for the final contract value.
        // Total input sats are renamed to payout sats to align with contract parameter names
        const truncationSizeLow = anyhedge_util_1.calculateRequiredScriptNumTruncation(naiveTotalInputSats);
        const payoutSatsLowTrunc = anyhedge_util_1.truncScriptNum(naiveTotalInputSats, truncationSizeLow);
        // The difference between the high and low truncation is needed in the contract so we calculate it here.
        const truncationSizeDelta = truncationSizeHigh - truncationSizeLow;
        // If the truncation delta is negative, the contract is always unredeemable
        if (truncationSizeDelta < 0) {
            throw (new Error(`Truncation delta amount (${truncationSizeDelta}) cannot be < 0). `
                + 'The current value may cause the contract to be unredeemable. '
                + 'There may be a fundamental problem with the input parameters.'));
        }
        // Total sats, long input sats, long input units are calculated only for metadata
        const totalInputSats = anyhedge_util_1.untruncScriptNum(payoutSatsLowTrunc, truncationSizeLow);
        const longInputSats = totalInputSats - hedgeInputSats;
        const longInputUnits = ((longInputSats / constants_1.SATS_PER_BCH) * startPrice);
        // In addition to money-related numbers, we derive time-related numbers for the contract and metadata.
        // Earliest liquidation provides a grace period in which the contract cannot settle.
        const earliestLiquidationHeight = startBlockHeight + earliestLiquidationModifier;
        // Maturity provides a deadline after which anyone can settle the contract
        // using the maturity block's first price message.
        const maturityHeight = startBlockHeight + maturityModifier;
        // We also package keys and other fixed values for the contract and metadata.
        // Create hedge and long lock scripts.
        const hedgeLockScript = await bitcoincash_util_1.buildLockScriptP2PKH(hedgePublicKey);
        const longLockScript = await bitcoincash_util_1.buildLockScriptP2PKH(longPublicKey);
        // Create hex strings with zeroes matching the low and delta truncation sizes.
        const highLowDeltaTruncatedZeroes = '00'.repeat(truncationSizeDelta);
        const lowTruncatedZeroes = '00'.repeat(truncationSizeLow);
        // Assemble the contract parameters.
        const contractParameters = {
            lowLiquidationPrice,
            highLiquidationPrice,
            earliestLiquidationHeight,
            maturityHeight,
            oraclePubk: oraclePublicKey,
            hedgeLockScript,
            longLockScript,
            hedgeMutualRedeemPubk: hedgePublicKey,
            longMutualRedeemPubk: longPublicKey,
            lowTruncatedZeroes,
            highLowDeltaTruncatedZeroes,
            hedgeUnitsXSatsPerBchHighTrunc,
            payoutSatsLowTrunc,
        };
        // Build the corresponding contract
        const contract = await this.compileContract(contractParameters);
        // Store the total dust protection cost.
        const dustCost = 2 * constants_1.DUST_LIMIT;
        // Estimate the miner cost for the payout transaction size (paying 1.0 sats/b).
        const minerCost = await anyhedge_util_1.estimatePayoutTransactionFee(contract, contractParameters, 1.0);
        // Assemble the contract metadata.
        const contractMetadata = {
            oraclePublicKey,
            hedgePublicKey,
            longPublicKey,
            startBlockHeight,
            maturityModifier,
            earliestLiquidationModifier,
            highLiquidationPriceMultiplier,
            lowLiquidationPriceMultiplier,
            startPrice,
            hedgeUnits,
            longInputUnits,
            totalInputSats,
            hedgeInputSats,
            longInputSats,
            dustCost,
            minerCost,
        };
        // Assemble the final contract data.
        const contractData = {
            version: this.contractVersion,
            address: contract.address,
            parameters: contractParameters,
            metadata: contractMetadata,
        };
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['createContractUnsafe() =>', contractData]);
        // Pass back the contract data to the caller.
        return contractData;
    }
    /**
     * Computes contract hashes from contract parameters
     *
     * @param contractParameters parameters of the contract
     *
     * @returns the hashes corresponding to the passed parameters
     */
    async createContractHashes(contractParameters) {
        // Output function call arguments for easier collection of test data.
        // eslint-disable-next-line prefer-rest-params
        javascript_util_1.debug.params(['createContractHashes() <=', arguments]);
        // Assemble the mutual redemption data.
        const { hedgeMutualRedeemPubk, longMutualRedeemPubk } = contractParameters;
        const mutualRedemptionData = [hedgeMutualRedeemPubk, longMutualRedeemPubk];
        // Assemble the payout data.
        const { hedgeLockScript, longLockScript, oraclePubk } = contractParameters;
        const payoutData = [hedgeLockScript, longLockScript, oraclePubk];
        // Calculate the hashes required to create a contract instance.
        const mutualRedemptionDataHash = libauth_1.binToHex(await bitcoincash_util_1.hash160(libauth_1.hexToBin(mutualRedemptionData.join(''))));
        const payoutDataHash = libauth_1.binToHex(await bitcoincash_util_1.hash160(libauth_1.hexToBin(payoutData.join(''))));
        // Assemble ContractHashes interface.
        const contractHashes = { mutualRedemptionDataHash, payoutDataHash };
        // Output function result for easier collection of test data.
        javascript_util_1.debug.result(['createContractHashes() =>', contractHashes]);
        return contractHashes;
    }
    /**
     * Parse a settlement transaction to extract as much data as possible, ending up with partial
     * ContractParameters, ContractSettlement and ContractFunding objects, depending on what data
     * could be retrieved.
     *
     * @param settlementTransactionHex   hex string for the settlement transaction
     *
     * @throws {Error} when the passed transaction hex cannot be parsed by Libauth.
     * @throws {SettlementParseError} if the transaction does not have exactly one input.
     * @throws {SettlementParseError} if the transaction does not have exactly two outputs.
     * @throws {SettlementParseError} if the unlocking script does not include exactly 6 or 10 input parameters.
     * @throws {SettlementParseError} if the redeem script does not match expectations for an AnyHedge contract.
     * @returns partial ContractParameters, ContractSettlement, and ContractFunding objects. See {@link https://gitlab.com/GeneralProtocols/anyhedge/library/-/blob/development/examples/parse-settlement-transaction.js|examples/parse-settlement-transaction.js} to inspect the data that this function returns.
     */
    async parseSettlementTransaction(settlementTransactionHex) {
        return anyhedge_util_1.parseSettlementTransaction(settlementTransactionHex);
    }
}
exports.AnyHedgeManager = AnyHedgeManager;
//# sourceMappingURL=anyhedge.js.map