UNPKG

@ledgerhq/live-common

Version:
246 lines • 13.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const errors_1 = require("@ledgerhq/errors"); const hw_app_exchange_1 = require("@ledgerhq/hw-app-exchange"); const accountName_1 = require("@ledgerhq/live-wallet/accountName"); const logs_1 = require("@ledgerhq/logs"); const bignumber_js_1 = __importDefault(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 errors_2 = require("../../errors"); const exchange_1 = require("../../families/hedera/exchange"); const deviceAccess_1 = require("../../hw/deviceAccess"); const promise_1 = require("../../promise"); const error_1 = require("../error"); const providers_1 = require("../providers"); const index_1 = require("@ledgerhq/coin-framework/sanction/index"); const errors_3 = require("@ledgerhq/coin-framework/sanction/errors"); const currencies_1 = require("../../currencies"); const COMPLETE_EXCHANGE_LOG = "SWAP-CompleteExchange"; const completeExchange = (input) => { let { transaction } = input; // TODO build a tx from the data const { deviceId, deviceModelId, exchange, provider, binaryPayload, signature, rateType, exchangeType, } = input; const { fromAccount, fromParentAccount } = exchange; const { toAccount, toParentAccount } = exchange; return new rxjs_1.Observable(o => { let unsubscribed = false; let ignoreTransportError = false; let currentStep = "INIT"; const confirmExchange = async () => { if (deviceId === undefined) { throw new errors_1.DisconnectedDeviceDuringOperation(); } await (0, deviceAccess_1.withDevicePromise)(deviceId, async (transport) => { const providerConfig = await (0, providers_1.getSwapProvider)(provider); if (providerConfig.useInExchangeApp === false) { throw new Error(`Unsupported provider type ${providerConfig.type}`); } const exchange = (0, hw_app_exchange_1.createExchange)(transport, exchangeType, rateType, providerConfig.version); const refundAccount = (0, account_1.getMainAccount)(fromAccount, fromParentAccount); const payoutAccount = (0, account_1.getMainAccount)(toAccount, toParentAccount); const accountBridge = (0, bridge_1.getAccountBridge)(refundAccount); const payoutAccountBridge = (0, bridge_1.getAccountBridge)(payoutAccount); const mainPayoutCurrency = (0, account_1.getAccountCurrency)(payoutAccount); const payoutCurrency = (0, account_1.getAccountCurrency)(toAccount); const refundCurrency = (0, account_1.getAccountCurrency)(fromAccount); const mainRefundCurrency = (0, account_1.getAccountCurrency)(refundAccount); const sanctionedAddresses = []; for (const acc of [refundAccount, payoutAccount]) { const isSanctioned = await (0, index_1.isAddressSanctioned)(acc.currency, acc.freshAddress); if (isSanctioned) sanctionedAddresses.push(acc.freshAddress); } if (sanctionedAddresses.length > 0) { throw new errors_3.AddressesSanctionedError("AddressesSanctionedError", { addresses: sanctionedAddresses, }); } if (mainPayoutCurrency.type !== "CryptoCurrency") throw new Error("This should be a cryptocurrency"); if (mainRefundCurrency.type !== "CryptoCurrency") throw new Error("This should be a cryptocurrency"); // Thorswap ERC20 token exception hack: // - We remove subAccountId to prevent EVM calldata swap during prepareTransaction. // - Set amount to 0 to ensure correct handling of the transaction // (this is adjusted during prepareTransaction before signing the actual EVM transaction for tokens but we skip it). // - Since it's an ERC20 token transaction (not ETH), amount is set to 0 ETH // because no ETH is being sent, only tokens. // - This workaround can't be applied earlier in the flow as the amount is used for display purposes and checks. // We must set the amount to 0 at this stage to avoid issues during the transaction. // - This ensures proper handling of Thorswap-ERC20-specific transactions. if ((provider.toLocaleLowerCase() === "thorswap" || provider.toLocaleLowerCase() === "lifi") && transaction.subAccountId && transaction.family === "evm") { const transactionFixed = { ...transaction, subAccountId: undefined, amount: (0, bignumber_js_1.default)(0), }; transaction = await accountBridge.prepareTransaction(refundAccount, transactionFixed); } else { transaction = await accountBridge.prepareTransaction(refundAccount, transaction); } if (transaction.family === "bitcoin") { const transactionFixed = { ...transaction, rbf: true, }; transaction = await accountBridge.prepareTransaction(refundAccount, transactionFixed); } 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 currentStep = "SET_PARTNER_KEY"; await exchange.setPartnerKey((0, providers_1.convertToAppExchangePartnerKey)(providerConfig)); if (unsubscribed) return; currentStep = "CHECK_PARTNER"; await exchange.checkPartner(providerConfig.signature); if (unsubscribed) return; currentStep = "PROCESS_TRANSACTION"; const { payload, format } = exchange.transactionType === 3 /* ExchangeTypes.SwapNg */ ? { payload: Buffer.from("." + binaryPayload), format: "jws" } : { payload: Buffer.from(binaryPayload, "hex"), format: "raw" }; await exchange.processTransaction(payload, estimatedFees, format); if (unsubscribed) return; const goodSign = convertSignature(signature, exchange.transactionType); currentStep = "CHECK_TRANSACTION_SIGNATURE"; await exchange.checkTransactionSignature(goodSign); if (unsubscribed) return; // Hedera swap payload is filled with user account address, // but the device app requires the related public key for verification. // Since this key is stored on-chain, we use the TrustedService // to fetch a signed descriptor linking the address to its public key. const hederaCurrency = (0, currencies_1.getCryptoCurrencyById)("hedera"); let hederaAccount = null; if (payoutAccount.currency.family === hederaCurrency.family) { hederaAccount = payoutAccount; } else if (refundAccount.currency.family === hederaCurrency.family) { hederaAccount = refundAccount; } if (hederaAccount) { (0, invariant_1.default)(deviceModelId, "hedera: deviceModelId is not available"); await (0, exchange_1.handleHederaTrustedFlow)({ exchange, hederaAccount, deviceModelId }); if (unsubscribed) return; } const payoutAddressParameters = payoutAccountBridge.getSerializedAddressParameters(payoutAccount, mainPayoutCurrency.id); if (unsubscribed) return; if (!payoutAddressParameters) { throw new Error(`Family not supported: ${mainPayoutCurrency.family}`); } //-- CHECK_PAYOUT_ADDRESS const { config: payoutAddressConfig, signature: payoutAddressConfigSignature } = await (0, __1.getCurrencyExchangeConfig)(payoutCurrency); try { currentStep = "CHECK_PAYOUT_ADDRESS"; await exchange.validatePayoutOrAsset(payoutAddressConfig, payoutAddressConfigSignature, payoutAddressParameters); } catch (e) { if (e instanceof errors_1.TransportStatusError && e.statusCode === 0x6a83) { throw new errors_1.WrongDeviceForAccountPayout((0, hw_app_exchange_1.getExchangeErrorMessage)(e.statusCode, currentStep).errorMessage, { accountName: (0, accountName_1.getDefaultAccountName)(payoutAccount), }); } throw (0, error_1.convertTransportError)(currentStep, e); } o.next({ type: "complete-exchange-requested", estimatedFees: estimatedFees.toString(), }); // Swap specific checks to confirm the refund address is correct. if (unsubscribed) return; 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; try { currentStep = "CHECK_REFUND_ADDRESS"; await exchange.checkRefundAddress(refundAddressConfig, refundAddressConfigSignature, refundAddressParameters); (0, logs_1.log)(COMPLETE_EXCHANGE_LOG, "checkrefund address"); } catch (e) { if (e instanceof errors_1.TransportStatusError && e.statusCode === 0x6a83) { (0, logs_1.log)(COMPLETE_EXCHANGE_LOG, "transport error"); throw new errors_1.WrongDeviceForAccountRefund((0, hw_app_exchange_1.getExchangeErrorMessage)(e.statusCode, currentStep).errorMessage, { accountName: (0, accountName_1.getDefaultAccountName)(refundAccount), }); } throw (0, error_1.convertTransportError)(currentStep, e); } if (unsubscribed) return; ignoreTransportError = true; currentStep = "SIGN_COIN_TRANSACTION"; await exchange.signCoinTransaction(); }).catch(e => { if (ignoreTransportError) return; if (e instanceof errors_1.TransportStatusError && e.statusCode === 0x6a84) { throw new errors_2.TransactionRefusedOnDevice(); } throw (0, error_1.convertTransportError)(currentStep, e); }); await (0, promise_1.delay)(3000); if (unsubscribed) return; o.next({ type: "complete-exchange-result", completeExchangeResult: transaction, }); }; confirmExchange().then(() => { o.complete(); unsubscribed = true; }, e => { o.next({ type: "complete-exchange-error", error: e, }); o.complete(); unsubscribed = true; }); return () => { unsubscribed = true; }; }); }; function convertSignature(signature, exchangeType) { return exchangeType === 3 /* ExchangeTypes.SwapNg */ ? base64UrlDecode(signature) : (() => { const sig = secp256k1_1.secp256k1.Signature.fromCompact(Buffer.from(signature, "hex")); return Buffer.from(sig.toDERRawBytes()); })(); } function base64UrlDecode(base64Url) { // React Native Hermes engine does not support Buffer.from(signature, "base64url") const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); return Buffer.from(base64, "base64"); } exports.default = completeExchange; //# sourceMappingURL=completeExchange.js.map