"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompositeClient = void 0;
const tendermint_rpc_1 = require("@cosmjs/tendermint-rpc");
const order_1 = require("@ritbit/v4-proto/src/codegen/ritbit/clob/order");
const ethers_1 = require("ethers");
const long_1 = __importDefault(require("long"));
const protobufjs_1 = __importDefault(require("protobufjs"));
const helpers_1 = require("../lib/helpers");
const validation_1 = require("../lib/validation");
const types_1 = require("../types");
const constants_1 = require("./constants");
const chain_helpers_1 = require("./helpers/chain-helpers");
const indexer_client_1 = require("./indexer-client");
const errors_1 = require("./lib/errors");
const registry_1 = require("./lib/registry");
const subaccount_1 = require("./subaccount");
const validator_client_1 = require("./validator-client");
// Required for encoding and decoding queries that are of type Long.
// Must be done once but since the individal modules should be usable
// - must be set in each module that encounters encoding/decoding Longs.
// Reference: https://github.com/protobufjs/protobuf.js/issues/921
protobufjs_1.default.util.Long = long_1.default;
protobufjs_1.default.configure();
class CompositeClient {
    static async connect(network) {
        const client = new CompositeClient(network);
        await client.initialize();
        return client;
    }
    constructor(network, apiTimeout) {
        this.gasDenom = constants_1.SelectedGasDenom.USDC;
        this.network = network;
        this._indexerClient = new indexer_client_1.IndexerClient(network.indexerConfig, apiTimeout);
    }
    async initialize() {
        this._validatorClient = await validator_client_1.ValidatorClient.connect(this.network.validatorConfig);
    }
    get indexerClient() {
        /**
         * Get the validator client
         */
        return this._indexerClient;
    }
    get validatorClient() {
        /**
         * Get the validator client
         */
        return this._validatorClient;
    }
    get selectedGasDenom() {
        if (!this._validatorClient)
            return undefined;
        return this._validatorClient.selectedGasDenom;
    }
    setSelectedGasDenom(gasDenom) {
        if (!this._validatorClient)
            throw new Error('Validator client not initialized');
        this._validatorClient.setSelectedGasDenom(gasDenom);
    }
    async populateAccountNumberCache(address) {
        if (!this._validatorClient)
            throw new Error('Validator client not initialized');
        return this._validatorClient.populateAccountNumberCache(address);
    }
    /**
     * @description Sign a list of messages with a wallet.
     * the calling function is responsible for creating the messages.
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The Signature.
     */
    async sign(subaccount, messaging, zeroFee, gasPrice, memo, account) {
        return this.validatorClient.post.sign(subaccount, messaging, zeroFee, gasPrice, memo, account);
    }
    /**
     * @description Send a list of messages with a wallet.
     * the calling function is responsible for creating the messages.
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The Transaction Hash.
     */
    async send(subaccount, messaging, zeroFee, gasPrice, memo, broadcastMode, account, gasAdjustment, additionalFeeCoins) {
        return this.validatorClient.post.send(subaccount, messaging, zeroFee, gasPrice, memo, broadcastMode, account, gasAdjustment, additionalFeeCoins);
    }
    /**
     * @description Send a signed transaction.
     *
     * @param signedTransaction The signed transaction to send.
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The Transaction Hash.
     */
    async sendSignedTransaction(signedTransaction) {
        return this.validatorClient.post.sendSignedTransaction(signedTransaction);
    }
    /**
     * @description Simulate a list of messages with a wallet.
     * the calling function is responsible for creating the messages.
     *
     * To send multiple messages with gas estimate:
     * 1. Client is responsible for creating the messages.
     * 2. Call simulate() to get the gas estimate.
     * 3. Call send() to send the messages.
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The gas estimate.
     */
    async simulate(subaccount, messaging, gasPrice, memo, account) {
        return this.validatorClient.post.simulate(subaccount, messaging, gasPrice, memo, account);
    }
    /**
     * @description Calculate the goodTilBlock value for a SHORT_TERM order
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The goodTilBlock value
     */
    async calculateGoodTilBlock(orderFlags, currentHeight, goodTilBlock) {
        if (orderFlags === types_1.OrderFlags.SHORT_TERM) {
            if (goodTilBlock !== undefined && goodTilBlock !== 0 && goodTilBlock !== null) {
                return Promise.resolve(goodTilBlock);
            }
            else {
                const height = currentHeight !== null && currentHeight !== void 0 ? currentHeight : (await this.validatorClient.get.latestBlockHeight());
                return height + constants_1.SHORT_BLOCK_FORWARD;
            }
        }
        else {
            return Promise.resolve(0);
        }
    }
    /**
     * @description Validate the goodTilBlock value for a SHORT_TERM order
     *
     * @param goodTilBlock Number of blocks from the current block height the order will
     * be valid for.
     *
     * @throws UserError if the goodTilBlock value is not valid given latest block height and
     * SHORT_BLOCK_WINDOW.
     */
    async validateGoodTilBlock(goodTilBlock) {
        const height = await this.validatorClient.get.latestBlockHeight();
        const nextValidBlockHeight = height + 1;
        const lowerBound = nextValidBlockHeight;
        const upperBound = nextValidBlockHeight + constants_1.SHORT_BLOCK_WINDOW;
        if (goodTilBlock < lowerBound || goodTilBlock > upperBound) {
            throw new errors_1.UserError(`Invalid Short-Term order GoodTilBlock.
        Should be greater-than-or-equal-to ${lowerBound} and less-than-or-equal-to ${upperBound}.
        Provided good til block: ${goodTilBlock}`);
        }
    }
    /**
     * @description Calculate the goodTilBlockTime value for a LONG_TERM order
     * the calling function is responsible for creating the messages.
     *
     * @param goodTilTimeInSeconds The goodTilTimeInSeconds of the order to place.
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The goodTilBlockTime value
     */
    calculateGoodTilBlockTime(goodTilTimeInSeconds) {
        const now = new Date();
        const millisecondsPerSecond = 1000;
        const interval = goodTilTimeInSeconds * millisecondsPerSecond;
        const future = new Date(now.valueOf() + interval);
        return Math.round(future.getTime() / 1000);
    }
    /**
     * @description Place a short term order with human readable input.
     *
     * Use human readable form of input, including price and size
     * The quantum and subticks are calculated and submitted
     *
     * @param subaccount The subaccount to place the order under
     * @param marketId The market to place the order on
     * @param side The side of the order to place
     * @param price The price of the order to place
     * @param size The size of the order to place
     * @param clientId The client id of the order to place
     * @param timeInForce The time in force of the order to place
     * @param goodTilBlock The goodTilBlock of the order to place
     * @param reduceOnly The reduceOnly of the order to place
     *
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The transaction hash.
     */
    async placeShortTermOrder(subaccount, marketId, side, price, size, clientId, goodTilBlock, timeInForce, reduceOnly, memo) {
        const msgs = new Promise((resolve, reject) => {
            const msg = this.placeShortTermOrderMessage(subaccount, marketId, side, price, size, clientId, goodTilBlock, timeInForce, reduceOnly);
            msg
                .then((it) => {
                resolve([it]);
            })
                .catch((err) => {
                console.log(err);
                reject(err);
            });
        });
        const account = this.validatorClient.post.account(subaccount.address, undefined);
        return this.send(subaccount, () => msgs, true, undefined, memo, undefined, () => account);
    }
    /**
     * @description Place an order with human readable input.
     *
     * Only MARKET and LIMIT types are supported right now
     * Use human readable form of input, including price and size
     * The quantum and subticks are calculated and submitted
     *
     * @param subaccount The subaccount to place the order on.
     * @param marketId The market to place the order on.
     * @param type The type of order to place.
     * @param side The side of the order to place.
     * @param price The price of the order to place.
     * @param size The size of the order to place.
     * @param clientId The client id of the order to place.
     * @param timeInForce The time in force of the order to place.
     * @param goodTilTimeInSeconds The goodTilTimeInSeconds of the order to place.
     * @param execution The execution of the order to place.
     * @param postOnly The postOnly of the order to place.
     * @param reduceOnly The reduceOnly of the order to place.
     * @param triggerPrice The trigger price of conditional orders.
     * @param marketInfo optional market information for calculating quantums and subticks.
     *        This can be constructed from Indexer API. If set to null, additional round
     *        trip to Indexer API will be made.
     * @param currentHeight Current block height. This can be obtained from ValidatorClient.
     *        If set to null, additional round trip to ValidatorClient will be made.
     *
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The transaction hash.
     */
    async placeOrder(subaccount, marketId, type, side, price, size, clientId, timeInForce, goodTilTimeInSeconds, execution, postOnly, reduceOnly, triggerPrice, marketInfo, currentHeight, goodTilBlock, memo, broadcastMode) {
        const msgs = new Promise((resolve) => {
            const msg = this.placeOrderMessage(subaccount, marketId, type, side, price, size, clientId, timeInForce, goodTilTimeInSeconds, execution, postOnly, reduceOnly, triggerPrice, marketInfo, currentHeight, goodTilBlock);
            msg
                .then((it) => resolve([it]))
                .catch((err) => {
                throw err;
            });
        });
        const orderFlags = (0, chain_helpers_1.calculateOrderFlags)(type, timeInForce);
        const account = this.validatorClient.post.account(subaccount.address, orderFlags);
        return this.send(subaccount, () => msgs, true, undefined, memo, broadcastMode, () => account);
    }
    /**
     * @description Calculate and create the place order message
     *
     * Only MARKET and LIMIT types are supported right now
     * Use human readable form of input, including price and size
     * The quantum and subticks are calculated and submitted
     *
     * @param subaccount The subaccount to place the order under
     * @param marketId The market to place the order on
     * @param type The type of order to place
     * @param side The side of the order to place
     * @param price The price of the order to place
     * @param size The size of the order to place
     * @param clientId The client id of the order to place
     * @param timeInForce The time in force of the order to place
     * @param goodTilTimeInSeconds The goodTilTimeInSeconds of the order to place
     * @param execution The execution of the order to place
     * @param postOnly The postOnly of the order to place
     * @param reduceOnly The reduceOnly of the order to place
     *
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The message to be passed into the protocol
     */
    async placeOrderMessage(subaccount, marketId, type, side, price, size, clientId, timeInForce, goodTilTimeInSeconds, execution, postOnly, reduceOnly, triggerPrice, marketInfo, currentHeight, goodTilBlock) {
        const orderFlags = (0, chain_helpers_1.calculateOrderFlags)(type, timeInForce);
        const result = await Promise.all([
            this.calculateGoodTilBlock(orderFlags, currentHeight, goodTilBlock),
            this.retrieveMarketInfo(marketId, marketInfo),
        ]);
        const desiredGoodTilBlock = result[0];
        const clobPairId = result[1].clobPairId;
        const atomicResolution = result[1].atomicResolution;
        const stepBaseQuantums = result[1].stepBaseQuantums;
        const quantumConversionExponent = result[1].quantumConversionExponent;
        const subticksPerTick = result[1].subticksPerTick;
        const orderSide = (0, chain_helpers_1.calculateSide)(side);
        const quantums = (0, chain_helpers_1.calculateQuantums)(size, atomicResolution, stepBaseQuantums);
        const subticks = (0, chain_helpers_1.calculateSubticks)(price, atomicResolution, quantumConversionExponent, subticksPerTick);
        const orderTimeInForce = (0, chain_helpers_1.calculateTimeInForce)(type, timeInForce, execution, postOnly);
        let goodTilBlockTime = 0;
        if (orderFlags === types_1.OrderFlags.LONG_TERM || orderFlags === types_1.OrderFlags.CONDITIONAL) {
            if (goodTilTimeInSeconds == null) {
                throw new Error('goodTilTimeInSeconds must be set for LONG_TERM or CONDITIONAL order');
            }
            else {
                goodTilBlockTime = this.calculateGoodTilBlockTime(goodTilTimeInSeconds);
            }
        }
        const clientMetadata = (0, chain_helpers_1.calculateClientMetadata)(type);
        const conditionalType = (0, chain_helpers_1.calculateConditionType)(type);
        const conditionalOrderTriggerSubticks = (0, chain_helpers_1.calculateConditionalOrderTriggerSubticks)(type, atomicResolution, quantumConversionExponent, subticksPerTick, triggerPrice);
        return this.validatorClient.post.composer.composeMsgPlaceOrder(subaccount.address, subaccount.subaccountNumber, clientId, clobPairId, orderFlags, desiredGoodTilBlock, goodTilBlockTime, orderSide, quantums, subticks, orderTimeInForce, reduceOnly !== null && reduceOnly !== void 0 ? reduceOnly : false, clientMetadata, conditionalType, conditionalOrderTriggerSubticks);
    }
    async retrieveMarketInfo(marketId, marketInfo) {
        if (marketInfo) {
            return Promise.resolve(marketInfo);
        }
        else {
            const marketsResponse = await this.indexerClient.markets.getPerpetualMarkets(marketId);
            const market = marketsResponse.markets[marketId];
            const clobPairId = market.clobPairId;
            const atomicResolution = market.atomicResolution;
            const stepBaseQuantums = market.stepBaseQuantums;
            const quantumConversionExponent = market.quantumConversionExponent;
            const subticksPerTick = market.subticksPerTick;
            return {
                clobPairId,
                atomicResolution,
                stepBaseQuantums,
                quantumConversionExponent,
                subticksPerTick,
            };
        }
    }
    /**
     * @description Calculate and create the short term place order message
     *
     * Use human readable form of input, including price and size
     * The quantum and subticks are calculated and submitted
     *
     * @param subaccount The subaccount to place the order under
     * @param marketId The market to place the order on
     * @param side The side of the order to place
     * @param price The price of the order to place
     * @param size The size of the order to place
     * @param clientId The client id of the order to place
     * @param timeInForce The time in force of the order to place
     * @param goodTilBlock The goodTilBlock of the order to place
     * @param reduceOnly The reduceOnly of the order to place
     *
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The message to be passed into the protocol
     */
    async placeShortTermOrderMessage(subaccount, marketId, side, price, size, clientId, goodTilBlock, timeInForce, reduceOnly) {
        await this.validateGoodTilBlock(goodTilBlock);
        const marketsResponse = await this.indexerClient.markets.getPerpetualMarkets(marketId);
        const market = marketsResponse.markets[marketId];
        const clobPairId = market.clobPairId;
        const atomicResolution = market.atomicResolution;
        const stepBaseQuantums = market.stepBaseQuantums;
        const quantumConversionExponent = market.quantumConversionExponent;
        const subticksPerTick = market.subticksPerTick;
        const orderSide = (0, chain_helpers_1.calculateSide)(side);
        const quantums = (0, chain_helpers_1.calculateQuantums)(size, atomicResolution, stepBaseQuantums);
        const subticks = (0, chain_helpers_1.calculateSubticks)(price, atomicResolution, quantumConversionExponent, subticksPerTick);
        const orderFlags = types_1.OrderFlags.SHORT_TERM;
        return this.validatorClient.post.composer.composeMsgPlaceOrder(subaccount.address, subaccount.subaccountNumber, clientId, clobPairId, orderFlags, goodTilBlock, 0, // Short term orders use goodTilBlock.
        orderSide, quantums, subticks, timeInForce, reduceOnly, 0, // Client metadata is 0 for short term orders.
        order_1.Order_ConditionType.CONDITION_TYPE_UNSPECIFIED, // Short term orders cannot be conditional.
        long_1.default.fromInt(0));
    }
    /**
     * @description Cancel an order with order information from web socket or REST.
     *
     * @param subaccount The subaccount to cancel the order from
     * @param clientId The client id of the order to cancel
     * @param orderFlags The order flags of the order to cancel
     * @param clobPairId The clob pair id of the order to cancel
     * @param goodTilBlock The goodTilBlock of the order to cancel
     * @param goodTilBlockTime The goodTilBlockTime of the order to cancel
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The transaction hash.
     */
    async cancelRawOrder(subaccount, clientId, orderFlags, clobPairId, goodTilBlock, goodTilBlockTime) {
        return this.validatorClient.post.cancelOrder(subaccount, clientId, orderFlags, clobPairId, goodTilBlock, goodTilBlockTime);
    }
    /**
     * @description Cancel an order with human readable input.
     *
     * @param subaccount The subaccount to cancel the order from
     * @param clientId The client id of the order to cancel
     * @param orderFlags The order flags of the order to cancel
     * @param marketId The market to cancel the order on
     * @param goodTilBlock The goodTilBlock of the order to cancel
     * @param goodTilBlockTime The goodTilBlockTime of the order to cancel
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The transaction hash.
     */
    async cancelOrder(subaccount, clientId, orderFlags, marketId, goodTilBlock, goodTilTimeInSeconds) {
        const marketsResponse = await this.indexerClient.markets.getPerpetualMarkets(marketId);
        const market = marketsResponse.markets[marketId];
        const clobPairId = market.clobPairId;
        if (!(0, validation_1.verifyOrderFlags)(orderFlags)) {
            throw new Error(`Invalid order flags: ${orderFlags}`);
        }
        let goodTilBlockTime;
        if ((0, validation_1.isStatefulOrder)(orderFlags)) {
            if (goodTilTimeInSeconds === undefined || goodTilTimeInSeconds === 0) {
                throw new Error('goodTilTimeInSeconds must be set for LONG_TERM or CONDITIONAL order');
            }
            if (goodTilBlock !== 0) {
                throw new Error('goodTilBlock should be zero since LONG_TERM or CONDITIONAL orders ' +
                    'use goodTilTimeInSeconds instead of goodTilBlock.');
            }
            goodTilBlockTime = this.calculateGoodTilBlockTime(goodTilTimeInSeconds);
        }
        else {
            if (goodTilBlock === undefined || goodTilBlock === 0) {
                throw new Error('goodTilBlock must be non-zero for SHORT_TERM orders');
            }
            if (goodTilTimeInSeconds !== undefined && goodTilTimeInSeconds !== 0) {
                throw new Error('goodTilTimeInSeconds should be zero since SHORT_TERM orders use goodTilBlock instead of goodTilTimeInSeconds.');
            }
        }
        return this.validatorClient.post.cancelOrder(subaccount, clientId, orderFlags, clobPairId, goodTilBlock, goodTilBlockTime);
    }
    /**
     * @description Batch cancel short term orders using marketId to clobPairId translation.
     *
     * @param subaccount The subaccount to cancel the order from
     * @param shortTermOrders The list of short term order batches to cancel with marketId
     * @param goodTilBlock The goodTilBlock of the order to cancel
     * @returns The transaction hash.
     */
    async batchCancelShortTermOrdersWithMarketId(subaccount, shortTermOrders, goodTilBlock, broadcastMode) {
        const orderBatches = await Promise.all(shortTermOrders.map(async ({ marketId, clobPairId, clientIds }) => ({
            clobPairId: (clobPairId !== null && clobPairId !== void 0 ? clobPairId : (await this.indexerClient.markets.getPerpetualMarkets(marketId)).markets[marketId]).clobPairId,
            clientIds,
        })));
        return this.validatorClient.post.batchCancelShortTermOrders(subaccount, orderBatches, goodTilBlock, broadcastMode);
    }
    /**
     * @description Batch cancel short term orders using clobPairId.
     *
     * @param subaccount The subaccount to cancel the order from
     * @param shortTermOrders The list of short term order batches to cancel with clobPairId
     * @param goodTilBlock The goodTilBlock of the order to cancel
     * @returns The transaction hash.
     */
    async batchCancelShortTermOrdersWithClobPairId(subaccount, shortTermOrders, goodTilBlock, broadcastMode) {
        return this.validatorClient.post.batchCancelShortTermOrders(subaccount, shortTermOrders, goodTilBlock, broadcastMode);
    }
    /**
     * @description Transfer from a subaccount to another subaccount
     *
     * @param subaccount The subaccount to transfer from
     * @param recipientAddress The recipient address
     * @param recipientSubaccountNumber The recipient subaccount number
     * @param amount The amount to transfer
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The transaction hash.
     */
    async transferToSubaccount(subaccount, recipientAddress, recipientSubaccountNumber, amount, memo, broadcastMode) {
        const msgs = new Promise((resolve) => {
            const msg = this.transferToSubaccountMessage(subaccount, recipientAddress, recipientSubaccountNumber, amount);
            resolve([msg]);
        });
        return this.send(subaccount, () => msgs, false, undefined, memo, broadcastMode !== null && broadcastMode !== void 0 ? broadcastMode : tendermint_rpc_1.Method.BroadcastTxCommit);
    }
    /**
     * @description Create message to transfer from a subaccount to another subaccount
     *
     * @param subaccount The subaccount to transfer from
     * @param recipientAddress The recipient address
     * @param recipientSubaccountNumber The recipient subaccount number
     * @param amount The amount to transfer
     *
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The message
     */
    transferToSubaccountMessage(subaccount, recipientAddress, recipientSubaccountNumber, amount) {
        const validatorClient = this._validatorClient;
        if (validatorClient === undefined) {
            throw new Error('validatorClient not set');
        }
        const quantums = (0, ethers_1.parseUnits)(amount, validatorClient.config.denoms.USDC_DECIMALS);
        if (quantums > BigInt(long_1.default.MAX_VALUE.toString())) {
            throw new Error('amount to large');
        }
        if (quantums < 0) {
            throw new Error('amount must be positive');
        }
        return this.validatorClient.post.composer.composeMsgTransfer(subaccount.address, subaccount.subaccountNumber, recipientAddress, recipientSubaccountNumber, 0, long_1.default.fromString(quantums.toString()));
    }
    /**
     * @description Deposit from wallet to subaccount
     *
     * @param subaccount The subaccount to deposit to
     * @param amount The amount to deposit
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The transaction hash.
     */
    async depositToSubaccount(subaccount, amount, memo) {
        const msgs = new Promise((resolve) => {
            const msg = this.depositToSubaccountMessage(subaccount, amount);
            resolve([msg]);
        });
        return this.validatorClient.post.send(subaccount, () => msgs, false, undefined, memo);
    }
    /**
     * @description Create message to deposit from wallet to subaccount
     *
     * @param subaccount The subaccount to deposit to
     * @param amount The amount to deposit
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The message
     */
    depositToSubaccountMessage(subaccount, amount) {
        const validatorClient = this._validatorClient;
        if (validatorClient === undefined) {
            throw new Error('validatorClient not set');
        }
        const quantums = (0, ethers_1.parseUnits)(amount, validatorClient.config.denoms.USDC_DECIMALS);
        if (quantums > BigInt(long_1.default.MAX_VALUE.toString())) {
            throw new Error('amount to large');
        }
        if (quantums < 0) {
            throw new Error('amount must be positive');
        }
        return this.validatorClient.post.composer.composeMsgDepositToSubaccount(subaccount.address, subaccount.subaccountNumber, 0, long_1.default.fromString(quantums.toString()));
    }
    /**
     * @description Withdraw from subaccount to wallet
     *
     * @param subaccount The subaccount to withdraw from
     * @param amount The amount to withdraw
     * @param recipient The recipient address, default to subaccount address
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The transaction hash
     */
    async withdrawFromSubaccount(subaccount, amount, recipient, memo) {
        const msgs = new Promise((resolve) => {
            const msg = this.withdrawFromSubaccountMessage(subaccount, amount, recipient);
            resolve([msg]);
        });
        return this.send(subaccount, () => msgs, false, undefined, memo);
    }
    /**
     * @description Create message to withdraw from subaccount to wallet
     * with human readable input.
     *
     * @param subaccount The subaccount to withdraw from
     * @param amount The amount to withdraw
     * @param recipient The recipient address
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The message
     */
    withdrawFromSubaccountMessage(subaccount, amount, recipient) {
        const validatorClient = this._validatorClient;
        if (validatorClient === undefined) {
            throw new Error('validatorClient not set');
        }
        const quantums = (0, ethers_1.parseUnits)(amount, validatorClient.config.denoms.USDC_DECIMALS);
        if (quantums > BigInt(long_1.default.MAX_VALUE.toString())) {
            throw new Error('amount to large');
        }
        if (quantums < 0) {
            throw new Error('amount must be positive');
        }
        return this.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(subaccount.address, subaccount.subaccountNumber, 0, long_1.default.fromString(quantums.toString()), recipient);
    }
    /**
     * @description Create message to withdraw from account to EVM chain (bridge withdrawal).
     *
     * @param sender Cosmos address of the sender
     * @param amount Amount in min units (string)
     * @param ethRecipient Recipient address on EVM chain
     * @param ethChainId Target EVM chain ID
     * @param assetId Asset ID in Cosmos
     *
     * @returns The message
     */
    withdrawalMessage(sender, amount, ethRecipient, ethChainId, assetId) {
        const validatorClient = this._validatorClient;
        if (validatorClient === undefined) {
            throw new Error('validatorClient not set');
        }
        return this.validatorClient.post.composer.composeMsgWithdrawal(sender, amount, ethRecipient, ethChainId, assetId);
    }
    /**
     * @description Create message to send chain token from subaccount to wallet
     * with human readable input.
     *
     * @param subaccount The subaccount to withdraw from
     * @param amount The amount to withdraw
     * @param recipient The recipient address
     *
     * @throws UnexpectedClientError if a malformed response is returned with no GRPC error
     * at any point.
     * @returns The message
     */
    sendTokenMessage(wallet, amount, recipient) {
        var _a;
        const address = wallet.address;
        if (address === undefined) {
            throw new errors_1.UserError('wallet address is not set. Call connectWallet() first');
        }
        const { CHAINTOKEN_DENOM: chainTokenDenom, CHAINTOKEN_DECIMALS: chainTokenDecimals } = ((_a = this._validatorClient) === null || _a === void 0 ? void 0 : _a.config.denoms) || {};
        if (chainTokenDenom === undefined || chainTokenDecimals === undefined) {
            throw new Error('Chain token denom not set in validator config');
        }
        const quantums = (0, ethers_1.parseUnits)(amount, chainTokenDecimals);
        return this.validatorClient.post.composer.composeMsgSendToken(address, recipient, chainTokenDenom, quantums.toString());
    }
    async signPlaceOrder(subaccount, marketId, type, side, price, 
    // trigger_price: number,   // not used for MARKET and LIMIT
    size, clientId, timeInForce, goodTilTimeInSeconds, execution, postOnly, reduceOnly) {
        const msgs = new Promise((resolve) => {
            const msg = this.placeOrderMessage(subaccount, marketId, type, side, price, 
            // trigger_price: number,   // not used for MARKET and LIMIT
            size, clientId, timeInForce, goodTilTimeInSeconds, execution, postOnly, reduceOnly);
            msg
                .then((it) => resolve([it]))
                .catch((err) => {
                console.log(err);
            });
        });
        const signature = await this.sign(subaccount, () => msgs, true);
        return Buffer.from(signature).toString('base64');
    }
    async signCancelOrder(subaccount, clientId, orderFlags, clobPairId, goodTilBlock, goodTilBlockTime) {
        const msgs = new Promise((resolve) => {
            const msg = this.validatorClient.post.composer.composeMsgCancelOrder(subaccount.address, subaccount.subaccountNumber, clientId, clobPairId, orderFlags, goodTilBlock, goodTilBlockTime);
            resolve([msg]);
        });
        const signature = await this.sign(subaccount, () => msgs, true);
        return Buffer.from(signature).toString('base64');
    }
    async bulkCancelAndTransferAndPlaceStatefulOrders(subaccount, 
    // these are executed in this order, all in one block, and all succeed or all fail
    cancelOrderPayloads, transferToSubaccountPayload, placeOrderPayloads, memo, broadcastMode) {
        if (cancelOrderPayloads.some((c) => c.orderFlags === types_1.OrderFlags.SHORT_TERM)) {
            throw new Error('SHORT_TERM cancels cannot be batched');
        }
        if (placeOrderPayloads.some((placePayload) => (0, chain_helpers_1.calculateOrderFlags)(placePayload.type, placePayload.timeInForce) ===
            types_1.OrderFlags.SHORT_TERM)) {
            throw new Error('SHORT_TERM orders cannot be batched');
        }
        const account = this.validatorClient.post.account(subaccount.address, undefined);
        const msgs = (async () => {
            const cancelMsgPromises = cancelOrderPayloads.map(async (cancelPayload) => {
                const cancelSubaccount = subaccount.cloneWithSubaccount(cancelPayload.subaccountNumber);
                return this.validatorClient.post.cancelOrderMsg(cancelSubaccount.address, cancelSubaccount.subaccountNumber, cancelPayload.clientId, cancelPayload.orderFlags, cancelPayload.clobPairId, cancelPayload.goodTilBlock, cancelPayload.goodTilBlockTime);
            });
            const transferMsg = (() => {
                if (transferToSubaccountPayload == null) {
                    return undefined;
                }
                const transferSubaccount = subaccount.cloneWithSubaccount(transferToSubaccountPayload.sourceSubaccountNumber);
                return this.transferToSubaccountMessage(transferSubaccount, transferSubaccount.address, transferToSubaccountPayload.recipientSubaccountNumber, transferToSubaccountPayload.transferAmount);
            })();
            const placeOrderMsgPromises = placeOrderPayloads.map((placePayload) => {
                const placeSubaccount = subaccount.cloneWithSubaccount(placePayload.subaccountNumber);
                return this.placeOrderMessage(placeSubaccount, placePayload.marketId, placePayload.type, placePayload.side, placePayload.price, placePayload.size, placePayload.clientId, placePayload.timeInForce, placePayload.goodTilTimeInSeconds, placePayload.execution, placePayload.postOnly, placePayload.reduceOnly, placePayload.triggerPrice, placePayload.marketInfo, placePayload.currentHeight, placePayload.goodTilBlock);
            });
            return Promise.all([
                ...cancelMsgPromises,
                ...(transferMsg != null ? [transferMsg] : []),
                ...placeOrderMsgPromises,
            ]);
        })();
        return this.send(subaccount, () => msgs, true, undefined, memo, broadcastMode !== null && broadcastMode !== void 0 ? broadcastMode : tendermint_rpc_1.Method.BroadcastTxCommit, () => account);
    }
    // vaults
    async depositToMegavault(subaccount, amountUsdc, broadcastMode) {
        return this.validatorClient.post.depositToMegavault(subaccount, (0, helpers_1.bigIntToBytes)((0, chain_helpers_1.calculateVaultQuantums)(amountUsdc)), broadcastMode);
    }
    depositToMegavaultMessage(subaccount, amountUsdc) {
        return this.validatorClient.post.depositToMegavaultMsg(subaccount.address, subaccount.subaccountNumber, (0, helpers_1.bigIntToBytes)((0, chain_helpers_1.calculateVaultQuantums)(amountUsdc)));
    }
    async withdrawFromMegavault(subaccount, shares, minAmount, broadcastMode) {
        return this.validatorClient.post.withdrawFromMegavault(subaccount, (0, helpers_1.bigIntToBytes)(BigInt(Math.floor(shares))), (0, helpers_1.bigIntToBytes)((0, chain_helpers_1.calculateVaultQuantums)(minAmount)), broadcastMode);
    }
    withdrawFromMegavaultMessage(subaccount, shares, minAmount) {
        return this.validatorClient.post.withdrawFromMegavaultMsg(subaccount.address, subaccount.subaccountNumber, (0, helpers_1.bigIntToBytes)(BigInt(Math.floor(shares))), (0, helpers_1.bigIntToBytes)((0, chain_helpers_1.calculateVaultQuantums)(minAmount)));
    }
    /**
     * @description Submit a governance proposal to add a new market.
     *
     * @param params Parameters neeeded to create a new market.
     * @param title Title of the gov proposal.
     * @param summary Summary of the gov proposal.
     * @param initialDepositAmount Initial deposit amount of the gov proposal.
     * @param proposer proposer of the gov proposal.
     *
     * @returns the transaction hash.
     */
    async submitGovAddNewMarketProposal(wallet, params, title, summary, initialDepositAmount, memo, metadata, expedited) {
        const msg = new Promise((resolve) => {
            const composer = this.validatorClient.post.composer;
            const registry = (0, registry_1.generateRegistry)();
            const msgs = [];
            const isDydxUsd = params.ticker.toLowerCase() === 'dydx-usd';
            // x/prices.MsgCreateOracleMarket
            const createOracleMarket = composer.composeMsgCreateOracleMarket(params.id, params.ticker, params.priceExponent, params.minExchanges, params.minPriceChange, params.exchangeConfigJson);
            // x/perpetuals.MsgCreatePerpetual
            const createPerpetual = composer.composeMsgCreatePerpetual(params.id, isDydxUsd ? 1000001 : params.id, params.ticker, params.atomicResolution, params.liquidityTier, params.marketType);
            // x/clob.MsgCreateClobPair
            const createClobPair = composer.composeMsgCreateClobPair(params.id, params.id, params.quantumConversionExponent, params.stepBaseQuantums, params.subticksPerTick);
            // x/clob.MsgUpdateClobPair
            const updateClobPair = composer.composeMsgUpdateClobPair(params.id, params.id, params.quantumConversionExponent, params.stepBaseQuantums, params.subticksPerTick);
            // x/delaymsg.MsgDelayMessage
            const delayMessage = composer.composeMsgDelayMessage(
            // IMPORTANT: must wrap messages in Any type to fit into delaymsg.
            composer.wrapMessageAsAny(registry, updateClobPair), params.delayBlocks);
            // The order matters.
            if (!isDydxUsd) {
                msgs.push(createOracleMarket);
            }
            msgs.push(createPerpetual);
            msgs.push(createClobPair);
            msgs.push(delayMessage);
            // x/gov.v1.MsgSubmitProposal
            const submitProposal = composer.composeMsgSubmitProposal(title, initialDepositAmount, this.validatorClient.config.denoms, // use the client denom.
            summary, 
            // IMPORTANT: must wrap messages in Any type for gov's submit proposal.
            composer.wrapMessageArrAsAny(registry, msgs), wallet.address, // proposer
            metadata, expedited);
            resolve([submitProposal]);
        });
        return this.send(subaccount_1.SubaccountInfo.forLocalWallet(wallet), () => msg, false, undefined, memo);
    }
    async createMarketPermissionless(subaccount, ticker, broadcastMode, gasAdjustment, memo) {
        return this.validatorClient.post.createMarketPermissionless(ticker, subaccount, broadcastMode, gasAdjustment, memo);
    }
    async addAuthenticator(subaccount, authenticatorType, data) {
        // Validate the provided authenticators before sending to the validator
        const authenticator = {
            type: authenticatorType,
            config: JSON.parse(new TextDecoder().decode(data)),
        };
        if (!this.validateAuthenticator(authenticator)) {
            throw new Error('Invalid authenticator, please ensure the authenticator permissions are correct');
        }
        return this.validatorClient.post.addAuthenticator(subaccount, authenticatorType, data);
    }
    async removeAuthenticator(subaccount, id) {
        return this.validatorClient.post.removeAuthenticator(subaccount, long_1.default.fromString(id));
    }
    async getAuthenticators(address) {
        return this.validatorClient.get.getAuthenticators(address);
    }
    validateAuthenticator(authenticator) {
        const decodeCompositeConfig = (config) => {
            if (Array.isArray(config)) {
                return config;
            }
            if (typeof config === 'string') {
                try {
                    const decoded = Buffer.from(config, 'base64').toString('utf8');
                    const parsed = JSON.parse(decoded);
                    return Array.isArray(parsed) ? parsed : null;
                }
                catch {
                    return null;
                }
            }
            return null;
        };
        const checkAuthenticator = (auth) => {
            if (auth.type === constants_1.AuthenticatorType.SIGNATURE_VERIFICATION) {
                return true;
            }
            if (auth.type === constants_1.AuthenticatorType.ANY_OF || auth.type === constants_1.AuthenticatorType.ALL_OF) {
                const subAuthenticators = decodeCompositeConfig(auth.config);
                if (subAuthenticators == null) {
                    return false;
                }
                if (auth.type === constants_1.AuthenticatorType.ANY_OF) {
                    return subAuthenticators.every((nested) => checkAuthenticator(nested));
                }
                return subAuthenticators.some((nested) => checkAuthenticator(nested));
            }
            return false;
        };
        return checkAuthenticator(authenticator);
    }
}
exports.CompositeClient = CompositeClient;
//# sourceMappingURL=data:application/json;base64, 