import { __addDisposableResource, __disposeResources } from "tslib";
import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
import { KeetaAnchorMetadataServer } from '../../lib/anchor-metadata-server.js';
import { KeetaNet } from '../../client/index.js';
import { KeetaAnchorError, KeetaAnchorUserError } from '../../lib/error.js';
import { assertConversionInputCanonicalJSON, assertKeetaFXAnchorClientCreateExchangeRequestJSON, Errors } from './common.js';
import * as Signing from '../../lib/utils/signing.js';
import { KeetaAnchorQueueRunner, KeetaAnchorQueueStorageDriverMemory } from '../../lib/queue/index.js';
import { KeetaAnchorQueuePipelineAdvanced } from '../../lib/queue/pipeline.js';
import { assertNever } from '../../lib/utils/never.js';
import * as typia from 'typia';
import { assertExchangeBlockParametersAndComputeRefund, convertQuoteToExpectedSwapWithoutCost } from './util.js';
import { AsyncDisposableStack } from '../../lib/utils/defer.js';
import { asleep } from '../../lib/utils/asleep.js';
/**
 * Enable additional runtime "paranoid" checks in the FX server.
 *
 * This may have a small performance impact but increases safety
 * by ensuring that the accounts used in quotes are actually
 * configured in the server.
 *
 * During the transition to multiple accounts this may help catch
 * misconfigurations so it is enabled by default for now.
 */
