"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handlers = exports.ExchangeType = void 0;
/* eslint-disable no-console */
const index_1 = require("@ledgerhq/coin-framework/account/index");
const state_1 = require("@ledgerhq/cryptoassets/state");
const hw_app_exchange_1 = require("@ledgerhq/hw-app-exchange");
const types_live_1 = require("@ledgerhq/types-live");
const wallet_api_core_1 = require("@ledgerhq/wallet-api-core");
const wallet_api_exchange_module_1 = require("@ledgerhq/wallet-api-exchange-module");
Object.defineProperty(exports, "ExchangeType", { enumerable: true, get: function () { return wallet_api_exchange_module_1.ExchangeType; } });
const wallet_api_server_1 = require("@ledgerhq/wallet-api-server");
const bignumber_js_1 = require("bignumber.js");
const bridge_1 = require("../../bridge");
const actions_1 = require("../../exchange/swap/api/v5/actions");
const transactionStrategies_1 = require("../../exchange/swap/transactionStrategies");
const converters_1 = require("../converters");
const error_1 = require("./error");
const error_2 = require("../../exchange/error");
const swap_1 = require("../../exchange/swap");
const setBroadcastTransaction_1 = require("../../exchange/swap/setBroadcastTransaction");
const hw_app_eth_1 = require("@ledgerhq/hw-app-eth");
const parser_1 = require("./parser");
const handleSwapErrors_1 = require("./handleSwapErrors");
const get_1 = __importDefault(require("lodash/get"));
const handlers = ({ accounts, tracking, manifest, uiHooks: { "custom.exchange.start": uiExchangeStart, "custom.exchange.complete": uiExchangeComplete, "custom.exchange.error": uiError, "custom.isReady": uiIsReady, "custom.exchange.swap": uiSwap, }, }) => ({
    "custom.exchange.start": (0, wallet_api_server_1.customWrapper)(async (params) => {
        if (!params) {
            tracking.startExchangeNoParams(manifest);
            return { transactionId: "" };
        }
        const trackingParams = {
            provider: params.provider,
            exchangeType: params.exchangeType,
        };
        tracking.startExchangeRequested(trackingParams);
        let exchangeParams;
        // Use `if else` instead of switch to leverage TS type narrowing and avoid `params` force cast.
        if (params.exchangeType == "SWAP") {
            exchangeParams = await extractSwapStartParam(params, accounts);
        }
        else if (params.exchangeType == "SELL") {
            exchangeParams = extractSellStartParam(params, accounts);
        }
        else {
            exchangeParams = extractFundStartParam(params, accounts);
        }
        return new Promise((resolve, reject) => uiExchangeStart({
            exchangeParams,
            onSuccess: (nonce, device) => {
                tracking.startExchangeSuccess(trackingParams);
                resolve({ transactionId: nonce, device });
            },
            onCancel: error => {
                tracking.startExchangeFail(trackingParams);
                reject(error);
            },
        }));
    }),
    "custom.exchange.complete": (0, wallet_api_server_1.customWrapper)(async (params) => {
        if (!params) {
            tracking.completeExchangeNoParams(manifest);
            return { transactionHash: "" };
        }
        const trackingParams = {
            provider: params.provider,
            exchangeType: params.exchangeType,
        };
        tracking.completeExchangeRequested(trackingParams);
        const realFromAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(params.fromAccountId);
        if (!realFromAccountId) {
            return Promise.reject(new Error(`accountId ${params.fromAccountId} unknown`));
        }
        const fromAccount = accounts.find(acc => acc.id === realFromAccountId);
        if (!fromAccount) {
            throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createAccountNotFound)(params.fromAccountId));
        }
        const fromParentAccount = (0, index_1.getParentAccount)(fromAccount, accounts);
        let exchange;
        if (params.exchangeType === "SWAP") {
            const realToAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(params.toAccountId);
            if (!realToAccountId) {
                return Promise.reject(new Error(`accountId ${params.toAccountId} unknown`));
            }
            const toAccount = accounts.find(a => a.id === realToAccountId);
            if (!toAccount) {
                throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createAccountNotFound)(params.toAccountId));
            }
            // TODO: check logic for EmptyTokenAccount
            let toParentAccount = (0, index_1.getParentAccount)(toAccount, accounts);
            let newTokenAccount;
            if (params.tokenCurrency) {
                const currency = await (0, state_1.getCryptoAssetsStore)().findTokenById(params.tokenCurrency);
                if (!currency) {
                    throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createCurrencyNotFound)(params.tokenCurrency));
                }
                if (toAccount.type === "Account") {
                    newTokenAccount = (0, index_1.makeEmptyTokenAccount)(toAccount, currency);
                    toParentAccount = toAccount;
                }
                else {
                    newTokenAccount = (0, index_1.makeEmptyTokenAccount)(toParentAccount, currency);
                }
            }
            const toCurrency = await getToCurrency(params.hexBinaryPayload, toAccount, newTokenAccount);
            exchange = {
                fromAccount,
                fromParentAccount,
                fromCurrency: (0, types_live_1.getCurrencyForAccount)(fromAccount),
                toAccount: newTokenAccount ? newTokenAccount : toAccount,
                toParentAccount,
                toCurrency,
            };
        }
        else {
            exchange = {
                fromAccount,
                fromParentAccount,
                fromCurrency: (0, types_live_1.getCurrencyForAccount)(fromAccount),
            };
        }
        const mainFromAccount = (0, index_1.getMainAccount)(fromAccount, fromParentAccount);
        const mainFromAccountFamily = mainFromAccount.currency.family;
        const transaction = (0, wallet_api_core_1.deserializeTransaction)(params.rawTransaction);
        const { liveTx } = (0, converters_1.getWalletAPITransactionSignFlowInfos)({
            walletApiTransaction: transaction,
            account: fromAccount,
        });
        if (liveTx.family !== mainFromAccountFamily) {
            return Promise.reject(new Error(`Account and transaction must be from the same family. Account family: ${mainFromAccountFamily}, Transaction family: ${liveTx.family}`));
        }
        const accountBridge = (0, bridge_1.getAccountBridge)(fromAccount, fromParentAccount);
        /**
         * 'subAccountId' is used for ETH and it's ERC-20 tokens.
         * This field is ignored for BTC
         */
        const subAccountId = fromParentAccount && fromParentAccount.id !== fromAccount.id ? fromAccount.id : undefined;
        const bridgeTx = accountBridge.createTransaction(fromAccount);
        /**
         * We append the `recipient` to the tx created from `createTransaction`
         * to avoid having userGasLimit reset to null for ETH txs
         * cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
         */
        const tx = accountBridge.updateTransaction({
            ...bridgeTx,
            recipient: liveTx.recipient,
        }, {
            ...liveTx,
            feesStrategy: params.feeStrategy.toLowerCase(),
            subAccountId,
        });
        let amountExpectedTo;
        let magnitudeAwareRate;
        let refundAddress;
        let payoutAddress;
        if (params.exchangeType === "SWAP") {
            // Get amountExpectedTo and magnitudeAwareRate from binary payload
            const decodePayload = await (0, hw_app_exchange_1.decodeSwapPayload)(params.hexBinaryPayload);
            amountExpectedTo = new bignumber_js_1.BigNumber(decodePayload.amountToWallet.toString());
            magnitudeAwareRate = tx.amount && amountExpectedTo.dividedBy(tx.amount);
            refundAddress = decodePayload.refundAddress;
            payoutAddress = decodePayload.payoutAddress;
        }
        return new Promise((resolve, reject) => uiExchangeComplete({
            exchangeParams: {
                exchangeType: wallet_api_exchange_module_1.ExchangeType[params.exchangeType],
                provider: params.provider,
                transaction: tx,
                signature: params.hexSignature,
                binaryPayload: params.hexBinaryPayload,
                exchange,
                feesStrategy: params.feeStrategy,
                swapId: params.exchangeType === "SWAP" ? params.swapId : undefined,
                amountExpectedTo,
                magnitudeAwareRate,
                refundAddress,
                payoutAddress,
            },
            onSuccess: (transactionHash) => {
                tracking.completeExchangeSuccess({
                    ...trackingParams,
                    currency: params.rawTransaction.family,
                });
                resolve({ transactionHash });
            },
            onCancel: error => {
                tracking.completeExchangeFail(trackingParams);
                reject(error);
            },
        }));
    }),
    "custom.exchange.error": (0, wallet_api_server_1.customWrapper)(async (params) => {
        return new Promise((resolve, reject) => uiError({
            error: params,
            onSuccess: () => {
                resolve();
            },
            onCancel: () => {
                reject();
            },
        }));
    }),
    "custom.exchange.swap": (0, wallet_api_server_1.customWrapper)(async (params) => {
        try {
            if (!params) {
                tracking.startExchangeNoParams(manifest);
                throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createUnknownError)({ message: "params is undefined" }));
            }
            const { provider, fromAmount, fromAmountAtomic, quoteId, toNewTokenId, customFeeConfig, swapAppVersion, sponsored, isEmbedded, } = params;
            const trackingParams = {
                provider: params.provider,
                exchangeType: params.exchangeType,
                isEmbeddedSwap: isEmbedded,
            };
            tracking.startExchangeRequested(trackingParams);
            const exchangeStartParams = (await extractSwapStartParam(params, accounts));
            const { fromCurrency, fromAccount, fromParentAccount, toCurrency, toAccount, toParentAccount, } = exchangeStartParams.exchange;
            if (!fromAccount || !fromCurrency) {
                throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createAccountNotFound)(params.fromAccountId));
            }
            const fromAccountAddress = fromParentAccount
                ? fromParentAccount.freshAddress
                : fromAccount.freshAddress;
            const toAccountAddress = toParentAccount
                ? toParentAccount.freshAddress
                : toAccount.freshAddress;
            // Step 1: Open the drawer and open exchange app
            const startExchange = async () => {
                return new Promise((resolve, reject) => {
                    uiExchangeStart({
                        exchangeParams: exchangeStartParams,
                        onSuccess: (nonce, device) => {
                            tracking.startExchangeSuccess(trackingParams);
                            resolve({ transactionId: nonce, device });
                        },
                        onCancel: error => {
                            tracking.startExchangeFail(trackingParams);
                            reject(error);
                        },
                    });
                });
            };
            let transactionId;
            let deviceInfo;
            try {
                const result = await startExchange();
                transactionId = result.transactionId;
                deviceInfo = result.device;
            }
            catch (error) {
                const rawError = (0, get_1.default)(error, "response.data.error", error);
                const wrappedError = (0, parser_1.createStepError)({
                    error: (0, parser_1.toError)(rawError),
                    step: parser_1.StepError.NONCE,
                });
                throw wrappedError;
            }
            tracking.swapPayloadRequested({
                provider,
                transactionId,
                fromAccountAddress,
                toAccountAddress,
                fromCurrencyId: fromCurrency.id,
                toCurrencyId: toCurrency?.id,
                fromAmount,
                quoteId,
            });
            const { binaryPayload, signature, payinAddress, swapId, payinExtraId, extraTransactionParameters, } = await (0, actions_1.retrieveSwapPayload)({
                provider,
                deviceTransactionId: transactionId,
                fromAccountAddress,
                toAccountAddress,
                fromAccountCurrency: fromCurrency.id,
                toAccountCurrency: toCurrency.id,
                amount: fromAmount,
                amountInAtomicUnit: fromAmountAtomic,
                quoteId,
                toNewTokenId,
            }).catch((error) => {
                const wrappedError = (0, parser_1.createStepError)({
                    error: (0, get_1.default)(error, "response.data.error", error),
                    step: parser_1.StepError.PAYLOAD,
                });
                throw wrappedError;
            });
            tracking.swapResponseRetrieved({
                binaryPayload,
                signature,
                payinAddress,
                swapId,
                payinExtraId,
                extraTransactionParameters,
            });
            // Complete Swap
            const trackingCompleteParams = {
                provider: params.provider,
                exchangeType: params.exchangeType,
                isEmbeddedSwap: isEmbedded,
            };
            tracking.completeExchangeRequested(trackingCompleteParams);
            const strategyData = {
                recipient: payinAddress,
                amount: fromAmountAtomic,
                currency: fromCurrency,
                customFeeConfig: customFeeConfig ?? {},
                payinExtraId,
                extraTransactionParameters,
                sponsored,
            };
            const transaction = await getStrategy(strategyData, "swap");
            const mainFromAccount = (0, index_1.getMainAccount)(fromAccount, fromParentAccount);
            if (transaction.family !== mainFromAccount.currency.family) {
                return Promise.reject(new Error(`Account and transaction must be from the same family. Account family: ${mainFromAccount.currency.family}, Transaction family: ${transaction.family}`));
            }
            const accountBridge = (0, bridge_1.getAccountBridge)(fromAccount, fromParentAccount);
            /**
             * 'subAccountId' is used for ETH and it's ERC-20 tokens.
             * This field is ignored for BTC
             */
            const subAccountId = fromParentAccount && fromParentAccount.id !== fromAccount.id ? fromAccount.id : undefined;
            const bridgeTx = accountBridge.createTransaction(fromAccount);
            /**
             * We append the `recipient` to the tx created from `createTransaction`
             * to avoid having userGasLimit reset to null for ETH txs
             * cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
             */
            const tx = accountBridge.updateTransaction({
                ...bridgeTx,
                recipient: transaction.recipient,
            }, {
                ...transaction,
                feesStrategy: params.feeStrategy.toLowerCase(),
                subAccountId,
            });
            // Get amountExpectedTo and magnitudeAwareRate from binary payload
            const decodePayload = await (0, hw_app_exchange_1.decodeSwapPayload)(binaryPayload);
            const amountExpectedTo = new bignumber_js_1.BigNumber(decodePayload.amountToWallet.toString());
            const magnitudeAwareRate = tx.amount && amountExpectedTo.dividedBy(tx.amount);
            const refundAddress = decodePayload.refundAddress;
            const payoutAddress = decodePayload.payoutAddress;
            // tx.amount should be BigNumber
            tx.amount = new bignumber_js_1.BigNumber(tx.amount);
            return new Promise((resolve, reject) => uiSwap({
                exchangeParams: {
                    exchangeType: wallet_api_exchange_module_1.ExchangeType.SWAP,
                    provider: params.provider,
                    transaction: tx,
                    signature: signature,
                    binaryPayload: binaryPayload,
                    exchange: {
                        fromAccount,
                        fromParentAccount,
                        toAccount,
                        toParentAccount,
                        fromCurrency: fromCurrency,
                        toCurrency: toCurrency,
                    },
                    feesStrategy: params.feeStrategy,
                    swapId: swapId,
                    amountExpectedTo: amountExpectedTo.toNumber(),
                    magnitudeAwareRate,
                    refundAddress,
                    payoutAddress,
                    sponsored,
                },
                onSuccess: ({ operationHash, swapId }) => {
                    tracking.completeExchangeSuccess({
                        ...trackingParams,
                        currency: transaction.family,
                    });
                    (0, setBroadcastTransaction_1.setBroadcastTransaction)({
                        provider,
                        result: { operation: operationHash, swapId },
                        sourceCurrencyId: fromCurrency.id,
                        targetCurrencyId: toCurrency?.id,
                        hardwareWalletType: deviceInfo?.modelId,
                        swapAppVersion,
                        fromAccountAddress,
                        toAccountAddress,
                        fromAmount,
                    });
                    resolve({ operationHash, swapId });
                },
                onCancel: error => {
                    (0, swap_1.postSwapCancelled)({
                        provider: provider,
                        swapId: swapId,
                        swapStep: (0, error_2.getSwapStepFromError)(error),
                        statusCode: error.name,
                        errorMessage: error.message,
                        sourceCurrencyId: fromCurrency.id,
                        targetCurrencyId: toCurrency?.id,
                        hardwareWalletType: deviceInfo?.modelId,
                        swapType: quoteId ? "fixed" : "float",
                        swapAppVersion,
                        fromAccountAddress,
                        toAccountAddress,
                        refundAddress,
                        payoutAddress,
                        fromAmount,
                        seedIdFrom: mainFromAccount.seedIdentifier,
                        seedIdTo: toParentAccount?.seedIdentifier || toAccount?.seedIdentifier,
                        data: transaction.data
                            ? `0x${(0, hw_app_eth_1.padHexString)(transaction.data?.toString("hex") || "")}`
                            : "0x",
                    });
                    reject((0, parser_1.createStepError)({ error, step: parser_1.StepError.SIGNATURE }));
                },
            }));
        }
        catch (error) {
            // Skip DrawerClosedError
            // do not redirect to the error screen
            if (isDrawerClosedError(error)) {
                throw error;
            }
            // Global catch for any errors during the swap process
            // moved out as sonarcloud suggested to avoid 4 level nested functions
            const createErrorRejector = (error, reject) => {
                return () => reject(error);
            };
            const displayError = (error) => new Promise((resolve, reject) => {
                const rejectWithError = createErrorRejector(error, reject);
                uiError({
                    error,
                    onSuccess: rejectWithError,
                    onCancel: rejectWithError,
                });
            });
            await (0, handleSwapErrors_1.handleErrors)(error, {
                onDisplayError: displayError,
            });
            throw error;
        }
    }),
    "custom.isReady": (0, wallet_api_server_1.customWrapper)(async () => {
        return new Promise((resolve, reject) => uiIsReady({
            onSuccess: () => {
                resolve();
            },
            onCancel: () => {
                reject();
            },
        }));
    }),
});
exports.handlers = handlers;
async function extractSwapStartParam(params, accounts) {
    if (!("fromAccountId" in params && "toAccountId" in params)) {
        throw new error_1.ExchangeError((0, error_1.createWrongSwapParams)(params));
    }
    const realFromAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(params.fromAccountId);
    if (!realFromAccountId) {
        throw new error_1.ExchangeError((0, error_1.createAccounIdNotFound)(params.fromAccountId));
    }
    const fromAccount = accounts.find(acc => acc.id === realFromAccountId);
    if (!fromAccount) {
        throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createAccountNotFound)(params.fromAccountId));
    }
    let toAccount;
    if (params.exchangeType === "SWAP" && params.toAccountId) {
        const realToAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(params.toAccountId);
        if (!realToAccountId) {
            throw new error_1.ExchangeError((0, error_1.createAccounIdNotFound)(params.toAccountId));
        }
        toAccount = accounts.find(a => a.id === realToAccountId);
        if (!toAccount) {
            throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createAccountNotFound)(params.toAccountId));
        }
    }
    const fromParentAccount = (0, index_1.getParentAccount)(fromAccount, accounts);
    const toParentAccount = toAccount ? (0, index_1.getParentAccount)(toAccount, accounts) : undefined;
    const currency = params.tokenCurrency
        ? await (0, state_1.getCryptoAssetsStore)().findTokenById(params.tokenCurrency)
        : null;
    const newTokenAccount = currency ? (0, index_1.makeEmptyTokenAccount)(toAccount, currency) : null;
    return {
        exchangeType: params.exchangeType,
        provider: params.provider,
        exchange: {
            fromAccount,
            fromParentAccount,
            fromCurrency: (0, types_live_1.getCurrencyForAccount)(fromAccount),
            toAccount: newTokenAccount ? newTokenAccount : toAccount,
            toParentAccount: toParentAccount,
            toCurrency: (0, types_live_1.getCurrencyForAccount)(newTokenAccount ? newTokenAccount : toAccount),
        },
    };
}
function extractSellStartParam(params, accounts) {
    if (!("provider" in params)) {
        throw new error_1.ExchangeError((0, error_1.createWrongSellParams)(params));
    }
    if (!params.fromAccountId) {
        return {
            exchangeType: params.exchangeType,
            provider: params.provider,
        };
    }
    const realFromAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(params?.fromAccountId);
    if (!realFromAccountId) {
        throw new error_1.ExchangeError((0, error_1.createAccounIdNotFound)(params.fromAccountId));
    }
    const fromAccount = accounts?.find(acc => acc.id === realFromAccountId);
    if (!fromAccount) {
        throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createAccountNotFound)(params.fromAccountId));
    }
    const fromParentAccount = (0, index_1.getParentAccount)(fromAccount, accounts);
    return {
        exchangeType: params.exchangeType,
        provider: params.provider,
        exchange: {
            fromAccount,
            fromParentAccount,
        },
    };
}
function extractFundStartParam(params, accounts) {
    if (!("provider" in params)) {
        throw new error_1.ExchangeError((0, error_1.createWrongFundParams)(params));
    }
    if (!params.fromAccountId) {
        return {
            exchangeType: params.exchangeType,
            provider: params.provider,
        };
    }
    const realFromAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(params?.fromAccountId);
    if (!realFromAccountId) {
        throw new error_1.ExchangeError((0, error_1.createAccounIdNotFound)(params.fromAccountId));
    }
    const fromAccount = accounts?.find(acc => acc.id === realFromAccountId);
    if (!fromAccount) {
        throw new wallet_api_core_1.ServerError((0, wallet_api_core_1.createAccountNotFound)(params.fromAccountId));
    }
    const fromParentAccount = (0, index_1.getParentAccount)(fromAccount, accounts);
    return {
        exchangeType: params.exchangeType,
        provider: params.provider,
        exchange: {
            fromAccount,
            fromParentAccount,
        },
    };
}
async function getToCurrency(binaryPayload, toAccount, newTokenAccount) {
    const { payoutAddress: tokenAddress, currencyTo } = await (0, hw_app_exchange_1.decodeSwapPayload)(binaryPayload);
    // In case of an SPL Token recipient and no TokenAccount exists.
    if (toAccount.type !== "TokenAccount" && // it must not be a SPL Token
        toAccount.currency.id === "solana" && // the target account must be a SOL Account
        tokenAddress !== toAccount.freshAddress) {
        // tokenAddress is the SPL token mint address for Solana tokens
        const splTokenCurrency = await (0, state_1.getCryptoAssetsStore)().findTokenByAddressInCurrency(tokenAddress, "solana");
        if (splTokenCurrency && splTokenCurrency.ticker === currencyTo)
            return splTokenCurrency;
    }
    return newTokenAccount?.token ?? (0, types_live_1.getCurrencyForAccount)(toAccount);
}
async function getStrategy({ recipient, amount, currency, customFeeConfig, payinExtraId, extraTransactionParameters, sponsored, }, customErrorType) {
    const family = currency.type === "TokenCurrency" ? currency.parentCurrency?.family : currency.family;
    if (!family) {
        throw new Error(`TokenCurrency missing parentCurrency family: ${currency.id}`);
    }
    // Remove unsupported utxoStrategy for now
    if (customFeeConfig?.utxoStrategy) {
        delete customFeeConfig.utxoStrategy;
    }
    const strategy = transactionStrategies_1.transactionStrategy?.[family];
    if (!strategy) {
        throw new Error(`No transaction strategy found for family: ${family}`);
    }
    // Convert customFeeConfig values to BigNumber
    const convertedCustomFeeConfig = {};
    if (customFeeConfig) {
        for (const [key, value] of Object.entries(customFeeConfig)) {
            convertedCustomFeeConfig[key] = new bignumber_js_1.BigNumber(value?.toString() || 0);
        }
    }
    return strategy({
        family,
        amount: new bignumber_js_1.BigNumber(amount),
        recipient,
        customFeeConfig: convertedCustomFeeConfig,
        payinExtraId,
        extraTransactionParameters,
        customErrorType,
        sponsored,
    });
}
function isDrawerClosedError(error) {
    if (!error || typeof error !== "object")
        return false;
    return ((0, get_1.default)(error, "name") === "DrawerClosedError" || (0, get_1.default)(error, "cause.name") === "DrawerClosedError");
}
//# sourceMappingURL=server.js.map