"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const cryptoassets_1 = require("@ledgerhq/cryptoassets");
const errors_1 = require("@ledgerhq/errors");
const hw_app_exchange_1 = __importStar(require("@ledgerhq/hw-app-exchange"));
const network_1 = __importDefault(require("@ledgerhq/live-network/network"));
const logs_1 = require("@ledgerhq/logs");
const bignumber_js_1 = require("bignumber.js");
const invariant_1 = __importDefault(require("invariant"));
const rxjs_1 = require("rxjs");
const secp256k1_1 = require("@noble/curves/secp256k1");
const __1 = require("../");
const account_1 = require("../../account");
const bridge_1 = require("../../bridge");
const live_env_1 = require("@ledgerhq/live-env");
const errors_2 = require("../../errors");
const deviceAccess_1 = require("../../hw/deviceAccess");
const promise_1 = require("../../promise");
const _1 = require("./");
const mock_1 = require("./mock");
const providers_1 = require("../providers");
const accountName_1 = require("@ledgerhq/live-wallet/accountName");
const withDevicePromise = (deviceId, fn) => (0, rxjs_1.firstValueFrom)((0, deviceAccess_1.withDevice)(deviceId)(transport => (0, rxjs_1.from)(fn(transport))));
// init a swap with the Exchange app
// throw if TransactionStatus have errors
// you get at the end a final Transaction to be done (it's not yet signed, nor broadcasted!) and a swapId
const initSwap = (input) => {
    let swapId;
    let { transaction } = input;
    const { exchange, exchangeRate, deviceId } = input;
    if ((0, live_env_1.getEnv)("MOCK"))
        return (0, mock_1.mockInitSwap)(exchange, exchangeRate, transaction);
    return new rxjs_1.Observable(o => {
        let unsubscribed = false;
        const confirmSwap = async () => {
            let ignoreTransportError;
            let magnitudeAwareRate;
            (0, logs_1.log)("swap", `attempt to connect to ${deviceId}`);
            await withDevicePromise(deviceId, async (transport) => {
                const ratesFlag = exchangeRate.tradeMethod === "fixed" ? 0 /* RateTypes.Fixed */ : 1 /* RateTypes.Floating */;
                const swap = new hw_app_exchange_1.default(transport, 0 /* ExchangeTypes.Swap */, ratesFlag);
                // NB this id is crucial to prevent replay attacks, if it changes
                // we need to start the flow again.
                const deviceTransactionId = await swap.startNewTransaction();
                if (unsubscribed)
                    return;
                const { provider, rateId } = exchangeRate;
                const { fromParentAccount, fromAccount, toParentAccount, toAccount } = exchange;
                const { amount } = transaction;
                const refundCurrency = (0, account_1.getAccountCurrency)(fromAccount);
                const unitFrom = (0, account_1.getAccountCurrency)(exchange.fromAccount).units[0];
                const payoutCurrency = (0, account_1.getAccountCurrency)(toAccount);
                const refundAccount = (0, account_1.getMainAccount)(fromAccount, fromParentAccount);
                const payoutAccount = (0, account_1.getMainAccount)(toAccount, toParentAccount);
                const apiAmount = new bignumber_js_1.BigNumber(amount).div(new bignumber_js_1.BigNumber(10).pow(unitFrom.magnitude));
                // Request a swap, this locks the rates for fixed trade method only.
                // NB Added the try/catch because of the API stability issues.
                let res;
                const swapProviderConfig = await (0, providers_1.getSwapProvider)(provider);
                const headers = {
                    EquipmentId: (0, live_env_1.getEnv)("USER_ID"),
                    ...((0, _1.getSwapUserIP)() !== undefined ? (0, _1.getSwapUserIP)() : {}),
                };
                const data = {
                    provider,
                    amountFrom: apiAmount.toString(),
                    amountFromInSmallestDenomination: amount.toNumber(),
                    from: refundCurrency.id,
                    to: payoutCurrency.id,
                    address: payoutAccount.freshAddress,
                    refundAddress: refundAccount.freshAddress,
                    deviceTransactionId,
                    ...(rateId && ratesFlag === 0 /* RateTypes.Fixed */
                        ? {
                            rateId,
                        }
                        : {}), // NB float rates dont need rate ids.
                };
                try {
                    res = await (0, network_1.default)({
                        method: "POST",
                        url: `${(0, _1.getSwapAPIBaseURL)()}/swap`,
                        headers,
                        data,
                    });
                    if (unsubscribed || !res || !res.data)
                        return;
                }
                catch (e) {
                    if (e.msg.messageKey == "WRONG_OR_EXPIRED_RATE_ID") {
                        o.next({
                            type: "init-swap-error",
                            error: new errors_2.SwapRateExpiredError(),
                            swapId,
                        });
                    }
                    o.next({
                        type: "init-swap-error",
                        error: new errors_2.SwapGenericAPIError(),
                        swapId,
                    });
                    o.complete();
                    return;
                }
                const swapResult = res.data;
                swapId = swapResult.swapId;
                const accountBridge = (0, bridge_1.getAccountBridge)(refundAccount);
                transaction = accountBridge.updateTransaction(transaction, {
                    recipient: swapResult.payinAddress,
                });
                if (refundCurrency.id === "ripple") {
                    transaction = accountBridge.updateTransaction(transaction, {
                        tag: new bignumber_js_1.BigNumber(swapResult.payinExtraId).toNumber(),
                    });
                    (0, invariant_1.default)(transaction.tag, "Refusing to swap xrp without a destination tag");
                }
                else if (refundCurrency.id === "stellar") {
                    transaction = accountBridge.updateTransaction(transaction, {
                        memoValue: swapResult.payinExtraId,
                        memoType: "MEMO_TEXT",
                    });
                    (0, invariant_1.default)(transaction.memoValue, "Refusing to swap xlm without a destination memo");
                }
                // Triplecheck we're not working with an abandonseed recipient anymore
                (0, invariant_1.default)(transaction.recipient !==
                    (0, cryptoassets_1.getAbandonSeedAddress)(refundCurrency.type === "TokenCurrency"
                        ? refundCurrency.parentCurrency.id
                        : refundCurrency.id), "Recipient address should never be the abandonseed address");
                transaction = await accountBridge.prepareTransaction(refundAccount, transaction);
                if (unsubscribed)
                    return;
                const { errors, estimatedFees } = await accountBridge.getTransactionStatus(refundAccount, transaction);
                if (unsubscribed)
                    return;
                const errorsKeys = Object.keys(errors);
                if (errorsKeys.length > 0) {
                    throw errors[errorsKeys[0]]; // throw the first error
                }
                if (swapProviderConfig.useInExchangeApp === false) {
                    throw new Error(`Unsupported provider type ${swapProviderConfig.type}`);
                }
                // Prepare swap app to receive the tx to forward.
                await swap.setPartnerKey((0, providers_1.convertToAppExchangePartnerKey)(swapProviderConfig));
                if (unsubscribed)
                    return;
                await swap.checkPartner(swapProviderConfig.signature);
                if (unsubscribed)
                    return;
                await swap.processTransaction(Buffer.from(swapResult.binaryPayload, "hex"), estimatedFees);
                if (unsubscribed)
                    return;
                const goodSign = (() => {
                    const sig = secp256k1_1.secp256k1.Signature.fromCompact(Buffer.from(swapResult.signature, "hex"));
                    return Buffer.from(sig.toDERRawBytes());
                })();
                await swap.checkTransactionSignature(goodSign);
                if (unsubscribed)
                    return;
                const mainPayoutCurrency = (0, account_1.getAccountCurrency)(payoutAccount);
                (0, invariant_1.default)(mainPayoutCurrency.type === "CryptoCurrency", "This should be a cryptocurrency");
                // FIXME: invariant not triggering typescriptp type guard
                if (mainPayoutCurrency.type !== "CryptoCurrency") {
                    throw new Error("This should be a cryptocurrency");
                }
                const mainPayoutBridge = (0, bridge_1.getAccountBridge)(payoutAccount);
                const payoutAddressParameters = mainPayoutBridge.getSerializedAddressParameters(payoutAccount, mainPayoutCurrency.id);
                if (unsubscribed)
                    return;
                if (!payoutAddressParameters) {
                    throw new Error(`Family not supported: ${mainPayoutCurrency.family}`);
                }
                const { config: payoutAddressConfig, signature: payoutAddressConfigSignature } = await (0, __1.getCurrencyExchangeConfig)(payoutCurrency);
                try {
                    await swap.validatePayoutOrAsset(payoutAddressConfig, payoutAddressConfigSignature, payoutAddressParameters);
                }
                catch (e) {
                    if (e instanceof errors_1.TransportStatusError && e.statusCode === 0x6a83) {
                        throw new errors_1.WrongDeviceForAccountPayout(undefined, {
                            accountName: (0, accountName_1.getDefaultAccountName)(payoutAccount),
                        });
                    }
                    throw e;
                }
                if (unsubscribed)
                    return;
                const mainRefundCurrency = (0, account_1.getAccountCurrency)(refundAccount);
                (0, invariant_1.default)(mainRefundCurrency.type === "CryptoCurrency", "This should be a cryptocurrency");
                // FIXME: invariant not triggering typescriptp type guard
                if (mainRefundCurrency.type !== "CryptoCurrency") {
                    throw new Error("This should be a cryptocurrency");
                }
                const refundAddressParameters = accountBridge.getSerializedAddressParameters(refundAccount, mainRefundCurrency.id);
                if (unsubscribed)
                    return;
                if (!refundAddressParameters) {
                    throw new Error(`Family not supported: ${mainRefundCurrency.family}`);
                }
                const { config: refundAddressConfig, signature: refundAddressConfigSignature } = await (0, __1.getCurrencyExchangeConfig)(refundCurrency);
                if (unsubscribed)
                    return;
                // NB Floating rates may change the original amountTo so we can pass an override
                // to properly render the amount on the device confirmation steps. Although changelly
                // made the calculation inside the binary payload, we still have to deal with it here
                // to not break their other clients.
                let amountExpectedTo;
                if (swapResult.binaryPayload) {
                    const decodePayload = await (0, hw_app_exchange_1.decodePayloadProtobuf)(swapResult.binaryPayload);
                    amountExpectedTo = new bignumber_js_1.BigNumber(decodePayload.amountToWallet.toString());
                    magnitudeAwareRate = transaction.amount && amountExpectedTo.dividedBy(transaction.amount);
                }
                let amountExpectedFrom;
                if (swapResult.binaryPayload) {
                    const decodePayload = await (0, hw_app_exchange_1.decodePayloadProtobuf)(swapResult.binaryPayload);
                    amountExpectedFrom = new bignumber_js_1.BigNumber(decodePayload.amountToProvider.toString());
                    if (data.amountFromInSmallestDenomination !== amountExpectedFrom.toNumber())
                        throw new Error("AmountFrom received from partner's payload mismatch user input");
                }
                o.next({
                    type: "init-swap-requested",
                    amountExpectedTo,
                    estimatedFees,
                });
                try {
                    await swap.checkRefundAddress(refundAddressConfig, refundAddressConfigSignature, refundAddressParameters);
                }
                catch (e) {
                    if (e instanceof errors_1.TransportStatusError && e.statusCode === 0x6a83) {
                        throw new errors_1.WrongDeviceForAccountRefund(undefined, {
                            accountName: (0, accountName_1.getDefaultAccountName)(refundAccount),
                        });
                    }
                    throw e;
                }
                if (unsubscribed)
                    return;
                ignoreTransportError = true;
                await swap.signCoinTransaction();
            }).catch(e => {
                if (ignoreTransportError)
                    return;
                if (e instanceof errors_1.TransportStatusError && e.statusCode === 0x6a84) {
                    throw new errors_2.TransactionRefusedOnDevice("", { step: "SIGN_COIN_TRANSACTION" });
                }
                throw e;
            });
            if (!swapId)
                return;
            (0, logs_1.log)("swap", "awaiting device disconnection");
            await (0, promise_1.delay)(3000);
            if (unsubscribed)
                return;
            o.next({
                type: "init-swap-result",
                initSwapResult: {
                    transaction,
                    swapId,
                    magnitudeAwareRate,
                },
            });
        };
        confirmSwap().then(() => {
            o.complete();
            unsubscribed = true;
        }, e => {
            o.next({
                type: "init-swap-error",
                error: e,
                swapId,
            });
            o.complete();
            unsubscribed = true;
        });
        return () => {
            unsubscribed = true;
        };
    });
};
exports.default = initSwap;
//# sourceMappingURL=initSwap.js.map