const PARANOID = true;
;
async function formatQuoteSignable(unsignedQuote) {
    const retval = [
        unsignedQuote.request.from,
        unsignedQuote.request.to,
        unsignedQuote.request.amount,
        unsignedQuote.request.affinity,
        unsignedQuote.account,
        unsignedQuote.convertedAmount,
        unsignedQuote.cost.token,
        unsignedQuote.cost.amount
    ];
    if (unsignedQuote.request.preferredCostAsset !== undefined) {
        retval.push(unsignedQuote.request.preferredCostAsset);
    }
    return (retval);
}
async function generateSignedQuote(signer, unsignedQuote) {
    const signableQuote = await formatQuoteSignable(unsignedQuote);
    const signed = await Signing.SignData(signer, signableQuote);
    return ({
        ...unsignedQuote,
        signed: signed
    });
}
async function verifySignedData(signedBy, quote) {
    const signableQuote = await formatQuoteSignable(quote);
    return (await Signing.VerifySignedData(signedBy, signableQuote, quote.signed));
}
async function requestToAccounts(config, request) {
    let account;
    // eslint-disable-next-line @typescript-eslint/no-deprecated
    if (config.account !== undefined) {
        if (!('getConversionRateAndFee' in config.fx)) {
            throw (new Error('getConversionRateAndFee must be provided in fx configuration when using account property'));
        }
        const rateFee = await config.fx.getConversionRateAndFee(request, { purpose: 'estimate' });
        account = rateFee.account;
    }
    else {
        account = null;
    }
    let signer = null;
    if (config.signer !== undefined) {
        signer = (KeetaNet.lib.Account.isInstance(config.signer) ? config.signer : await config.signer(request)).assertAccount();
    }
    if (account !== null) {
        if (signer === null) {
            signer = account.assertAccount();
        }
        if (!account.isAccount() && !account.isStorage()) {
            throw (new Error('FX Account should be an Account or Storage Account'));
        }
    }
    else if (signer === null) {
        throw (new Error('Either account or signer must be provided'));
    }
    return ({
        account: account,
        signer: signer
    });
}
export function toValidateQuoteInput(input) {
    const ret = {
        account: KeetaNet.lib.Account.toAccount(input.account),
        convertedAmount: BigInt(input.convertedAmount),
        cost: {
            amount: BigInt(input.cost.amount),
            token: KeetaNet.lib.Account.toAccount(input.cost.token)
        }
    };
    if ('convertedAmountBound' in input && input.convertedAmountBound !== undefined) {
        ret.convertedAmountBound = BigInt(input.convertedAmountBound);
    }
    if ('signed' in input && input.signed !== undefined) {
        ret.signed = input.signed;
    }
    if ('request' in input && input.request !== undefined) {
        ret.request = {
            from: KeetaNet.lib.Account.toAccount(input.request.from),
            to: KeetaNet.lib.Account.toAccount(input.request.to),
            amount: BigInt(input.request.amount),
            affinity: input.request.affinity
        };
    }
    return (ret);
}
function encodeKeetaFXAnchorQueueStage1Request(request) {
    let expected;
    if (request.expected === null) {
        expected = null;
    }
    else {
        expected = {
            receive: {
                token: request.expected.receive.token.publicKeyString.get(),
                amount: request.expected.receive.amount.toString()
            },
            send: {
                token: request.expected.send.token.publicKeyString.get(),
                amount: request.expected.send.amount.toString()
            }
        };
    }
    ;
    const account = request.account.publicKeyString.get();
    const block = Buffer.from(request.block.toBytes()).toString('base64');
    const retval = {
        version: 1,
        account,
        block,
        request: request.request,
        expected: expected
    };
    return (retval);
}
function decodeKeetaFXAnchorQueueStage1Request(request) {
    /* See note at bottom of file */
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    const reqJSON = assertKeetaFXAnchorQueueStage1RequestJSON(request);
    if (reqJSON.version !== 1) {
        throw (new Error(`Unsupported KeetaFXAnchorQueueStage1Request version ${reqJSON.version}`));
    }
    let expected;
    if (reqJSON.expected === null) {
        expected = null;
    }
    else {
        expected = {
            receive: {
                token: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.expected.receive.token).assertKeyType(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN),
                amount: BigInt(reqJSON.expected.receive.amount)
            },
            send: {
                token: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.expected.send.token).assertKeyType(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN),
                amount: BigInt(reqJSON.expected.send.amount)
            }
        };
    }
    const retval = {
        account: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.account),
        block: new KeetaNet.lib.Block(reqJSON.block),
        request: reqJSON.request,
        expected: expected
    };
    return (retval);
}
class KeetaFXAnchorQueuePipelineStage1 extends KeetaAnchorQueueRunner {
    serverConfig;
    sequential = true;
    /**
     * Timeout for processing a single job -- if exceeded the job is marked as aborted
     */
    processTimeout = 60 * 1000;
    constructor(config) {
        super(config);
        this.serverConfig = config.serverConfig;
        this.processorAborted = this.processorStuck.bind(this);
    }
    /**
     * Handles both stuck (no status update after a long period) and
     * aborted (timeout while processing an entry) states.
     *
     * We just put the job back into pending because the processor
     * will check the network state again.
     */
    async processorStuck(entry) {
        return ({
            status: 'pending',
            output: entry.output
        });
    }
    /**
     * Process the entry, attempting to submit the swap block(s)
     * to the network.  Verifies the block can be submitted before
     * attempting submission.  Also verifies if the block is already
     * on the network and marks the job as completed if so.
     */
    async processor(entry) {
        const { block, request } = entry.request;
        const config = this.serverConfig;
        let userClient;
        if (KeetaNet.UserClient.isInstance(config.client)) {
            userClient = config.client;
            if (!(userClient.account.comparePublicKey(entry.request.account))) {
                return ({
                    status: 'failed_permanently',
                    output: null,
                    error: `Mismatched account for FX request with configured UserClient account`
                });
            }
        }
        else {
            const { signer, account: checkAccount } = await requestToAccounts(config, request);
            if (checkAccount === null) {
                if (this.serverConfig.accounts === undefined) {
                    throw (new Error('No accounts configured for FX server'));
                }
                if (!this.serverConfig.accounts.has(entry.request.account)) {
                    return ({
                        status: 'failed_permanently',
                        output: null,
                        error: `Mismatched account for FX request and configured account (no matching account found)`
                    });
                }
            }
            else if (!checkAccount.comparePublicKey(entry.request.account)) {
                return ({
                    status: 'failed_permanently',
                    output: null,
                    error: `Mismatched account for FX request and configured account (single account not matched)`
                });
            }
            userClient = new KeetaNet.UserClient({
                client: config.client.client,
                network: config.client.network,
                networkAlias: config.client.networkAlias,
                account: entry.request.account,
                signer: signer
            });
        }
        /* Check for the block already being on the network, if so we can mark this job as completed */
        const blockExists = await userClient.block(block.hash);
        if (blockExists !== null) {
            const existingOutput = entry.output;
            const blocks = existingOutput?.blocks ?? [Buffer.from(block.toBytes()).toString('base64')];
            return ({
                status: 'completed',
                output: {
                    blockhash: block.hash.toString(),
                    blocks: blocks
                }
            });
        }
        /* Get the current head block of the account in the block */
        const accountHead = await userClient.head({ account: block.account });
        let isHeadBlock = false;
        if (accountHead !== null) {
            isHeadBlock = accountHead.compare(block.previous);
        }
        else {
            isHeadBlock = block['$opening'];
        }
        /* If the account's head block is not the block's previous, see if the block's previous exists on the network */
        if (!isHeadBlock) {
            let previousBlockOnNetwork = false;
            if (block['$opening']) {
                /* Opening block means that there is no previous block, so we are free to proceed */
                previousBlockOnNetwork = true;
            }
            else {
                const previousBlock = await userClient.block(block.previous);
                if (previousBlock !== null) {
                    previousBlockOnNetwork = true;
                }
            }
            let blockPreviousString;
            if (block['$opening']) {
                blockPreviousString = '<opening>';
            }
            else {
                blockPreviousString = block.previous.toString();
            }
            /* If block.previous exists on the network, and it's not the account head block we can mark this job as failed_permanently */
            if (previousBlockOnNetwork) {
                /* Block's previous exists on the network, but is not the account head -- mark as failed_permanently */
                return ({
                    status: 'failed_permanently',
                    output: null,
                    error: `Block previous (${blockPreviousString}) exists on the network but is not the account head`
                });
            }
            else {
                /* If block.previous does not exist on the network we can mark this job as failed_temporarily -- it can't process until the missing block is added */
                /* Block's previous does not exist on the network -- mark as failed_temporarily */
                return ({
                    status: 'failed_temporarily',
                    output: null,
                    error: `Block previous (${blockPreviousString}) does not exist on the network`
                });
            }
        }
        /* We are clear to attempt the swap now */
        const builder = userClient.initBuilder();
        let expected = entry.request.expected;
        /**
         * We only want to refund excess for cost/paying token if it is not a fixed rate
         * as if it is a fixed rate transfer, you can assume the client did not send unintentionally
         *
         * Additionally, other FX anchors are not forced to refund any, so there is no guarantee to client that refunds will occur
         */
        if (expected === null) {
            if (!('getConversionRateAndFee' in this.serverConfig.fx)) {
                this.serverConfig.logger?.error('KeetaFXAnchorQueuePipelineStage1::processor', 'getConversionRateAndFee must be provided in fx configuration to process exchanges without expected details');
                return ({
                    status: 'failed_permanently',
                    output: null,
                    error: 'Server misconfiguration: getConversionRateAndFee must be provided in fx configuration to process exchanges without expected details'
                });
            }
            if (request.preferredCostAsset !== undefined) {
                if (this.serverConfig.fx.acceptedCostAssets === undefined) {
                    return ({
                        status: 'failed_permanently',
                        output: null,
                        error: 'This server does not support preferred cost asset selection'
                    });
                }
                if (!this.serverConfig.fx.acceptedCostAssets.includes(request.preferredCostAsset)) {
                    return ({
                        status: 'failed_permanently',
                        output: null,
                        error: `Preferred cost asset "${request.preferredCostAsset}" is not accepted by this server`
                    });
                }
            }
            const quote = await this.serverConfig.fx.getConversionRateAndFee(request, { purpose: 'exchange', request: entry.request });
            const { refunds } = assertExchangeBlockParametersAndComputeRefund({
                block: block,
                liquidityAccount: entry.request.account,
                allowedLiquidityAccounts: null,
                checks: { quote, request },
                isQuoteBasedExchange: false
            });
            for (const refund of refunds) {
                builder.send(block.account, refund.amount, refund.token);
            }
            expected = convertQuoteToExpectedSwapWithoutCost({ quote, request });
        }
        builder.send(block.account, expected.send.amount, expected.send.token);
        const sendBlock = await builder.computeBlocks();
        const swapBlocks = [...sendBlock.blocks, block];
        const publishOptions = {};
        if (userClient.config.generateFeeBlock !== undefined) {
            publishOptions.generateFeeBlock = userClient.config.generateFeeBlock;
        }
        try {
            const publishResult = await userClient.client.transmit(swapBlocks, publishOptions);
            if (!publishResult.publish) {
                throw (new Error('Exchange Publish Failed'));
            }
        }
        catch (error) {
            if (KeetaNet.lib.Error.isInstance(error) &&
                'shouldRetry' in error &&
                // Disable this warning as there is nothing stopping this from not being a boolean
                // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
                error.shouldRetry === false) {
                this.serverConfig.logger?.warn('KeetaFXAnchorQueuePipelineStage1::processor', 'Non-retryable error publishing swap blocks:', error);
                return ({
                    status: 'failed_permanently',
                    output: null,
                    error: `${error.code} ${error.message}`
                });
            }
            throw (error);
        }
        /* Set the output and mark the job as pending so we can run the queue again and check for completion */
        return ({
            status: 'pending',
            output: {
                blockhash: block.hash.toString(),
                blocks: swapBlocks.map(function (block) {
                    return (Buffer.from(block.toBytes()).toString('base64'));
                })
            }
        });
    }
    encodeRequest(request) {
        return (encodeKeetaFXAnchorQueueStage1Request(request));
    }
    encodeResponse(response) {
        return (response);
    }
    decodeRequest(request) {
        return (decodeKeetaFXAnchorQueueStage1Request(request));
    }
    decodeResponse(response) {
        /* See note at bottom of file */
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        return (assertKeetaFXAnchorQueueStage1ResponseOrNull(response));
    }
}
class KeetaFXAnchorQueuePipeline extends KeetaAnchorQueuePipelineAdvanced {
    serverConfig;
    accounts;
    runners = {};
    extensions;
    successStageRunner = null;
    failureStageRunner = null;
    constructor(options) {
        super(options);
        this.serverConfig = options.serverConfig;
        this.accounts = options.accounts;
        this.extensions = options.extensions;
    }
    async createPipeline() {
        const failureExtension = this.extensions?.failure;
        const successExtension = this.extensions?.success;
        if (failureExtension || successExtension) {
            class BaseExtensionClass extends KeetaAnchorQueueRunner {
                #configSet = false;
                // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
                #processorMethod = (() => {
                    throw (new Error('config not set'));
                });
                _setConfigurationAndProcessor(config) {
                    if (this.#configSet) {
                        throw (new Error('Configuration can only be set once'));
                    }
                    this.#configSet = true;
                    if (typeof config === 'function') {
                        this.#processorMethod = config;
                    }
                    else {
                        const { processor, ...parameters } = config;
                        this.#processorMethod = processor;
                        this.setConfiguration(parameters);
                    }
                }
                async processor(entry) {
                    return (await this.#processorMethod(entry));
                }
            }
            if (failureExtension) {
                const failureQueue = await this.baseQueue.partition('failed');
                this.queues.push(failureQueue);
                const runner = (new class extends BaseExtensionClass {
                    encodeRequest(request) { return (encodeKeetaFXAnchorQueueStage1Request(request)); }
                    decodeRequest(request) { return (decodeKeetaFXAnchorQueueStage1Request(request)); }
                    encodeResponse(response) { return (response); }
                    decodeResponse(response) { return (response); }
                }({
                    id: 'keeta-fx-anchor-failure-runner',
                    queue: failureQueue,
                    logger: this.logger
                }));
                runner._setConfigurationAndProcessor(failureExtension);
                this.failureStageRunner = runner;
            }
            if (successExtension) {
                const successQueue = await this.baseQueue.partition('success');
                this.queues.push(successQueue);
                const runner = (new class extends BaseExtensionClass {
                    encodeRequest(request) { return (request); }
                    decodeRequest(request) {
                        /* See note at bottom of file */
                        // eslint-disable-next-line @typescript-eslint/no-use-before-define
                        const parsed = assertKeetaFXAnchorQueueStage1ResponseOrNull(request);
                        if (parsed === null) {
                            throw (new Error('Invalid request for success extension'));
                        }
                        return (parsed);
                    }
                    encodeResponse(response) { return (response); }
                    decodeResponse(response) {
                        /* See note at bottom of file */
                        // eslint-disable-next-line @typescript-eslint/no-use-before-define
                        return (assertKeetaFXAnchorQueueStage1ResponseOrNull(response));
                    }
                }({
                    id: 'keeta-fx-anchor-success-runner',
                    queue: successQueue,
                    logger: this.logger
                }));
                runner._setConfigurationAndProcessor(successExtension);
                this.successStageRunner = runner;
            }
        }
        for (const account of this.accounts) {
            const queue = await this.baseQueue.partition(account.publicKeyAndTypeString);
            this.queues.push(queue);
            const runner = new KeetaFXAnchorQueuePipelineStage1({
                id: `keeta-fx-anchor-runner-${account.publicKeyAndTypeString}`,
                queue: queue,
                logger: this.logger,
                serverConfig: this.serverConfig
            });
            if (this.successStageRunner) {
                runner.pipe(this.successStageRunner, { exclusiveTarget: false });
            }
            if (this.failureStageRunner) {
                runner.pipeFailed(this.failureStageRunner, { exclusiveTarget: false });
            }
            this.runners[account.publicKeyAndTypeString] = runner;
        }
    }
    getStage(_ignore_stageID) {
        throw (new Error('method not supported'));
    }
    async add(request) {
        await super.init();
        const account = request.account.publicKeyAndTypeString;
        const runner = this.runners[account];
        if (runner === undefined) {
            throw (new Error(`No queue runner for account ${account}`));
        }
        return (await runner.add(request));
    }
    /**
     * Get the status of a request by ID.
     * If the request is found in any of the runners, it is returned with its current status.
     * Because typically the user will only care about the status of the first stage-
     * The request will be marked as failed or completed if it is found in the success or failure stages, instead of the status from those stages.
     * @param id The ID of the request to get the status of
     * @returns The inferred status of the request, or null if the request is not found
     */
    async get(id) {
        await super.init();
        for (const account of this.accounts) {
            const runner = this.runners[account.publicKeyAndTypeString];
            if (runner === undefined) {
                continue;
            }
            const entry = await runner.get(id);
            if (entry !== null) {
                if (entry.status === 'moved') {
                    let failedFound = null;
                    let successFound = null;
                    if (this.failureStageRunner || this.successStageRunner) {
                        await Promise.all([
                            (async () => {
                                if (!this.failureStageRunner) {
                                    return (null);
                                }
                                failedFound = await this.failureStageRunner.get(id);
                            })(),
                            (async () => {
                                if (!this.successStageRunner) {
                                    return (null);
                                }
                                successFound = await this.successStageRunner.get(id);
                            })()
                        ]);
                    }
                    if (failedFound && successFound) {
                        this.logger?.warn(`Request with ID ${String(id)} was found in both the success and failure stages -- this should not happen`, { requestID: id });
                    }
                    else if (failedFound) {
                        entry.status = 'failed_permanently';
                    }
                    else if (successFound) {
                        entry.status = 'completed';
                    }
                    else {
                        this.logger?.warn(`Request with ID ${String(id)} has moved from the main processing stage but was not found in either the success or failure stages -- leaving status as "moved"`, { requestID: id });
                    }
                }
                return (entry);
            }
        }
        return (null);
    }
    #getAllRunners() {
        const runners = [];
        for (const account of this.accounts) {
            const runner = this.runners[account.publicKeyAndTypeString];
            if (runner === undefined) {
                continue;
            }
            runners.push(runner);
        }
        if (this.successStageRunner) {
            runners.push(this.successStageRunner);
        }
        if (this.failureStageRunner) {
            runners.push(this.failureStageRunner);
        }
        return (runners);
    }
    async run(options) {
        await super.init();
        const runners = this.#getAllRunners();
        let retval = false;
        for (const runner of runners) {
            const more = await runner.run(options);
            if (more) {
                retval = true;
            }
        }
        return (retval);
    }
    async maintain() {
        await super.init();
        const runners = this.#getAllRunners();
        for (const runner of runners) {
            await runner.maintain();
        }
    }
    async destroy() {
        const logger = this.methodLogger('destroy');
        if (this.destroyed) {
            return;
        }
        this.destroyed = true;
        await super.destroy();
        const runners = this.#getAllRunners();
        for (const runner of runners) {
            try {
                await runner.destroy();
            }
            catch (error) {
                logger?.error('Error destroying runner:', error);
            }
        }
    }
}
class BaseKeetaNetFXAnchorHTTPServer extends KeetaAnchorMetadataServer {
    homepage;
    fx;
    canPerformExchanges;
    accounts = null;
    constructor(config) {
        super(config);
        this.homepage = config.homepage ?? '';
        this.fx = config.fx;
        // This is incorrect as undefined and true are the same (because default is true), but using a negation will not result in the correct type for canPerformExchanges, so we have to disable the lint warning and accept the incorrectness
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
        this.canPerformExchanges = config.fx.performExchanges === undefined || config.fx.performExchanges === true;
    }
    async getUnsignedQuoteData(conversion, purpose) {
        if (purpose !== 'estimate' && !this.canPerformExchanges) {
            throw (new Error(`FX configuration does not support performing exchanges, so purpose "${purpose}" is invalid`));
        }
        if (conversion.preferredCostAsset !== undefined) {
            if (this.fx.acceptedCostAssets === undefined) {
                throw (new KeetaAnchorUserError('This server does not support preferred cost asset selection'));
            }
            if (!this.fx.acceptedCostAssets.includes(conversion.preferredCostAsset)) {
                throw (new KeetaAnchorUserError(`Preferred cost asset "${conversion.preferredCostAsset}" is not accepted by this server`));
            }
        }
        if ('getConversionRateAndFee' in this.fx) {
            if (!this.accounts) {
                throw (new Error('Accounts must be configured to use "getConversionRateAndFee" in fx configuration'));
            }
            const rateAndFee = await this.fx.getConversionRateAndFee(conversion, { purpose });
            if (PARANOID) {
                const quoteAccount = rateAndFee.account;
                if (!this.accounts.has(quoteAccount)) {
                    throw (new Error('"getConversionRateAndFee" returned an account not configured for this server'));
                }
            }
            return (rateAndFee);
        }
        else if ('estimateRateAndFee' in this.fx) {
            if (purpose !== 'estimate') {
                throw (new Error('FX configuration only supports "estimateRateAndFee", so purpose must be "estimate"'));
            }
            return (await this.fx.estimateRateAndFee(conversion));
        }
        else {
            assertNever(this.fx);
        }
    }
    async initRoutes(config) {
        const routes = {};
        /*
         * To use the instance within the route handlers, we need to
         * make a local reference to it.
         */
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const instance = this;
        /**
         * If a homepage is provided, setup the route for it
         */
        if ('homepage' in config) {
            routes['GET /'] = async function () {
                let homepageData;
                if (typeof config.homepage === 'string') {
                    homepageData = config.homepage;
                }
                else {
                    if (!config.homepage) {
                        throw (new Error('internal error: No homepage function provided'));
                    }
                    homepageData = await config.homepage();
                }
                return ({
                    output: homepageData,
                    contentType: 'text/html'
                });
            };
        }
        /**
         * Setup the request handler for an estimate request
         */
        routes['POST /api/getEstimate'] = async (_ignore_params, postData) => {
            if (!postData || typeof postData !== 'object') {
                throw (new Error('No POST data provided'));
            }
            if (!('request' in postData)) {
                throw (new Error('POST data missing request'));
            }
            const conversion = assertConversionInputCanonicalJSON(postData.request);
            const rateAndFee = await this.getUnsignedQuoteData(conversion, 'estimate');
            let requiresQuoteBody;
            if (!instance.quoteConfiguration || !instance.canPerformExchanges) {
                requiresQuoteBody = { canPerformExchange: false };
            }
            else if (instance.quoteConfiguration.requiresQuote) {
                requiresQuoteBody = { requiresQuote: true };
            }
            else {
                if (rateAndFee.convertedAmountBound === undefined) {
                    instance.logger.warn('POST /api/getEstimate', 'FX configuration indicates quotes are not required, but "convertedAmountBound" was not provided in the rate and fee response');
                }
                else {
                    if (conversion.affinity === 'to' && (BigInt(rateAndFee.convertedAmount) > rateAndFee.convertedAmountBound)) {
                        throw (new KeetaAnchorError('Affinity is to, but bound is less than estimated sent amount'));
                    }
                    if (conversion.affinity === 'from' && (BigInt(rateAndFee.convertedAmount) < rateAndFee.convertedAmountBound)) {
                        throw (new KeetaAnchorError('Affinity is from, but bound is greater than estimated received amount'));
                    }
                }
                if (!('account' in rateAndFee) || !rateAndFee.account) {
                    throw (new Error('FX configuration indicates quotes are not required, but no account was provided in the rate and fee response'));
                }
                requiresQuoteBody = { requiresQuote: false, account: rateAndFee.account };
            }
            const estimateResponse = {
                ok: true,
                estimate: KeetaNet.lib.Utils.Conversion.toJSONSerializable({
                    request: conversion,
                    convertedAmount: rateAndFee.convertedAmount,
                    convertedAmountBound: rateAndFee.convertedAmountBound,
                    expectedCost: {
                        min: rateAndFee.cost.amount,
                        max: rateAndFee.cost.amount,
                        token: rateAndFee.cost.token
                    },
                    ...requiresQuoteBody
                })
            };
            return ({
                output: JSON.stringify(estimateResponse)
            });
        };
        return (routes);
    }
    /**
     * Return the servers endpoints and possible currency conversions metadata
     */
    async buildServiceMetadata() {
        const operations = {
            getEstimate: (new URL('/api/getEstimate', this.url)).toString()
        };
        const metadata = {
            from: this.fx.from ?? [],
            operations: operations
        };
        if (this.fx.acceptedCostAssets) {
            metadata.acceptedCostAssets = this.fx.acceptedCostAssets;
        }
        if (this.fx.legal) {
            metadata.legal = this.fx.legal;
        }
        return (metadata);
    }
}
export class KeetaNetFXAnchorEstimateHTTPServer extends BaseKeetaNetFXAnchorHTTPServer {
    quoteConfiguration = null;
    constructor(config) {
        super(config);
        if (this.canPerformExchanges) {
            throw (new Error('FX configuration indicates exchanges can be performed, but KeetaNetFXAnchorEstimateHTTPServer does not support exchange capability'));
        }
    }
}
export class KeetaNetFXAnchorHTTPServer extends BaseKeetaNetFXAnchorHTTPServer {
    client;
    accounts;
    account = undefined;
    signer;
    quoteSigner;
    fx;
    pipeline;
    quoteConfiguration;
    autoRun;
    autoRunRunning = false;
    constructor(config) {
        super(config);
        this.client = config.client;
        this.fx = config.fx;
        this.quoteSigner = config.quoteSigner;
        this.quoteConfiguration = config.quoteConfiguration ?? { requiresQuote: true };
        if (!this.canPerformExchanges) {
            throw (new Error('FX configuration indicates exchanges cannot be performed, but KeetaNetFXAnchorHTTPServer requires exchange capability'));
        }
        /*
        * Setup the accounts
        */
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        if (config.account !== undefined && config.accounts === undefined) {
            /*
            * Deprecated: If a single account is provided, use that
            * along with the signer to create the accounts set
            */
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            this.accounts = new KeetaNet.lib.Account.Set([config.account]);
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            this.signer = config.signer ?? config.account;
            // eslint-disable-next-line @typescript-eslint/no-deprecated
        }
        else if (config.accounts !== undefined && config.account === undefined) {
            /*
            * Only allow either "account" or "accounts"+"signer" to be provided
            */
            if (config.signer === undefined) {
                throw (new Error('If "accounts" is provided, "signer" must also be provided'));
            }
            this.accounts = config.accounts;
            this.signer = config.signer;
        }
        else {
            throw (new Error('Either "account" (and optional "signer") or "accounts" and "signer" must be provided, but not both "account" and "accounts"'));
        }
        if (this.accounts.size === 0) {
            throw (new Error('No FX accounts provided'));
        }
        /*
        * If no storage driver is provided, we default to an in-memory
        * that we auto-run
        */
        this.autoRun = config.storage?.autoRun ?? false;
        if (config.storage === undefined) {
            this.autoRun = true;
        }
        /*
        * Create the pipeline to process transactions
        */
        this.pipeline = new KeetaFXAnchorQueuePipeline({
            id: 'keeta-fx-anchor-queue-pipeline',
            baseQueue: config.storage?.queue ?? new KeetaAnchorQueueStorageDriverMemory({
                id: 'keeta-fx-anchor-queue-pipeline-memory-driver',
                logger: this.logger
            }),
            accounts: this.accounts,
            logger: this.logger,
            serverConfig: this,
            extensions: config.queueRunnerExtensions
        });
    }
    async initRoutes(config) {
        const routes = await super.initRoutes(config);
        const logger = this.logger;
        /*
         * To use the instance within the route handlers, we need to
         * make a local reference to it.
         */
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const instance = this;
        routes['POST /api/getQuote'] = async (_ignore_params, postData) => {
            if (!instance.quoteConfiguration.requiresQuote && !instance.quoteConfiguration.issueQuotes) {
                throw (new Errors.QuoteIssuanceDisabled());
            }
            if (!postData || typeof postData !== 'object') {
                throw (new Error('No POST data provided'));
            }
            if (!('request' in postData)) {
                throw (new Error('POST data missing request'));
            }
            const conversion = assertConversionInputCanonicalJSON(postData.request);
            const rateAndFee = await this.getUnsignedQuoteData(conversion, 'quote');
            const unsignedQuote = KeetaNet.lib.Utils.Conversion.toJSONSerializable({
                request: conversion,
                ...rateAndFee
            });
            if (config.quoteSigner === null) {
                throw (new Error('Quote signer not configured, this is required when issuing quotes'));
            }
            const signedQuote = await generateSignedQuote(config.quoteSigner, unsignedQuote);
            const quoteResponse = {
                ok: true,
                quote: signedQuote
            };
            return ({
                output: JSON.stringify(quoteResponse)
            });
        };
        routes['POST /api/createExchange'] = async (_ignore_params, postData) => {
            if (!postData || typeof postData !== 'object') {
                throw (new Error('No POST data provided'));
            }
            if (!('request' in postData)) {
                throw (new Error('POST data missing request'));
            }
            const request = assertKeetaFXAnchorClientCreateExchangeRequestJSON(postData.request);
            if (!('block' in request) || typeof request.block !== 'string') {
                throw (new Error('Block was not provided in exchange request'));
            }
            const block = new KeetaNet.lib.Block(request.block);
            let quoteInput;
            let conversionInput;
            let shouldValidateQuote;
            let liquidityAccount;
            let expectedConversion;
            let isQuoteBasedExchange;
            if ('quote' in request && 'estimate' in request && request.quote && request.estimate) {
                throw (new Error('Request cannot contain both quote and estimate'));
            }
            else if ('quote' in request && request.quote) {
                isQuoteBasedExchange = true;
                shouldValidateQuote = true;
                quoteInput = request.quote;
                conversionInput = quoteInput.request;
                const isValidQuote = await (async () => {
                    if (config.quoteSigner === null) {
                        return (false);
                    }
                    return (await verifySignedData(config.quoteSigner, quoteInput));
                })();
                if (!isValidQuote) {
                    throw (new Errors.QuoteValidationFailed());
                }
                liquidityAccount = quoteInput.account;
                expectedConversion = convertQuoteToExpectedSwapWithoutCost({
                    quote: toValidateQuoteInput(quoteInput),
                    request: conversionInput
                });
            }
            else if ('request' in request && request.request) {
                isQuoteBasedExchange = false;
                if (instance.quoteConfiguration.requiresQuote) {
                    throw (new Errors.QuoteRequired());
                }
                conversionInput = request.request;
                quoteInput = await this.getUnsignedQuoteData(conversionInput, 'estimate');
                if (instance.quoteConfiguration.validateQuoteBeforeExchange !== undefined) {
                    shouldValidateQuote = instance.quoteConfiguration.validateQuoteBeforeExchange;
                }
                else {
                    shouldValidateQuote = config.fx.validateQuote !== undefined;
                }
                for (const operation of block.operations) {
                    if (operation.type === KeetaNet.lib.Block.OperationType.SEND) {
                        if (!config.accounts) {
                            throw (new Error('No accounts configured for FX server, cannot infer liquidity account from block'));
                        }
                        if (config.accounts.has(operation.to)) {
                            liquidityAccount = operation.to;
                            break;
                        }
                    }
                }
                if (!liquidityAccount) {
                    throw (new KeetaAnchorUserError('Could not determine liquidity account from exchange block'));
                }
                // No expected conversion provided when using estimate, we determine the rate when processing the exchange
                expectedConversion = null;
            }
            else {
                throw (new Error('Either quote or request must be provided (but not both) in exchange request'));
            }
            if (!('account' in quoteInput) || !quoteInput.account) {
                throw (new Error('Quote is missing account, createExchange should not be supported for quotes without an account'));
            }
            const parsedQuote = toValidateQuoteInput(quoteInput);
            /* Validate the quote using the optional callback */
            if (config.fx.validateQuote !== undefined && shouldValidateQuote) {
                const isAcceptable = await config.fx.validateQuote(parsedQuote);
                if (!isAcceptable) {
                    throw (new Errors.QuoteValidationFailed());
                }
            }
            const liquidityAccountInstance = KeetaNet.lib.Account.toAccount(liquidityAccount);
            let allowedLiquidityAccounts;
            if (config.accounts) {
                allowedLiquidityAccounts = config.accounts;
                // eslint-disable-next-line @typescript-eslint/no-deprecated
            }
            else if (config.account) {
                // eslint-disable-next-line @typescript-eslint/no-deprecated
                allowedLiquidityAccounts = new KeetaNet.lib.Account.Set([config.account]);
            }
            else {
                throw (new Error('config.account or config.accounts must be provided'));
            }
            assertExchangeBlockParametersAndComputeRefund({
                block: block,
                liquidityAccount: liquidityAccountInstance,
                allowedLiquidityAccounts: allowedLiquidityAccounts,
                checks: { quote: parsedQuote, request: conversionInput },
                isQuoteBasedExchange: isQuoteBasedExchange
            });
            /* Enqueue the exchange request */
            const exchangeID = await instance.pipeline.add({
                account: liquidityAccountInstance,
                block: block,
                request: conversionInput,
                expected: expectedConversion
            });
            const exchangeResponse = {
                ok: true,
                exchangeID: exchangeID.toString(),
                status: 'pending'
            };
            if (instance.autoRun && !instance.autoRunRunning) {
                const env_1 = { stack: [], error: void 0, hasError: false };
                try {
                    /*
                     * Keep track of how many times, consecutively, the queue was empty when we
                     * went to run it
                     */
                    let noMoreJobsCount = 0;
                    /*
                     * Create a mutex around the queue running so we don't have
                     * lock contention for the worker ID if multiple requests
                     * are being served by the same instance
                     */
                    instance.autoRunRunning = true;
                    const cleanup = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
                    cleanup.defer(async function () {
                        instance.autoRunRunning = false;
                    });
                    /*
                     * For up to 15s process the queue, stopping only when
                     * we've had 2 consecutive runs that indicate there is
                     * no more in the queue to process
                     */
                    for (const startTime = Date.now(); Date.now() - startTime < 15000;) {
                        const more = await instance.pipeline.run({ timeoutMs: 1500 });
                        if (!more) {
                            noMoreJobsCount++;
                            if (noMoreJobsCount >= 2) {
                                break;
                            }
                        }
                        else {
                            noMoreJobsCount = 0;
                        }
                        await instance.pipeline.maintain();
                        await asleep(100);
                    }
                }
                catch (e_1) {
                    env_1.error = e_1;
                    env_1.hasError = true;
                }
                finally {
                    const result_1 = __disposeResources(env_1);
                    if (result_1)
                        await result_1;
                }
            }
            return ({
                output: JSON.stringify(exchangeResponse)
            });
        };
        routes['GET /api/getExchangeStatus/:id'] = async function (params) {
            if (params === undefined || params === null) {
                throw (new KeetaAnchorUserError('Expected params'));
            }
            const exchangeID = params.get('id');
            if (typeof exchangeID !== 'string') {
                throw (new Error('Missing exchangeID in params'));
            }
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            const queueEntryInfo = await instance.pipeline.get(exchangeID);
            const exchangeResponse = (function () {
                const inputStatus = queueEntryInfo?.status;
                switch (inputStatus) {
                    case undefined:
                        throw (new KeetaAnchorUserError('Exchange ID not found'));
                    case 'pending':
                    case 'processing':
                    case 'stuck':
                    case 'aborted':
                    case 'failed_temporarily':
                        return ({
                            ok: true,
                            status: 'pending',
                            exchangeID: exchangeID
                        });
                    case 'completed': {
                        const blockhash = queueEntryInfo?.output?.blockhash;
                        if (blockhash === undefined) {
                            return ({
                                ok: true,
                                status: 'pending',
                                exchangeID: exchangeID
                            });
                        }
                        else {
                            return ({
                                ok: true,
                                status: 'completed',
                                exchangeID: exchangeID,
                                blockhash: blockhash
                            });
                        }
                    }
                    case 'failed_permanently':
                        return ({
                            ok: false,
                            exchangeID: exchangeID,
                            status: 'failed',
                            error: 'Exchange failed'
                        });
                    case 'moved':
                        throw (new Error('Exchange ID has been moved'));
                    case '@internal':
                        throw (new Error('Invalid exchange status @internal'));
                    default:
                        assertNever(inputStatus);
                }
            })();
            logger.debug('GET /api/getExchangeStatus/:id', 'Exchange Status for ID', exchangeID, 'is', exchangeResponse, 'based on jobInfo', queueEntryInfo);
            return ({
                output: JSON.stringify(exchangeResponse)
            });
        };
        return (routes);
    }
    async buildServiceMetadata() {
        const baseMetadata = await super.buildServiceMetadata();
        if (this.quoteConfiguration.requiresQuote || this.quoteConfiguration.issueQuotes) {
            baseMetadata.operations.getQuote = (new URL('/api/getQuote', this.url)).toString();
        }
        baseMetadata.operations.getExchangeStatus = (new URL('/api/getExchangeStatus', this.url)).toString() + '/{id}';
        baseMetadata.operations.createExchange = (new URL('/api/createExchange', this.url)).toString();
        return (baseMetadata);
    }
    async stop() {
        await this.pipeline.destroy();
        await super.stop();
    }
}
/*
 * These are placed at the bottom of the file because the generation
 * breaks the code coverage computation;  Normally, we'd place these
 * in a ".generated.ts" file but for simplicity of internal types
 * we keep them here.
 */
const assertKeetaFXAnchorQueueStage1RequestJSON = (() => { const _io0 = input => 1 === input.version && "string" === typeof input.account && "string" === typeof input.block && ("object" === typeof input.request && null !== input.request && _io1(input.request)) && (null === input.expected || "object" === typeof input.expected && null !== input.expected && _io2(input.expected)); const _io1 = input => "string" === typeof input.from && (RegExp(/^keeta_am(.*)/).test(input.from) || RegExp(/^keeta_an(.*)/).test(input.from) || RegExp(/^keeta_ao(.*)/).test(input.from) || RegExp(/^keeta_ap(.*)/).test(input.from) || RegExp(/^tyblocks_am(.*)/).test(input.from) || RegExp(/^tyblocks_an(.*)/).test(input.from) || RegExp(/^tyblocks_ao(.*)/).test(input.from) || RegExp(/^tyblocks_ap(.*)/).test(input.from)) && ("string" === typeof input.to && (RegExp(/^keeta_am(.*)/).test(input.to) || RegExp(/^keeta_an(.*)/).test(input.to) || RegExp(/^keeta_ao(.*)/).test(input.to) || RegExp(/^keeta_ap(.*)/).test(input.to) || RegExp(/^tyblocks_am(.*)/).test(input.to) || RegExp(/^tyblocks_an(.*)/).test(input.to) || RegExp(/^tyblocks_ao(.*)/).test(input.to) || RegExp(/^tyblocks_ap(.*)/).test(input.to))) && "string" === typeof input.amount && ("from" === input.affinity || "to" === input.affinity) && (undefined === input.preferredCostAsset || "string" === typeof input.preferredCostAsset && (RegExp(/^keeta_am(.*)/).test(input.preferredCostAsset) || RegExp(/^keeta_an(.*)/).test(input.preferredCostAsset) || RegExp(/^keeta_ao(.*)/).test(input.preferredCostAsset) || RegExp(/^keeta_ap(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_am(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_an(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_ao(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_ap(.*)/).test(input.preferredCostAsset))); const _io2 = input => "object" === typeof input.receive && null !== input.receive && _io3(input.receive) && ("object" === typeof input.send && null !== input.send && _io4(input.send)); const _io3 = input => "string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) && "string" === typeof input.amount; const _io4 = input => "string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) && "string" === typeof input.amount; const _ao0 = (input, _path, _exceptionable = true) => (1 === input.version || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".version",
    expected: "1",
    value: input.version
}, _errorFactory)) && ("string" === typeof input.account || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".account",
    expected: "string",
    value: input.account
}, _errorFactory)) && ("string" === typeof input.block || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".block",
    expected: "string",
    value: input.block
}, _errorFactory)) && (("object" === typeof input.request && null !== input.request || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".request",
    expected: "{ from: TokenPublicKeyString; to: TokenPublicKeyString; amount: string; affinity: \"from\" | \"to\"; } & { preferredCostAsset?: TokenPublicKeyString; }",
    value: input.request
}, _errorFactory)) && _ao1(input.request, _path + ".request", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".request",
    expected: "{ from: TokenPublicKeyString; to: TokenPublicKeyString; amount: string; affinity: \"from\" | \"to\"; } & { preferredCostAsset?: TokenPublicKeyString; }",
    value: input.request
}, _errorFactory)) && (null === input.expected || ("object" === typeof input.expected && null !== input.expected || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".expected",
    expected: "(null | { receive: { token: TokenPublicKeyString; amount: string; } & {}; send: { token: TokenPublicKeyString; amount: string; } & {}; } & {})",
    value: input.expected
}, _errorFactory)) && _ao2(input.expected, _path + ".expected", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".expected",
    expected: "(null | { receive: { token: TokenPublicKeyString; amount: string; } & {}; send: { token: TokenPublicKeyString; amount: string; } & {}; } & {})",
    value: input.expected
}, _errorFactory)); const _ao1 = (input, _path, _exceptionable = true) => ("string" === typeof input.from && (RegExp(/^keeta_am(.*)/).test(input.from) || RegExp(/^keeta_an(.*)/).test(input.from) || RegExp(/^keeta_ao(.*)/).test(input.from) || RegExp(/^keeta_ap(.*)/).test(input.from) || RegExp(/^tyblocks_am(.*)/).test(input.from) || RegExp(/^tyblocks_an(.*)/).test(input.from) || RegExp(/^tyblocks_ao(.*)/).test(input.from) || RegExp(/^tyblocks_ap(.*)/).test(input.from)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".from",
    expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
    value: input.from
}, _errorFactory)) && ("string" === typeof input.to && (RegExp(/^keeta_am(.*)/).test(input.to) || RegExp(/^keeta_an(.*)/).test(input.to) || RegExp(/^keeta_ao(.*)/).test(input.to) || RegExp(/^keeta_ap(.*)/).test(input.to) || RegExp(/^tyblocks_am(.*)/).test(input.to) || RegExp(/^tyblocks_an(.*)/).test(input.to) || RegExp(/^tyblocks_ao(.*)/).test(input.to) || RegExp(/^tyblocks_ap(.*)/).test(input.to)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".to",
    expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
    value: input.to
}, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".amount",
    expected: "string",
    value: input.amount
}, _errorFactory)) && ("from" === input.affinity || "to" === input.affinity || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".affinity",
    expected: "(\"from\" | \"to\")",
    value: input.affinity
}, _errorFactory)) && (undefined === input.preferredCostAsset || "string" === typeof input.preferredCostAsset && (RegExp(/^keeta_am(.*)/).test(input.preferredCostAsset) || RegExp(/^keeta_an(.*)/).test(input.preferredCostAsset) || RegExp(/^keeta_ao(.*)/).test(input.preferredCostAsset) || RegExp(/^keeta_ap(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_am(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_an(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_ao(.*)/).test(input.preferredCostAsset) || RegExp(/^tyblocks_ap(.*)/).test(input.preferredCostAsset)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".preferredCostAsset",
    expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}` | undefined)",
    value: input.preferredCostAsset
}, _errorFactory)); const _ao2 = (input, _path, _exceptionable = true) => (("object" === typeof input.receive && null !== input.receive || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".receive",
    expected: "{ token: TokenPublicKeyString; amount: string; } & {}",
    value: input.receive
}, _errorFactory)) && _ao3(input.receive, _path + ".receive", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".receive",
    expected: "{ token: TokenPublicKeyString; amount: string; } & {}",
    value: input.receive
}, _errorFactory)) && (("object" === typeof input.send && null !== input.send || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".send",
    expected: "{ token: TokenPublicKeyString; amount: string; } & {}.o1",
    value: input.send
}, _errorFactory)) && _ao4(input.send, _path + ".send", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".send",
    expected: "{ token: TokenPublicKeyString; amount: string; } & {}.o1",
    value: input.send
}, _errorFactory)); const _ao3 = (input, _path, _exceptionable = true) => ("string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".token",
    expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
    value: input.token
}, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".amount",
    expected: "string",
    value: input.amount
}, _errorFactory)); const _ao4 = (input, _path, _exceptionable = true) => ("string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".token",
    expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
    value: input.token
}, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".amount",
    expected: "string",
    value: input.amount
}, _errorFactory)); const __is = input => "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
    if (false === __is(input)) {
        _errorFactory = errorFactory;
        ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "typia.createAssert",
            path: _path + "",
            expected: "KeetaFXAnchorQueueStage1RequestJSON",
            value: input
        }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
            method: "typia.createAssert",
            path: _path + "",
            expected: "KeetaFXAnchorQueueStage1RequestJSON",
            value: input
        }, _errorFactory))(input, "$input", true);
    }
    return input;
}; })();
const assertKeetaFXAnchorQueueStage1ResponseOrNull = (() => { const _io0 = input => Array.isArray(input.blocks) && input.blocks.every(elem => "string" === typeof elem) && "string" === typeof input.blockhash; const _ao0 = (input, _path, _exceptionable = true) => ((Array.isArray(input.blocks) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".blocks",
    expected: "Array<string>",
    value: input.blocks
}, _errorFactory)) && input.blocks.every((elem, _index2) => "string" === typeof elem || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".blocks[" + _index2 + "]",
    expected: "string",
    value: elem
}, _errorFactory)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".blocks",
    expected: "Array<string>",
    value: input.blocks
}, _errorFactory)) && ("string" === typeof input.blockhash || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "typia.createAssert",
    path: _path + ".blockhash",
    expected: "string",
    value: input.blockhash
}, _errorFactory)); const __is = input => null === input || "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
    if (false === __is(input)) {
        _errorFactory = errorFactory;
        ((input, _path, _exceptionable = true) => null === input || ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "typia.createAssert",
            path: _path + "",
            expected: "(KeetaFXAnchorQueueStage1Response | null)",
            value: input
        }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
            method: "typia.createAssert",
            path: _path + "",
            expected: "(KeetaFXAnchorQueueStage1Response | null)",
            value: input
        }, _errorFactory))(input, "$input", true);
    }
    return input;
}; })();
//# sourceMappingURL=server.js.map