From 5a3332f7f701ca31d6f0808575816891101450cb Mon Sep 17 00:00:00 2001 From: Thomas Brillard Date: Tue, 20 Jan 2026 16:33:37 +0100 Subject: [PATCH] feat(mobile): remove legacy swap form implementation Removes the legacy swap form UI and related swap logic from mobile app, including: - Removed legacy SwapFormNavigator and related screens - Removed swap reducer and actions - Cleaned up swap-related utilities and types - Removed unused provider icons and components - Updated navigation structure to use LiveApp swap flow - Cleaned up exchange swap logic in ledger-live-common --- .changeset/rotten-brooms-drive.md | 8 + apps/cli/src/commands-index.ts | 2 - apps/cli/src/commands/ptx/swap.ts | 299 ------------- .../components/DeviceAction/index.tsx | 33 +- .../renderer/components/debug/DebugMock.tsx | 58 --- .../tests/models/DeviceAction.ts | 57 --- .../__tests__/test-renderer.tsx | 2 - apps/ledger-live-mobile/e2e/bridge/types.ts | 4 - .../e2e/models/DeviceAction.ts | 25 -- .../e2e/specs/deeplinks.spec.ts | 5 - apps/ledger-live-mobile/package.json | 1 - .../src/actions/settings.ts | 4 - apps/ledger-live-mobile/src/actions/swap.ts | 15 - apps/ledger-live-mobile/src/actions/types.ts | 11 - .../src/components/DeviceAction/index.tsx | 9 +- .../src/components/ProviderIcon/index.tsx | 22 - .../src/components/ProviderIcon/styles.ts | 13 - .../RootNavigator/SwapFormNavigator.tsx | 63 --- .../RootNavigator/SwapNavigator.tsx | 137 ++---- .../RootNavigator/types/SwapFormNavigator.ts | 17 - .../RootNavigator/types/SwapNavigator.ts | 210 ++------- .../src/families/stacks/ScreenEditMemo.tsx | 2 - .../families/stellar/ScreenEditMemoValue.tsx | 2 - .../src/hooks/deviceActions.ts | 15 - .../src/navigation/tabNavigatorConfig.tsx | 18 - apps/ledger-live-mobile/src/reducers/index.ts | 2 - .../src/reducers/settings.ts | 17 - apps/ledger-live-mobile/src/reducers/swap.ts | 63 --- apps/ledger-live-mobile/src/reducers/types.ts | 13 - .../src/screens/Swap/Form/Banner.tsx | 30 -- .../src/screens/Swap/Form/Connect.tsx | 88 ---- .../src/screens/Swap/Form/EmptyState.tsx | 22 - .../src/screens/Swap/Form/Max.tsx | 51 --- .../screens/Swap/Form/Modal/Confirmation.tsx | 323 -------------- .../src/screens/Swap/Form/Modal/Terms.tsx | 55 --- .../src/screens/Swap/Form/Modal/index.tsx | 127 ------ .../src/screens/Swap/Form/Summary/Item.tsx | 30 -- .../src/screens/Swap/Form/Summary/index.tsx | 227 ---------- .../screens/Swap/Form/TxForm/AmountInput.tsx | 63 --- .../Swap/Form/TxForm/CurrencyValue.tsx | 38 -- .../src/screens/Swap/Form/TxForm/From.tsx | 158 ------- .../src/screens/Swap/Form/TxForm/Selector.tsx | 63 --- .../src/screens/Swap/Form/TxForm/To.tsx | 82 ---- .../src/screens/Swap/Form/TxForm/index.tsx | 36 -- .../src/screens/Swap/Form/index.tsx | 407 ------------------ .../screens/Swap/Form/useSelectedSwapRate.ts | 98 ----- .../screens/Swap/SubScreens/SelectAccount.tsx | 236 ---------- .../Swap/SubScreens/SelectCurrency.tsx | 110 ----- .../screens/Swap/SubScreens/SelectFees.tsx | 86 ---- .../Swap/SubScreens/SelectProvider.tsx | 145 ------- .../src/screens/Swap/SubScreens/index.ts | 4 - .../src/screens/Swap/index.ts | 2 - .../src/screens/Swap/types.ts | 26 -- .../src/screens/Swap/utils.ts | 29 -- libs/ledger-live-common/.unimportedrc.json | 1 - .../src/exchange/swap/getExchangeRates.ts | 185 -------- .../src/exchange/swap/index.ts | 10 - .../src/exchange/swap/initSwap.ts | 334 -------------- .../swap/maybeKeepTronAccountAlive.ts | 22 - .../maybeTezosAccountUnrevealedAccount.ts | 15 - .../exchange/swap/maybeTronEmptyAccount.ts | 15 - .../src/exchange/swap/mock.ts | 17 - .../src/exchange/swap/types.ts | 38 +- .../src/hw/actions/initSwap.ts | 197 --------- pnpm-lock.yaml | 23 - 65 files changed, 78 insertions(+), 4442 deletions(-) create mode 100644 .changeset/rotten-brooms-drive.md delete mode 100644 apps/cli/src/commands/ptx/swap.ts delete mode 100644 apps/ledger-live-mobile/src/actions/swap.ts delete mode 100644 apps/ledger-live-mobile/src/components/ProviderIcon/index.tsx delete mode 100644 apps/ledger-live-mobile/src/components/ProviderIcon/styles.ts delete mode 100644 apps/ledger-live-mobile/src/components/RootNavigator/SwapFormNavigator.tsx delete mode 100644 apps/ledger-live-mobile/src/components/RootNavigator/types/SwapFormNavigator.ts delete mode 100644 apps/ledger-live-mobile/src/navigation/tabNavigatorConfig.tsx delete mode 100644 apps/ledger-live-mobile/src/reducers/swap.ts delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Banner.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Connect.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/EmptyState.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Max.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Confirmation.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Terms.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Modal/index.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Summary/Item.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/Summary/index.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/AmountInput.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/CurrencyValue.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/From.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/Selector.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/To.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/index.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/index.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/Form/useSelectedSwapRate.ts delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectAccount.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectCurrency.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectFees.tsx delete mode 100644 apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectProvider.tsx delete mode 100644 libs/ledger-live-common/src/exchange/swap/getExchangeRates.ts delete mode 100644 libs/ledger-live-common/src/exchange/swap/initSwap.ts delete mode 100644 libs/ledger-live-common/src/exchange/swap/maybeKeepTronAccountAlive.ts delete mode 100644 libs/ledger-live-common/src/exchange/swap/maybeTezosAccountUnrevealedAccount.ts delete mode 100644 libs/ledger-live-common/src/exchange/swap/maybeTronEmptyAccount.ts delete mode 100644 libs/ledger-live-common/src/hw/actions/initSwap.ts diff --git a/.changeset/rotten-brooms-drive.md b/.changeset/rotten-brooms-drive.md new file mode 100644 index 00000000000..41fd2483f11 --- /dev/null +++ b/.changeset/rotten-brooms-drive.md @@ -0,0 +1,8 @@ +--- +"ledger-live-desktop": minor +"live-mobile": minor +"@ledgerhq/live-common": minor +"@ledgerhq/live-cli": minor +--- + +chore: removed swap legacy diff --git a/apps/cli/src/commands-index.ts b/apps/cli/src/commands-index.ts index a6f80e5d8b8..2a03765205c 100644 --- a/apps/cli/src/commands-index.ts +++ b/apps/cli/src/commands-index.ts @@ -57,7 +57,6 @@ import portfolio from "./commands/live/portfolio"; import synchronousOnboarding from "./commands/live/synchronousOnboarding"; import user from "./commands/live/user"; import version from "./commands/live/version"; -import swap from "./commands/ptx/swap"; export default { bot, @@ -119,5 +118,4 @@ export default { synchronousOnboarding, user, version, - swap, }; diff --git a/apps/cli/src/commands/ptx/swap.ts b/apps/cli/src/commands/ptx/swap.ts deleted file mode 100644 index 732505c26b0..00000000000 --- a/apps/cli/src/commands/ptx/swap.ts +++ /dev/null @@ -1,299 +0,0 @@ -/* eslint-disable no-console */ -import { first, tap, filter, map, take } from "rxjs/operators"; -import { - accountWithMandatoryTokens, - getAccountCurrency, - getMainAccount, -} from "@ledgerhq/live-common/account/index"; -import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets"; -import { getCryptoAssetsStore } from "@ledgerhq/cryptoassets/state"; -import { firstValueFrom, from } from "rxjs"; -import { BigNumber } from "bignumber.js"; -import commandLineArgs from "command-line-args"; -import { delay } from "@ledgerhq/live-common/promise"; -import { scan, scanCommonOpts } from "../../scan"; -import type { ScanCommonOpts } from "../../scan"; -import type { - ExchangeSwap, - ExchangeRate, - InitSwapResult, -} from "@ledgerhq/live-common/exchange/swap/types"; -import { initSwap, getExchangeRates } from "@ledgerhq/live-common/exchange/swap/index"; -import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; -import invariant from "invariant"; -import { Account, SignedOperation, TokenAccount } from "@ledgerhq/types-live"; - -export type SwapJobOpts = ScanCommonOpts & { - amount: string; - useAllAmount: boolean; - useFloat: boolean; - _unknown: any; // what is this? - deviceId: string; - tokenId: string; -}; - -const exec = async (opts: SwapJobOpts) => { - const { amount, useAllAmount, tokenId, useFloat, deviceId = "" } = opts; - invariant(amount || useAllAmount, `✖ amount in satoshis is needed or --useAllAmount `); - invariant(opts._unknown, `✖ second account information is missing`); - - //Remove suffix from arguments before passing them to sync. - const secondAccountOpts = commandLineArgs( - [ - ...scanCommonOpts, - { - name: "tokenId", - alias: "t", - type: String, - desc: "use a token account children of the account", - }, - ], - { - argv: opts._unknown.map((a: string, i: number) => (i % 2 ? a : a.replace("_2", ""))), - }, - ) as ScanCommonOpts & { tokenId: string }; - - console.log("• Open the source currency app"); - await delay(8000); - let fromParentAccount: Account | null = null; - let fromAccount: Account | TokenAccount | undefined = await firstValueFrom( - scan(opts).pipe(take(1)), - ); - invariant(fromAccount, `✖ No account found, is the right currency app open?`); - if (!fromAccount) { - throw new Error(`✖ No account found, is the right currency app open?`); - } - - //Are we asking for a token account? - if (tokenId) { - const token = await getCryptoAssetsStore().findTokenById(tokenId); - invariant(token, `✖ No token currency found with id ${tokenId}`); - if (!token) throw new Error(`✖ No token currency found with id ${tokenId}`); - const subAccounts = accountWithMandatoryTokens(fromAccount, [token]).subAccounts || []; - const subAccount = subAccounts.find(t => { - const currency = getAccountCurrency(t); - return tokenId === currency.id; - }); - // We have a token account, keep track of both now; - fromParentAccount = fromAccount; - fromAccount = subAccount; - invariant(fromAccount, `✖ No account found, is the right currency app open?`); - if (!fromAccount) { - throw new Error(`✖ No account found, is the right currency app open?`); - } - } - - if (fromParentAccount) { - console.log("\t:parentId:\t\t", fromParentAccount.id); - } - - console.log("\t:id:\t\t", fromAccount.id); - const formattedAmount = formatCurrencyUnit( - getAccountCurrency(fromAccount).units[0], - fromAccount.balance, - { - disableRounding: true, - alwaysShowSign: false, - showCode: true, - }, - ); - - console.log("\t:balance:\t", fromAccount.spendableBalance.toString(), ` [ ${formattedAmount} ]`); - - invariant(fromAccount.balance.gte(new BigNumber(amount)), `✖ Not enough balance`); - console.log("• Open the destination currency app"); - await delay(8000); - let toParentAccount: Account | null = null; - let toAccount: Account | TokenAccount | undefined = await firstValueFrom( - scan(secondAccountOpts).pipe(take(1)), - ); - - if (!toAccount) { - throw new Error(`✖ No account found`); - } - const { tokenId: tokenId2 } = secondAccountOpts; - - //Are we asking for a token account? - if (tokenId2) { - const token = await getCryptoAssetsStore().findTokenById(tokenId2); - if (!token) { - throw new Error(`✖ No token currency found with id ${tokenId2}`); - } - const subAccounts = accountWithMandatoryTokens(toAccount, [token]).subAccounts || []; - const subAccount = subAccounts.find(t => { - const currency = getAccountCurrency(t); - return tokenId2 === currency.id; - }); - // We have a token account, keep track of both now; - toParentAccount = toAccount; - toAccount = subAccount; - invariant(fromAccount, `✖ No account found`); - } - - invariant(toAccount, `✖ No account found`); - if (!toAccount) throw new Error(`✖ No account found`); - - if (toParentAccount) { - console.log("\t:parentId:\t\t", toParentAccount.id); - } - - console.log("\t:id:\t\t", toAccount.id); - const bridge = getAccountBridge(fromAccount, fromParentAccount); - let transaction = bridge.createTransaction(getMainAccount(fromAccount, fromParentAccount)); - transaction = bridge.updateTransaction(transaction, { - recipient: getAbandonSeedAddress( - getAccountCurrency(getMainAccount(fromAccount, fromParentAccount)).id, - ), - subAccountId: fromParentAccount ? fromAccount.id : undefined, - }); - - if (!useAllAmount) { - transaction = bridge.updateTransaction(transaction, { - amount: new BigNumber(amount), - }); - } else { - const amount = await bridge.estimateMaxSpendable({ - account: fromAccount, - parentAccount: fromParentAccount, - transaction, - }); - transaction = bridge.updateTransaction(transaction, { - amount, - }); - } - - transaction = await bridge.prepareTransaction( - getMainAccount(fromAccount, fromParentAccount), - transaction, - ); - const exchange: ExchangeSwap = { - fromAccount, - fromParentAccount, - fromCurrency: fromAccount.type === "TokenAccount" ? fromAccount.token : fromAccount.currency, - toAccount, - toParentAccount, - toCurrency: toAccount.type === "TokenAccount" ? toAccount.token : toAccount.currency, - }; - - const exchangeRates = await getExchangeRates({ exchange, transaction }); - - console.log({ exchangeRates }); - - const exchangeRate = exchangeRates.find(er => { - if (er.tradeMethod === (useFloat ? "float" : "fixed")) { - return true; - } - return false; - }); - - invariant(exchangeRate, `✖ No valid rate available`); - console.log(`Using first ${useFloat ? "float" : "fixed"} rate:\n`, exchangeRate); - console.log({ - transaction, - amount: transaction.amount.toString(), - }); - console.log("• Open the Exchange app"); - await delay(8000); - const initSwapResult = await firstValueFrom( - initSwap({ - exchange, - exchangeRate: exchangeRate as ExchangeRate, - transaction, - deviceId, - }).pipe( - tap((e: any) => { - switch (e.type) { - case "init-swap-requested": - console.log("• Confirm swap operation on your device"); - break; - - case "init-swap-error": - console.log(e); - invariant(false, "Something went wrong confirming the swap"); - // $FlowFixMe - break; - - case "init-swap-result": - console.log(e); - } - - if (e.type === "init-swap-requested") - console.log("• Confirm swap operation on your device"); - }), - filter((e: any) => e.type === "init-swap-result"), - map((e: any) => { - if (e.type === "init-swap-result") { - return e.initSwapResult; - } - }), - ), - ); - transaction = (initSwapResult as InitSwapResult).transaction; - console.log("Device app switch & silent signing"); - await delay(8000); - const mainFromAccount = getMainAccount(fromAccount, fromParentAccount); - const signedOperation = await firstValueFrom( - bridge - .signOperation({ - account: mainFromAccount, - deviceId, - transaction, - }) - .pipe( - tap(e => console.log(e)), - first((e: any) => e.type === "signed"), - map((e: any) => { - if (e.type === "signed") { - return e.signedOperation; - } - }), - ), - ); - console.log("Broadcasting"); - const operation = await bridge.broadcast({ - account: mainFromAccount, - signedOperation: signedOperation as SignedOperation, - }); - console.log({ - operation, - }); -}; - -export default { - description: "Perform an arbitrary swap between two currencies on the same seed", - args: [ - { - name: "mock", - alias: "m", - type: Boolean, - desc: "Whether or not to use the real backend or a mocked version", - }, - { - name: "amount", - alias: "a", - type: Number, - desc: "Amount in satoshi units to send", - }, - { - name: "useAllAmount", - alias: "u", - type: Boolean, - desc: "Attempt to send all using the emulated max amount calculation", - }, - { - name: "tokenId", - alias: "t", - type: String, - desc: "Use a token account children of the account", - }, - { - name: "useFloat", - alias: "f", - type: Boolean, - desc: "Use first floating rate returned. Defaults to false.", - }, - ...scanCommonOpts, - ], - job: (opts: SwapJobOpts) => from(exec(opts)), -}; diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx index 6fdfe8cd26b..ec3d60ee7b6 100644 --- a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.tsx @@ -69,11 +69,7 @@ import { DeviceInfo, DeviceModelInfo, } from "@ledgerhq/types-live"; -import { - ExchangeRate, - ExchangeSwap, - InitSwapResult, -} from "@ledgerhq/live-common/exchange/swap/types"; +import { ExchangeRate, ExchangeSwap } from "@ledgerhq/live-common/exchange/swap/types"; import { Transaction, TransactionStatus } from "@ledgerhq/live-common/generated/types"; import { AppAndVersion } from "@ledgerhq/live-common/hw/connectApp"; import { Device } from "@ledgerhq/types-devices"; @@ -137,9 +133,6 @@ type States = PartialNullable<{ deviceStreamingProgress: number; displayUpgradeWarning: boolean; passWarning: () => void; - initSwapRequested: boolean; - initSwapError: Error; - initSwapResult: InitSwapResult | null; installingLanguage: boolean; languageInstallationRequested: boolean; signMessageRequested: AnyMessage; @@ -250,9 +243,6 @@ export const DeviceActionDefaultRendering = ({ transactionChecksOptIn, displayUpgradeWarning, passWarning, - initSwapRequested, - initSwapError, - initSwapResult, completeExchangeStarted, completeExchangeResult, completeExchangeError, @@ -512,27 +502,6 @@ export const DeviceActionDefaultRendering = ({ } } - if (initSwapRequested && !initSwapResult && !initSwapError) { - const { transaction, exchange, exchangeRate } = request as { - transaction: Transaction; - exchange: ExchangeSwap; - exchangeRate: ExchangeRate; - }; - const { amountExpectedTo, estimatedFees } = hookState; - return renderSwapDeviceConfirmation({ - modelId, - type, - transaction, - exchangeRate, - exchange, - amountExpectedTo: amountExpectedTo ?? undefined, - estimatedFees: estimatedFees ?? undefined, - swapDefaultTrack, - stateSettings, - walletState, - }); - } - if (allowOpeningRequestedWording || requestOpenApp) { // requestOpenApp for Nano S 1.3.1 (need to ask user to open the app.) const wording = allowOpeningRequestedWording || requestOpenApp || ""; diff --git a/apps/ledger-live-desktop/src/renderer/components/debug/DebugMock.tsx b/apps/ledger-live-desktop/src/renderer/components/debug/DebugMock.tsx index 18ce6b8b5b6..d65ac033bc0 100644 --- a/apps/ledger-live-desktop/src/renderer/components/debug/DebugMock.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/debug/DebugMock.tsx @@ -181,64 +181,6 @@ const swapEvents = [ ), }, }, - { - name: "init-swap-requested", - event: { - type: "init-swap-requested", - }, - }, - { - name: "init-swap-error", - event: { - type: "init-swap-error", - error: { - name: "SwapGenericAPIError", - }, - }, - }, - { - name: "init-swap-result", - event: { - type: "init-swap-result", - initSwapResult: { - transaction: fromTransactionRaw({ - family: "bitcoin", - recipient: "1Cz2ZXb6Y6AacXJTpo4RBjQMLEmscuxD8e", - amount: "1", - feePerByte: "1", - networkInfo: { - family: "bitcoin", - feeItems: { - items: [ - { - key: "0", - speed: "high", - feePerByte: "3", - }, - { - key: "1", - speed: "standard", - feePerByte: "2", - }, - { - key: "2", - speed: "low", - feePerByte: "1", - }, - ], - defaultFeePerByte: "1", - }, - }, - rbf: false, - utxoStrategy: { - strategy: 0, - excludeUTXOs: [], - }, - }), - swapId: "12345", - }, - }, - }, ]; const localizationEvents = [ { diff --git a/apps/ledger-live-desktop/tests/models/DeviceAction.ts b/apps/ledger-live-desktop/tests/models/DeviceAction.ts index 090add5206e..314971da400 100644 --- a/apps/ledger-live-desktop/tests/models/DeviceAction.ts +++ b/apps/ledger-live-desktop/tests/models/DeviceAction.ts @@ -252,63 +252,6 @@ export class DeviceAction { }); } - async initiateSwap() { - await this.page.evaluate(() => window.mock.events.mockDeviceEvent({ type: "opened" })); - await this.page.waitForTimeout(500); - // Keeping the same subject because it's too close and it's failing and I don't want to cry. - // await this.page.evaluate(() => - // window.mock.events.mockDeviceEvent({ type: "complete" }), - // ); - await this.page.waitForTimeout(2000); - await this.page.evaluate(() => - window.mock.events.mockDeviceEvent({ type: "init-swap-requested" }), - ); - - await this.loader.waitFor({ state: "detached" }); - await this.swapSummary.waitFor({ state: "visible" }); - } - - async confirmSwap() { - await this.page.evaluate(() => { - const mock = window.mock; - const transaction = mock.fromTransactionRaw({ - family: "bitcoin", - recipient: "1Cz2ZXb6Y6AacXJTpo4RBjQMLEmscuxD8e", - amount: "12", - feePerByte: "1", - networkInfo: { - family: "bitcoin", - feeItems: { - items: [ - { key: "0", speed: "high", feePerByte: "3" }, - { key: "1", speed: "standard", feePerByte: "2" }, - { key: "2", speed: "low", feePerByte: "1" }, - ], - defaultFeePerByte: "1", - }, - }, - rbf: false, - utxoStrategy: { - strategy: 0, - excludeUTXOs: [], - }, - }); - mock.events.mockDeviceEvent( - { - type: "init-swap-result", - initSwapResult: { - transaction, - swapId: "12345", - magnitudeAwareRate: 1.397e11, - }, - }, - { - type: "complete", - }, - ); - }); - } - async silentSign() { await this.page.evaluate(() => { window.mock.events.mockDeviceEvent({ type: "opened" }, { type: "complete" }); diff --git a/apps/ledger-live-mobile/__tests__/test-renderer.tsx b/apps/ledger-live-mobile/__tests__/test-renderer.tsx index 1cb793df983..3209369fd72 100644 --- a/apps/ledger-live-mobile/__tests__/test-renderer.tsx +++ b/apps/ledger-live-mobile/__tests__/test-renderer.tsx @@ -37,7 +37,6 @@ import { INITIAL_STATE as PROTECT_INITIAL_STATE } from "~/reducers/protect"; import { INITIAL_STATE as RATINGS_INITIAL_STATE } from "~/reducers/ratings"; import { INITIAL_STATE as RECEIVE_OPTIONS_DRAWER_INITIAL_STATE } from "~/reducers/receiveOptionsDrawer"; import { INITIAL_STATE as SETTINGS_INITIAL_STATE } from "~/reducers/settings"; -import { INITIAL_STATE as SWAP_INITIAL_STATE } from "~/reducers/swap"; import { INITIAL_STATE as TOASTS_INITIAL_STATE } from "~/reducers/toast"; import { State } from "~/reducers/types"; import { INITIAL_STATE as WALLET_CONNECT_INITIAL_STATE } from "~/reducers/walletconnect"; @@ -66,7 +65,6 @@ const INITIAL_STATE: State = { protect: PROTECT_INITIAL_STATE, ratings: RATINGS_INITIAL_STATE, settings: SETTINGS_INITIAL_STATE, - swap: SWAP_INITIAL_STATE, toasts: TOASTS_INITIAL_STATE, trustchain: TRUSTCHAIN_INITIAL_STATE, wallet: WALLET_INITIAL_STATE, diff --git a/apps/ledger-live-mobile/e2e/bridge/types.ts b/apps/ledger-live-mobile/e2e/bridge/types.ts index 892f8688628..d56c5e8ec35 100644 --- a/apps/ledger-live-mobile/e2e/bridge/types.ts +++ b/apps/ledger-live-mobile/e2e/bridge/types.ts @@ -9,7 +9,6 @@ import { ConnectAppEvent } from "@ledgerhq/live-common/hw/connectApp"; import { ConnectManagerEvent } from "@ledgerhq/live-common/hw/connectManager"; import { InstallLanguageEvent } from "@ledgerhq/live-common/hw/installLanguage"; import { LoadImageEvent } from "@ledgerhq/live-common/hw/customLockScreenLoad"; -import { SwapRequestEvent } from "@ledgerhq/live-common/exchange/swap/types"; import { FetchImageEvent } from "@ledgerhq/live-common/hw/customLockScreenFetch"; import { ExchangeRequestEvent } from "@ledgerhq/live-common/hw/actions/startExchange"; import { CompleteExchangeRequestEvent } from "@ledgerhq/live-common/exchange/platform/types"; @@ -88,7 +87,6 @@ export type MockDeviceEvent = | LoadImageEvent | FetchImageEvent | ExchangeRequestEvent - | SwapRequestEvent | RemoveImageEvent | RenameDeviceEvent | CompleteExchangeRequestEvent @@ -100,8 +98,6 @@ export const mockDeviceEventSubject = new Subject(); // these adaptor will filter the event type to satisfy typescript (workaround), it works because underlying exec usage will ignore unknown event type export const connectAppExecMock = (): Observable => mockDeviceEventSubject as Observable; -export const initSwapExecMock = (): Observable => - mockDeviceEventSubject as Observable; export const startExchangeExecMock = (): Observable => mockDeviceEventSubject as Observable; export const connectManagerExecMock = (): Observable => diff --git a/apps/ledger-live-mobile/e2e/models/DeviceAction.ts b/apps/ledger-live-mobile/e2e/models/DeviceAction.ts index b576a589da0..7776f74c584 100644 --- a/apps/ledger-live-mobile/e2e/models/DeviceAction.ts +++ b/apps/ledger-live-mobile/e2e/models/DeviceAction.ts @@ -1,4 +1,3 @@ -import BigNumber from "bignumber.js"; import { DeviceModelId } from "@ledgerhq/types-devices"; import { deviceInfo155 as deviceInfo, @@ -8,8 +7,6 @@ import { import { AppOp } from "@ledgerhq/live-common/apps/types"; import { AppType, DeviceInfo } from "@ledgerhq/types-live/lib/manager"; import { Device } from "@ledgerhq/live-common/hw/actions/types"; -import { Transaction } from "@ledgerhq/live-common/generated/types"; -import { delay } from "../helpers/commonHelpers"; import { mockDeviceEvent } from "../bridge/server"; import { DeviceLike } from "~/reducers/types"; @@ -203,28 +200,6 @@ export default class DeviceAction { await mockDeviceEvent({ type: "imageLoaded", imageSize, imageHash }); } - async initiateSwap(estimatedFees: BigNumber) { - await mockDeviceEvent({ type: "opened" }); - await delay(2000); // enough time to allow the UI to switch from one action to another - await mockDeviceEvent({ type: "init-swap-requested", estimatedFees }); - } - - async confirmSwap(transaction: Transaction) { - await mockDeviceEvent( - { - type: "init-swap-result", - initSwapResult: { - transaction, - swapId: "12345", - magnitudeAwareRate: new BigNumber(50000), - }, - }, - { - type: "complete", - }, - ); - } - async silentSign() { await this.waitForSpinner(); await mockDeviceEvent({ type: "opened" }, { type: "complete" }); diff --git a/apps/ledger-live-mobile/e2e/specs/deeplinks.spec.ts b/apps/ledger-live-mobile/e2e/specs/deeplinks.spec.ts index 08e7917d8b0..b0e0e929c80 100644 --- a/apps/ledger-live-mobile/e2e/specs/deeplinks.spec.ts +++ b/apps/ledger-live-mobile/e2e/specs/deeplinks.spec.ts @@ -56,11 +56,6 @@ describe("DeepLinks Tests", () => { await app.discover.expectApp(randomLiveApp); }); - it("should open Swap Form page", async () => { - await app.swap.openViaDeeplink(); - await app.swap.expectSwapPage(); - }); - it("should open Market Detail page for Bitcoin", async () => { await app.market.openViaDeeplink("bitcoin"); await app.market.expectMarketDetailPage(); diff --git a/apps/ledger-live-mobile/package.json b/apps/ledger-live-mobile/package.json index eff97b6cbfe..f2459ba39f7 100644 --- a/apps/ledger-live-mobile/package.json +++ b/apps/ledger-live-mobile/package.json @@ -187,7 +187,6 @@ "react-native-haptic-feedback": "2.3.3", "react-native-image-crop-tools": "1.6.4", "react-native-image-picker": "8.2.0", - "react-native-keyboard-aware-scroll-view": "0.9.5", "react-native-keychain": "10.0.0", "react-native-level-fs": "3.0.1", "react-native-linear-gradient": "2.8.3", diff --git a/apps/ledger-live-mobile/src/actions/settings.ts b/apps/ledger-live-mobile/src/actions/settings.ts index f84fda4d75c..04e33dc4dd1 100755 --- a/apps/ledger-live-mobile/src/actions/settings.ts +++ b/apps/ledger-live-mobile/src/actions/settings.ts @@ -5,7 +5,6 @@ import { useSelector, useDispatch } from "~/context/hooks"; import type { PortfolioRange } from "@ledgerhq/types-live"; import { selectedTimeRangeSelector } from "../reducers/settings"; import { - SettingsAcceptSwapProviderPayload, SettingsBlacklistTokenPayload, DangerouslyOverrideStatePayload, SettingsDismissBannerPayload, @@ -168,9 +167,6 @@ export const setLocale = createAction( SettingsActionTypes.SETTINGS_SET_LOCALE, ); -export const swapAcceptProvider = createAction( - SettingsActionTypes.ACCEPT_SWAP_PROVIDER, -); export const setLastSeenDeviceInfo = createAction( SettingsActionTypes.LAST_SEEN_DEVICE_INFO, ); diff --git a/apps/ledger-live-mobile/src/actions/swap.ts b/apps/ledger-live-mobile/src/actions/swap.ts deleted file mode 100644 index 93a8a5590e3..00000000000 --- a/apps/ledger-live-mobile/src/actions/swap.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createAction } from "redux-actions"; -import { Transaction } from "@ledgerhq/live-common/generated/types"; -import { ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; -import { State } from "../reducers/types"; - -/* ACTIONS */ - -export const updateTransactionAction = createAction( - "SWAP/UPDATE_TRANSACTION", -); -export const updateRateAction = createAction("SWAP/UPDATE_RATE"); - -/* SELECTORS */ -export const rateSelector = (state: State) => state.swap.exchangeRate; -export const rateExpirationSelector = (state: State) => state.swap.exchangeRateExpiration; diff --git a/apps/ledger-live-mobile/src/actions/types.ts b/apps/ledger-live-mobile/src/actions/types.ts index 6af89541dc0..28438c04db9 100644 --- a/apps/ledger-live-mobile/src/actions/types.ts +++ b/apps/ledger-live-mobile/src/actions/types.ts @@ -9,8 +9,6 @@ import type { FeatureId, } from "@ledgerhq/types-live"; import type { Payload as PostOnboardingPayload } from "@ledgerhq/live-common/postOnboarding/reducer"; -import type { Transaction } from "@ledgerhq/live-common/generated/types"; -import type { ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; import type { DeviceModelId } from "@ledgerhq/types-devices"; import type { AppState, @@ -31,7 +29,6 @@ import type { MarketState, LargeMoverState, InViewState, - SwapStateType, } from "../reducers/types"; import type { Unpacked } from "../types/helpers"; import type { HandlersPayloads } from "@ledgerhq/live-wallet/store"; @@ -273,7 +270,6 @@ export enum SettingsActionTypes { SETTINGS_SET_LANGUAGE = "SETTINGS_SET_LANGUAGE", SETTINGS_SET_LOCALE = "SETTINGS_SET_LOCALE", SETTINGS_SET_DATE_FORMAT = "SETTINGS_SET_DATE_FORMAT", - ACCEPT_SWAP_PROVIDER = "ACCEPT_SWAP_PROVIDER", LAST_SEEN_DEVICE_INFO = "LAST_SEEN_DEVICE_INFO", LAST_SEEN_DEVICE_LANGUAGE_ID = "LAST_SEEN_DEVICE_LANGUAGE_ID", SET_KNOWN_DEVICE_MODEL_IDS = "SET_KNOWN_DEVICE_MODEL_IDS", @@ -478,12 +474,6 @@ export enum SwapActionTypes { DANGEROUSLY_OVERRIDE_STATE = "DANGEROUSLY_OVERRIDE_STATE", } -export type UpdateProvidersPayload = SwapStateType["providers"]; -export type UpdateTransactionPayload = Transaction | undefined; -export type UpdateRatePayload = ExchangeRate | undefined; - -export type SwapPayload = UpdateTransactionPayload | UpdateRatePayload; - // === EARN ACTIONS == export enum EarnActionTypes { EARN_INFO_MODAL = "EARN_INFO_MODAL", @@ -599,7 +589,6 @@ export type ActionsPayload = | Action | Action | Action - | Action | Action | Action | Action diff --git a/apps/ledger-live-mobile/src/components/DeviceAction/index.tsx b/apps/ledger-live-mobile/src/components/DeviceAction/index.tsx index faf82ce3f10..5eefd875051 100644 --- a/apps/ledger-live-mobile/src/components/DeviceAction/index.tsx +++ b/apps/ledger-live-mobile/src/components/DeviceAction/index.tsx @@ -10,11 +10,7 @@ import { ImageDoesNotExistOnDevice, NoSuchAppOnProvider, } from "@ledgerhq/live-common/errors"; -import { - ExchangeRate, - ExchangeSwap, - InitSwapResult, -} from "@ledgerhq/live-common/exchange/swap/types"; +import { ExchangeRate, ExchangeSwap } from "@ledgerhq/live-common/exchange/swap/types"; import { Transaction } from "@ledgerhq/live-common/generated/types"; import type { AppRequest } from "@ledgerhq/live-common/hw/actions/app"; import type { Action, Device } from "@ledgerhq/live-common/hw/actions/types"; @@ -111,9 +107,6 @@ type Status = PartialNullable<{ deviceStreamingProgress: number; displayUpgradeWarning: boolean; passWarning: () => void; - initSwapRequested: boolean; - initSwapError: Error; - initSwapResult: InitSwapResult | null; installingLanguage: boolean; languageInstallationRequested: boolean; imageRemoveRequested: boolean; diff --git a/apps/ledger-live-mobile/src/components/ProviderIcon/index.tsx b/apps/ledger-live-mobile/src/components/ProviderIcon/index.tsx deleted file mode 100644 index 2cad9967f91..00000000000 --- a/apps/ledger-live-mobile/src/components/ProviderIcon/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { getProviderIconUrl } from "@ledgerhq/live-common/icons/providers/providers"; -import { ProviderIconSize } from "@ledgerhq/live-common/icons/providers/sizes"; -import * as Styles from "./styles"; -import { SvgUri } from "react-native-svg"; - -export type Props = { - name: string; - size?: ProviderIconSize; - boxed?: boolean; -}; - -const ProviderIcon = ({ name, size = "S", boxed = true }: Props): React.JSX.Element | null => { - const iconUrl = getProviderIconUrl({ boxed, name }); - return ( - - - - ); -}; - -export default ProviderIcon; diff --git a/apps/ledger-live-mobile/src/components/ProviderIcon/styles.ts b/apps/ledger-live-mobile/src/components/ProviderIcon/styles.ts deleted file mode 100644 index d9721b82ad1..00000000000 --- a/apps/ledger-live-mobile/src/components/ProviderIcon/styles.ts +++ /dev/null @@ -1,13 +0,0 @@ -import styled from "styled-components/native"; -import { ProviderIconSize, ProviderIconSizes } from "@ledgerhq/live-common/icons/providers/sizes"; -import { View } from "react-native"; - -type StyledIconProps = { - size: ProviderIconSize; -}; - -export const Contianer = styled(View)` - border-radius: 8px; - width: ${({ size }) => ProviderIconSizes[size]}px; - height: ${({ size }) => ProviderIconSizes[size]}px; -`; diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/SwapFormNavigator.tsx b/apps/ledger-live-mobile/src/components/RootNavigator/SwapFormNavigator.tsx deleted file mode 100644 index 20b56aef017..00000000000 --- a/apps/ledger-live-mobile/src/components/RootNavigator/SwapFormNavigator.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Text } from "@ledgerhq/native-ui"; -import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs"; -import React, { useMemo } from "react"; -import { useTranslation } from "~/context/Locale"; -import { useTheme } from "styled-components/native"; -import { ScreenName } from "~/const"; -import { getLineTabNavigatorConfig } from "~/navigation/tabNavigatorConfig"; -import { SwapForm } from "~/screens/Swap"; -import History from "~/screens/Swap/History"; -import { SwapNavigatorParamList } from "../RootNavigator/types/SwapNavigator"; -import type { StackNavigatorProps } from "../RootNavigator/types/helpers"; -import { SwapFormNavigatorParamList } from "./types/SwapFormNavigator"; - -type TabLabelProps = { - focused: boolean; - color: string; -}; - -const Tab = createMaterialTopTabNavigator(); - -export default function SwapFormNavigator({ - route: { params }, -}: StackNavigatorProps) { - const { t } = useTranslation(); - const { colors } = useTheme(); - const tabNavigationConfig = useMemo(() => getLineTabNavigatorConfig(colors), [colors]); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const initialParams = params as any; - - return ( - - ( - - {t("transfer.swap.form.tab")} - - ), - tabBarButtonTestID: "swap-form-tab", - }} - initialParams={initialParams} - /> - - ( - - {t("transfer.swap.history.tab")} - - ), - tabBarButtonTestID: "swap-history-tab", - }} - /> - - ); -} diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/SwapNavigator.tsx b/apps/ledger-live-mobile/src/components/RootNavigator/SwapNavigator.tsx index 4d939e0ae17..61c2ee55738 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/SwapNavigator.tsx +++ b/apps/ledger-live-mobile/src/components/RootNavigator/SwapNavigator.tsx @@ -1,4 +1,3 @@ -import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; import { Flex, Icons } from "@ledgerhq/native-ui"; import { useNavigation } from "@react-navigation/core"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; @@ -12,25 +11,14 @@ import { useTrack } from "~/analytics"; import { NavigatorName, ScreenName } from "~/const"; import { useNoNanoBuyNanoWallScreenOptions } from "~/context/NoNanoBuyNanoWall"; import { getStackNavigatorConfig } from "~/navigation/navigatorConfig"; -import { - OperationDetails, - PendingOperation, - SelectFees, - SelectProvider, - SwapLoading, -} from "~/screens/Swap/index"; +import { OperationDetails, PendingOperation, SwapLoading } from "~/screens/Swap/index"; import { SwapLiveApp } from "~/screens/Swap/LiveApp"; import { SWAP_VERSION } from "~/screens/Swap/utils"; -import StepHeader from "../StepHeader"; -import SwapFormNavigator from "./SwapFormNavigator"; import { BaseNavigatorStackParamList } from "./types/BaseNavigator"; import { StackNavigatorNavigation, StackNavigatorProps } from "./types/helpers"; -import { SwapFormNavigatorParamList } from "./types/SwapFormNavigator"; import { SwapNavigatorParamList } from "./types/SwapNavigator"; import { NavigationHeaderBackButton } from "../NavigationHeaderBackButton"; import SwapCustomError from "~/screens/Swap/SubScreens/SwapCustomError"; -import { SelectCurrency } from "~/screens/Swap/SubScreens/SelectCurrency"; -import { SelectAccount } from "~/screens/Swap/SubScreens/SelectAccount"; // Constants for tracking sources const TRACKING_SOURCES = { @@ -55,7 +43,7 @@ export default function SwapNavigator( const stackNavigationConfig = useMemo(() => getStackNavigatorConfig(colors, true), [colors]); const noNanoBuyNanoWallScreenOptions = useNoNanoBuyNanoWallScreenOptions(); const track = useTrack(); - const navigation = useNavigation>(); + const navigation = useNavigation>(); const goToSwapHistory = useCallback(() => { track("button_clicked", { @@ -99,84 +87,34 @@ export default function SwapNavigator( } }, [trackButtonClick, navigation]); - const ptxSwapLiveAppMobile = useFeature("ptxSwapLiveAppMobile"); - - const options = useMemo(() => { - return !ptxSwapLiveAppMobile?.enabled - ? { - ...("options" in noNanoBuyNanoWallScreenOptions - ? noNanoBuyNanoWallScreenOptions.options - : {}), - title: t("transfer.swap2.form.title"), - headerLeft: (): React.JSX.Element | null => null, - } - : { - ...("options" in noNanoBuyNanoWallScreenOptions - ? noNanoBuyNanoWallScreenOptions.options - : {}), - headerTitle: t("transfer.swap2.form.title"), - headerLeft: () => , - - headerRight: () => ( - - - - - - ), - }; - }, [goToSwapHistory, noNanoBuyNanoWallScreenOptions, ptxSwapLiveAppMobile?.enabled, t]); + const options = useMemo( + () => ({ + ...("options" in noNanoBuyNanoWallScreenOptions + ? noNanoBuyNanoWallScreenOptions.options + : {}), + headerTitle: t("transfer.swap2.form.title"), + headerLeft: () => , + headerRight: () => ( + + + + + + ), + }), + [goToSwapHistory, noNanoBuyNanoWallScreenOptions, t], + ); return ( } /> - ({ - headerTitle: () => , - headerRight: () => null, - })} - /> - - , - headerRight: () => null, - }} - /> - - , - headerRight: () => null, - }} - /> - - , - headerRight: () => null, - }} - /> - - ptxSwapLiveAppMobile?.enabled - ? { - headerTitle: t("transfer.swap2.history.title"), - headerLeft: () => , - headerRight: () => null, - } - : { - headerTitle: t("transfer.swap.title"), - headerLeft: route.params?.fromPendingOperation ? () => null : undefined, - } - } + options={{ + headerTitle: t("transfer.swap2.history.title"), + headerLeft: () => , + headerRight: () => null, + }} /> - {ptxSwapLiveAppMobile?.enabled ? ( - null, - }} - /> - ) : null} + null, + }} + /> ); } diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/SwapFormNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/SwapFormNavigator.ts deleted file mode 100644 index 01357a57f17..00000000000 --- a/apps/ledger-live-mobile/src/components/RootNavigator/types/SwapFormNavigator.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ScreenName } from "~/const"; -import type { - DefaultAccountSwapParamList, - DetailsSwapParamList, - SwapPendingOperation, - SwapSelectCurrency, -} from "../../../screens/Swap/types"; - -export type SwapFormNavigatorParamList = { - [ScreenName.SwapForm]: - | DetailsSwapParamList - | DefaultAccountSwapParamList - | SwapSelectCurrency - | SwapPendingOperation; - [ScreenName.SwapHistory]: undefined; - [ScreenName.SwapLiveApp]: undefined; -}; diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/SwapNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/SwapNavigator.ts index d7c41d9cd8a..58de3f61428 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/types/SwapNavigator.ts +++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/SwapNavigator.ts @@ -1,12 +1,5 @@ import type { Transaction as EvmTransaction, GasOptions } from "@ledgerhq/coin-evm/types/index"; -import type { NavigatorScreenParams } from "@react-navigation/core"; -import { - ExchangeRate, - SwapDataType, - SwapLiveError, -} from "@ledgerhq/live-common/exchange/swap/types"; -import { Transaction } from "@ledgerhq/live-common/generated/types"; -import type { SwapFormNavigatorParamList } from "./SwapFormNavigator"; +import { SwapLiveError } from "@ledgerhq/live-common/exchange/swap/types"; import type { AlgorandAccount, @@ -41,58 +34,23 @@ import type { Transaction as KaspaTransaction, TransactionStatus as KaspaTransactionStatus, } from "@ledgerhq/live-common/families/kaspa/types"; -import { Account, Operation } from "@ledgerhq/types-live"; +import { Account } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; import { ScreenName } from "~/const"; -import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; import type { DefaultAccountSwapParamList, DetailsSwapParamList, SwapOperationDetails, SwapPendingOperation, SwapSelectCurrency, - Target, } from "~/screens/Swap/types"; export type SwapNavigatorParamList = { [ScreenName.SwapTab]: - | NavigatorScreenParams | DetailsSwapParamList | DefaultAccountSwapParamList | SwapSelectCurrency | SwapPendingOperation; - [ScreenName.SwapSelectAccount]: { - target: Target; - provider?: string; - swap: SwapDataType; - selectableCurrencyIds: string[]; - selectedCurrency?: CryptoCurrency | TokenCurrency; - }; - [ScreenName.SwapSelectCurrency]: SwapSelectCurrency; - [ScreenName.SwapSelectProvider]: { - provider?: string; - swap: SwapDataType; - selectedRate: ExchangeRate | undefined; - }; - [ScreenName.SwapSelectFees]: { - accountId?: string; - parentAccountId?: string; - swap: SwapDataType; - rate?: ExchangeRate; - provider?: string; - transaction?: Transaction | null; - overrideAmountLabel?: string; - hideTotal?: boolean; - operation?: Operation; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; - }; [ScreenName.SwapHistory]: undefined; [ScreenName.SwapPendingOperation]: SwapPendingOperation; [ScreenName.SwapOperationDetails]: { @@ -105,28 +63,16 @@ export type SwapNavigatorParamList = { account: AlgorandAccount; transaction: AlgorandTransaction; status?: AlgorandTransactionStatus; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.BitcoinEditCustomFees]: { accountId: string; parentId?: string; transaction: BitcoinTransaction; status?: BitcoinTransactionStatus; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; satPerByte?: BigNumber | null; setSatPerByte?: (_: BigNumber) => void; }; @@ -135,60 +81,32 @@ export type SwapNavigatorParamList = { parentId?: string; account: CardanoAccount; transaction: CardanoTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.EvmCustomFees]: { accountId: string; parentId?: string; transaction: EvmTransaction; gasOptions?: GasOptions; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.EvmEditGasLimit]: { accountId: string; setGasLimit: (_: BigNumber) => void; gasLimit?: BigNumber | null; transaction: EvmTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.KaspaEditCustomFees]: { accountId: string; parentId?: string; transaction: KaspaTransaction; status?: KaspaTransactionStatus; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; sompiPerByte?: BigNumber | null; setSompiPerByte?: (_: BigNumber) => void; }; @@ -198,82 +116,46 @@ export type SwapNavigatorParamList = { account: Account; transaction: StellarTransaction; memoType?: string; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; }; [ScreenName.StellarEditCustomFees]: { accountId: string; parentId?: string; transaction: StellarTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.CosmosFamilyEditMemo]: { accountId: string; parentId?: string; account: CosmosAccount; transaction: CosmosTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.HederaEditMemo]: { accountId: string; parentId?: string; account: Account; transaction: HederaTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.XrpEditTag]: { accountId: string; parentId?: string; transaction: RippleTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.SolanaEditMemo]: { accountId: string; parentId?: string; account: SolanaAccount; transaction: SolanaTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.StellarEditMemoType]: { accountId: string; @@ -281,14 +163,8 @@ export type SwapNavigatorParamList = { account: Account; transaction: StellarTransaction; memoType?: string; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.InternetComputerEditMemo]: { accountId: string; @@ -311,42 +187,24 @@ export type SwapNavigatorParamList = { account: Account; transaction: StacksTransaction; memoType?: string; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.CasperEditTransferId]: { accountId: string; account: Account; parentId?: string; transaction: CasperTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.TonEditComment]: { accountId: string; account: Account; parentId?: string; transaction: TonTransaction; - currentNavigation: - | ScreenName.SignTransactionSummary - | ScreenName.SendSummary - | ScreenName.SwapForm; - nextNavigation: - | ScreenName.SignTransactionSelectDevice - | ScreenName.SendSelectDevice - | ScreenName.SwapForm; + currentNavigation: ScreenName.SignTransactionSummary | ScreenName.SendSummary; + nextNavigation: ScreenName.SignTransactionSelectDevice | ScreenName.SendSelectDevice; }; [ScreenName.SwapCustomError]: { error?: SwapLiveError | Error; diff --git a/apps/ledger-live-mobile/src/families/stacks/ScreenEditMemo.tsx b/apps/ledger-live-mobile/src/families/stacks/ScreenEditMemo.tsx index 608ad5d5c0a..4f221760804 100644 --- a/apps/ledger-live-mobile/src/families/stacks/ScreenEditMemo.tsx +++ b/apps/ledger-live-mobile/src/families/stacks/ScreenEditMemo.tsx @@ -13,13 +13,11 @@ import TextInput from "~/components/FocusedTextInput"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import { SendFundsNavigatorStackParamList } from "~/components/RootNavigator/types/SendFundsNavigator"; import { SignTransactionNavigatorParamList } from "~/components/RootNavigator/types/SignTransactionNavigator"; -import { SwapFormNavigatorParamList } from "~/components/RootNavigator/types/SwapFormNavigator"; import { popToScreen } from "~/helpers/navigationHelpers"; import { useAccountScreen } from "LLM/hooks/useAccountScreen"; type NavigationProps = BaseComposite< | StackNavigatorProps | StackNavigatorProps - | StackNavigatorProps >; function StacksEditMemo({ navigation, route }: NavigationProps) { diff --git a/apps/ledger-live-mobile/src/families/stellar/ScreenEditMemoValue.tsx b/apps/ledger-live-mobile/src/families/stellar/ScreenEditMemoValue.tsx index e0f67782b0a..90cf3de2bf6 100644 --- a/apps/ledger-live-mobile/src/families/stellar/ScreenEditMemoValue.tsx +++ b/apps/ledger-live-mobile/src/families/stellar/ScreenEditMemoValue.tsx @@ -13,14 +13,12 @@ import TextInput from "~/components/FocusedTextInput"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import { SendFundsNavigatorStackParamList } from "~/components/RootNavigator/types/SendFundsNavigator"; import { SignTransactionNavigatorParamList } from "~/components/RootNavigator/types/SignTransactionNavigator"; -import { SwapFormNavigatorParamList } from "~/components/RootNavigator/types/SwapFormNavigator"; import { popToScreen } from "~/helpers/navigationHelpers"; import { useAccountScreen } from "LLM/hooks/useAccountScreen"; type NavigationProps = BaseComposite< | StackNavigatorProps | StackNavigatorProps - | StackNavigatorProps >; function StellarEditMemoValue({ navigation, route }: NavigationProps) { diff --git a/apps/ledger-live-mobile/src/hooks/deviceActions.ts b/apps/ledger-live-mobile/src/hooks/deviceActions.ts index 0f743f0854b..53f4cf2abf4 100644 --- a/apps/ledger-live-mobile/src/hooks/deviceActions.ts +++ b/apps/ledger-live-mobile/src/hooks/deviceActions.ts @@ -3,7 +3,6 @@ import { createAction as appCreateAction } from "@ledgerhq/live-common/hw/action import { createAction as transactionCreateAction } from "@ledgerhq/live-common/hw/actions/transaction"; import { createAction as rawTransactionCreateAction } from "@ledgerhq/live-common/hw/actions/rawTransaction"; import { createAction as startExchangeCreateAction } from "@ledgerhq/live-common/hw/actions/startExchange"; -import { createAction as initSwapCreateAction } from "@ledgerhq/live-common/hw/actions/initSwap"; import { createAction as managerCreateAction } from "@ledgerhq/live-common/hw/actions/manager"; import { createAction as signMessageCreateAction } from "@ledgerhq/live-common/hw/signMessage/index"; import { createAction as completeExchangeCreateAction } from "@ledgerhq/live-common/hw/actions/completeExchange"; @@ -19,14 +18,12 @@ import installLanguage from "@ledgerhq/live-common/hw/installLanguage"; import customLockScreenFetch from "@ledgerhq/live-common/hw/customLockScreenFetch"; import customLockScreenRemove from "@ledgerhq/live-common/hw/customLockScreenRemove"; import connectManagerFactory from "@ledgerhq/live-common/hw/connectManager"; -import initSwap from "@ledgerhq/live-common/exchange/swap/initSwap"; import connectAppFactory from "@ledgerhq/live-common/hw/connectApp"; import useEnv from "@ledgerhq/live-common/hooks/useEnv"; import startExchange from "@ledgerhq/live-common/exchange/platform/startExchange"; import completeExchange from "@ledgerhq/live-common/exchange/platform/completeExchange"; import { connectAppExecMock, - initSwapExecMock, connectManagerExecMock, fetchImageExecMock, startExchangeExecMock, @@ -74,18 +71,6 @@ export function useRawTransactionDeviceAction() { ); } -export function useInitSwapDeviceAction() { - const mock = useEnv("MOCK"); - const isLdmkConnectAppEnabled = useFeature("ldmkConnectApp")?.enabled ?? false; - return useMemo( - () => - mock - ? initSwapCreateAction(connectAppExecMock, initSwapExecMock) - : initSwapCreateAction(connectAppFactory({ isLdmkConnectAppEnabled }), initSwap), - [isLdmkConnectAppEnabled, mock], - ); -} - export function useManagerDeviceAction() { const mock = useEnv("MOCK"); const isLdmkConnectAppEnabled = useFeature("ldmkConnectApp")?.enabled ?? false; diff --git a/apps/ledger-live-mobile/src/navigation/tabNavigatorConfig.tsx b/apps/ledger-live-mobile/src/navigation/tabNavigatorConfig.tsx deleted file mode 100644 index 59b978ac18f..00000000000 --- a/apps/ledger-live-mobile/src/navigation/tabNavigatorConfig.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { ColorPalette } from "@ledgerhq/native-ui"; - -export const getLineTabNavigatorConfig = (colors: ColorPalette) => ({ - screenOptions: { - tabBarActiveTintColor: colors.neutral.c100, - tabBarInactiveTintColor: colors.neutral.c80, - tabBarIndicatorStyle: { - backgroundColor: colors.primary.c70, - height: 3, - }, - tabBarStyle: { - backgroundColor: colors.background.main, - borderBottomWidth: 1, - borderColor: colors.neutral.c40, - }, - sceneStyle: { backgroundColor: colors.background.main }, - }, -}); diff --git a/apps/ledger-live-mobile/src/reducers/index.ts b/apps/ledger-live-mobile/src/reducers/index.ts index 4acde51def8..564d7540fcb 100644 --- a/apps/ledger-live-mobile/src/reducers/index.ts +++ b/apps/ledger-live-mobile/src/reducers/index.ts @@ -17,7 +17,6 @@ import notifications from "./notifications"; import protect from "./protect"; import ratings from "./ratings"; import settings from "./settings"; -import swap from "./swap"; import toasts from "./toast"; import trustchain from "./trustchain"; import type { State } from "./types"; @@ -48,7 +47,6 @@ const appReducer = combineReducers({ protect, ratings, settings, - swap, toasts, trustchain, wallet, diff --git a/apps/ledger-live-mobile/src/reducers/settings.ts b/apps/ledger-live-mobile/src/reducers/settings.ts index 77e9143aafb..9bb0d5570ea 100644 --- a/apps/ledger-live-mobile/src/reducers/settings.ts +++ b/apps/ledger-live-mobile/src/reducers/settings.ts @@ -15,7 +15,6 @@ import type { CurrencySettings, SettingsState, State, Theme } from "./types"; import { currencySettingsDefaults } from "../helpers/CurrencySettingsDefaults"; import { getDefaultLanguageLocale, getDefaultLocale } from "../languages"; import type { - SettingsAcceptSwapProviderPayload, SettingsBlacklistTokenPayload, SettingsDismissBannerPayload, SettingsHideEmptyTokenAccountsPayload, @@ -405,19 +404,6 @@ const handlers: ReducerMap = { locale: (action as Action).payload, }), - [SettingsActionTypes.ACCEPT_SWAP_PROVIDER]: (state, action) => ({ - ...state, - swap: { - ...state.swap, - acceptedProviders: [ - ...new Set([ - ...(state.swap?.acceptedProviders || []), - (action as Action).payload, - ]), - ], - }, - }), - [SettingsActionTypes.LAST_SEEN_DEVICE_INFO]: (state, action) => { const { payload } = action as Action; return { @@ -798,11 +784,8 @@ export const languageSelector = (state: State) => state.settings.language || getDefaultLanguageLocale(); export const languageIsSetByUserSelector = (state: State) => state.settings.languageIsSetByUser; export const localeSelector = (state: State) => state.settings.locale || getDefaultLocale(); - export const swapSelectableCurrenciesSelector = (state: State) => state.settings.swap.selectableCurrencies; -export const swapAcceptedProvidersSelector = (state: State) => - state.settings.swap.acceptedProviders; export const knownDeviceModelIdsSelector = (state: State) => state.settings.knownDeviceModelIds; export const customImageTypeSelector = (state: State) => state.settings.customLockScreenType; diff --git a/apps/ledger-live-mobile/src/reducers/swap.ts b/apps/ledger-live-mobile/src/reducers/swap.ts deleted file mode 100644 index 042e242c158..00000000000 --- a/apps/ledger-live-mobile/src/reducers/swap.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Action, handleActions, ReducerMap } from "redux-actions"; -import { DEFAULT_SWAP_RATES_LLM_INTERVAL_MS } from "@ledgerhq/live-common/exchange/swap/const/timeout"; -import { AvailableProviderV3, Pair } from "@ledgerhq/live-common/exchange/swap/types"; -import { SwapStateType } from "./types"; -import { - SwapActionTypes, - SwapPayload, - UpdateProvidersPayload, - UpdateRatePayload, - UpdateTransactionPayload, - DangerouslyOverrideStatePayload, -} from "../actions/types"; - -export const INITIAL_STATE: SwapStateType = { - providers: undefined, - pairs: undefined, - transaction: undefined, - exchangeRate: undefined, - exchangeRateExpiration: undefined, -}; - -export const flattenPairs = (acc: Array, value: AvailableProviderV3): Pair[] => [ - ...acc, - ...value.pairs, -]; - -const handlers: ReducerMap = { - [SwapActionTypes.UPDATE_PROVIDERS]: (_state, action) => { - const providers = (action as Action).payload; - const pairs = (providers || []).reduce(flattenPairs, []); - - return { ...INITIAL_STATE, providers, pairs }; - }, - [SwapActionTypes.UPDATE_TRANSACTION]: (state, action) => { - const payload = (action as Action).payload; - return { - ...state, - transaction: payload, - }; - }, - [SwapActionTypes.UPDATE_RATE]: (state, action) => { - const payload = (action as Action).payload; - return { - ...state, - exchangeRate: payload, - exchangeRateExpiration: - payload?.tradeMethod === "fixed" - ? new Date(new Date().getTime() + DEFAULT_SWAP_RATES_LLM_INTERVAL_MS) - : undefined, - }; - }, - - [SwapActionTypes.DANGEROUSLY_OVERRIDE_STATE]: (state, action): SwapStateType => ({ - ...state, - ...(action as Action).payload.swap, - }), - - RESET_STATE: () => ({ ...INITIAL_STATE }), -}; - -const options = { prefix: "SWAP" }; - -export default handleActions(handlers, INITIAL_STATE, options); diff --git a/apps/ledger-live-mobile/src/reducers/types.ts b/apps/ledger-live-mobile/src/reducers/types.ts index a85d4ed67f1..36722bebcbe 100644 --- a/apps/ledger-live-mobile/src/reducers/types.ts +++ b/apps/ledger-live-mobile/src/reducers/types.ts @@ -11,8 +11,6 @@ import type { DeviceModelId } from "@ledgerhq/devices"; import type { Currency, Unit } from "@ledgerhq/types-cryptoassets"; import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; import { PostOnboardingState } from "@ledgerhq/types-live"; -import { AvailableProviderV3, ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; -import { Transaction } from "@ledgerhq/live-common/generated/types"; import type { DataOfUser } from "LLM/features/NotificationsPrompt/types"; import type { RatingsHappyMoment, RatingsDataOfUser } from "../logic/ratings"; import { WalletTabNavigatorStackParamList } from "../components/RootNavigator/types/WalletTabNavigator"; @@ -292,16 +290,6 @@ export type WalletConnectState = { uri?: string; }; -// === SWAP STATE === - -export type SwapStateType = { - providers?: AvailableProviderV3[]; - pairs?: AvailableProviderV3["pairs"]; - transaction?: Transaction; - exchangeRate?: ExchangeRate; - exchangeRateExpiration?: Date; -}; - // === EARN STATE === export type OptionMetadata = { button: string; live_app: string; flow: string; link?: string }; @@ -386,7 +374,6 @@ export type State = LLMRTKApiState & { protect: ProtectState; ratings: RatingsState; settings: SettingsState; - swap: SwapStateType; toasts: ToastState; trustchain: TrustchainStore; wallet: WalletState; diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Banner.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Banner.tsx deleted file mode 100644 index 19a9d8cbcde..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Banner.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import { Flex, Text, Button } from "@ledgerhq/native-ui"; - -interface Props { - message: string; - cta: string; - onPress: () => void; -} - -export function Banner({ message, cta, onPress }: Props) { - return ( - - - {message} - - - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Connect.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Connect.tsx deleted file mode 100644 index 1c7e62597f5..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Connect.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useState, useCallback, useEffect } from "react"; -import { useNavigation } from "@react-navigation/native"; -import { Flex } from "@ledgerhq/native-ui"; -import { Device } from "@ledgerhq/live-common/hw/actions/types"; -import SelectDevice2, { SetHeaderOptionsRequest } from "~/components/SelectDevice2"; -import DeviceActionModal from "~/components/DeviceActionModal"; -import { TrackScreen } from "~/analytics"; -import { DeviceMeta } from "./Modal/Confirmation"; -import { useManagerDeviceAction } from "~/hooks/deviceActions"; -import { HOOKS_TRACKING_LOCATIONS } from "~/analytics/hooks/variables"; - -export function Connect({ - setResult, - provider, -}: { - setResult: (_: DeviceMeta) => void; - provider?: string; -}) { - const action = useManagerDeviceAction(); - - const [device, setDevice] = useState(); - const [result] = useState(); - - const onModalHide = useCallback(() => { - if (result) { - // Nb need this in order to wait for the first modal to hide - // see https://github.com/react-native-modal/react-native-modal#i-cant-show-multiple-modals-one-after-another - setResult(result); - } - }, [result, setResult]); - - const navigation = useNavigation(); - - // Only reacts on an update request for the left part of the header - // Keeping the rest of the header (exchange and history tab + close button) from the exchange screen. - const requestToSetHeaderOptions = useCallback( - (request: SetHeaderOptionsRequest) => { - if (request.type === "set") { - // SwapForm, rendering Connect, is wrapped in a Tab.Screen. To update the left header, - // we need to set the options of the parent. - navigation.getParent()?.setOptions({ - headerLeft: request.options.headerLeft, - }); - } else { - // Sets back the left part of the header to its initial values - navigation.getParent()?.setOptions({ - headerLeft: () => null, - }); - } - }, - [navigation], - ); - - // Cleaning any updates to the parent's header from requestToSetHeaderOptions on unmount to be safe - useEffect(() => { - return () => { - // Sets back the left part of the header to its initial values - navigation.getParent()?.setOptions({ - headerLeft: () => null, - }); - }; - }, [navigation]); - - return ( - - - - - - setDevice(undefined)} - onModalHide={onModalHide} - device={result ? null : device} - onResult={setResult} - action={action} - request={null} - onSelectDeviceLink={() => setDevice(undefined)} - analyticsPropertyFlow="swap" - location={HOOKS_TRACKING_LOCATIONS.swapFlow} - /> - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/EmptyState.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/EmptyState.tsx deleted file mode 100644 index e0b0698c21d..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/EmptyState.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { Box, Text } from "@ledgerhq/native-ui"; -import { useTranslation } from "~/context/Locale"; - -const EmptyState = () => { - const { t } = useTranslation(); - return ( - - - {t("transfer.swap2.form.providers.noProviders")} - - - ); -}; - -export default React.memo(EmptyState); diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Max.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Max.tsx deleted file mode 100644 index 2572893aee0..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Max.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect, useMemo } from "react"; -import { useTranslation } from "~/context/Locale"; -import { Flex, Text, Switch } from "@ledgerhq/native-ui"; -import { SwapTransactionType } from "@ledgerhq/live-common/exchange/swap/types"; -import { isAccount } from "@ledgerhq/live-common/account/index"; -import { useAnalytics } from "~/analytics"; -import { sharedSwapTracking } from "../utils"; - -export function Max({ swapTx }: { swapTx: SwapTransactionType }) { - const { t } = useTranslation(); - const { track } = useAnalytics(); - - const isMaxButtonHidden = useMemo( - () => isAccount(swapTx.swap.from.account), - [swapTx.swap.from.account], - ); - - useEffect(() => { - if (isMaxButtonHidden && swapTx.swap.isMaxEnabled) { - swapTx.toggleMax(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isMaxButtonHidden, swapTx.swap.isMaxEnabled]); - - const onToggle = (event: boolean) => { - track("button_clicked", { - ...sharedSwapTracking, - button: "max", - state: event, - }); - swapTx.toggleMax(); - }; - - return ( - !isMaxButtonHidden && ( - - - - {t("transfer.swap2.form.amount.useMax")} - - - - - - ) - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Confirmation.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Confirmation.tsx deleted file mode 100644 index ad5ecaf6a92..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Confirmation.tsx +++ /dev/null @@ -1,323 +0,0 @@ -import React, { useCallback, useEffect, useState, useMemo, useRef } from "react"; -import { useSelector, useDispatch } from "~/context/hooks"; -import { StyleSheet, View } from "react-native"; -import { useTranslation } from "~/context/Locale"; -import { useNavigation } from "@react-navigation/native"; -import { getEnv } from "@ledgerhq/live-env"; -import type { Transaction } from "@ledgerhq/live-common/generated/types"; -import { - ExchangeSwap, - ExchangeRate, - InitSwapResult, - SwapTransaction, - SwapTransactionType, -} from "@ledgerhq/live-common/exchange/swap/types"; -import { SyncSkipUnderPriority } from "@ledgerhq/live-common/bridge/react/index"; -import addToSwapHistory from "@ledgerhq/live-common/exchange/swap/addToSwapHistory"; -import { addPendingOperation, getMainAccount } from "@ledgerhq/live-common/account/index"; -import { - AccountLike, - DeviceInfo, - getCurrencyForAccount, - Operation, - SignedOperation, -} from "@ledgerhq/types-live"; -import { Device } from "@ledgerhq/live-common/hw/actions/types"; -import { - getIncompatibleCurrencyKeys, - postSwapAccepted, - postSwapCancelled, -} from "@ledgerhq/live-common/exchange/swap/index"; -import { InstalledItem } from "@ledgerhq/live-common/apps/types"; -import { useBroadcast } from "@ledgerhq/live-common/hooks/useBroadcast"; -import { HardwareUpdate, renderLoading } from "~/components/DeviceAction/rendering"; -import { updateAccountWithUpdater } from "~/actions/accounts"; -import DeviceAction from "~/components/DeviceAction"; -import QueuedDrawer from "~/components/QueuedDrawer"; -import ModalBottomAction from "~/components/ModalBottomAction"; -import { UnionToIntersection } from "~/types/helpers"; -import type { StackNavigatorNavigation } from "~/components/RootNavigator/types/helpers"; -import { ScreenName } from "~/const"; -import type { SwapNavigatorParamList } from "~/components/RootNavigator/types/SwapNavigator"; -import { useInitSwapDeviceAction, useTransactionDeviceAction } from "~/hooks/deviceActions"; -import { BigNumber } from "bignumber.js"; -import { mevProtectionSelector } from "~/reducers/settings"; -import { DeviceModelId } from "@ledgerhq/devices"; -import { CryptoOrTokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { HOOKS_TRACKING_LOCATIONS } from "~/analytics/hooks/variables"; - -export type DeviceMeta = { - result: { installed: InstalledItem[] } | null | undefined; - device: Device; - deviceInfo: DeviceInfo; -}; - -interface Props { - swapTx: SwapTransactionType; - exchangeRate: ExchangeRate; - deviceMeta: DeviceMeta; - onError: (_error: { error: Error; swapId?: string }) => void; - onCancel: () => void; - isOpen: boolean; -} - -type NavigationProp = StackNavigatorNavigation; - -export function Confirmation({ - swapTx: swapTxProp, - exchangeRate: exchangeRateProp, - onError, - onCancel, - deviceMeta, - isOpen, -}: Props) { - // tx should not change once user enter device action flow. - const swapTx = useRef(swapTxProp); - const exchangeRate = useRef(exchangeRateProp); - const provider = exchangeRate.current.provider; - - const { - from: { account: fromAccount, parentAccount: fromParentAccount, currency: fromCurrency }, - to: { account: toAccount, parentAccount: toParentAccount, currency: toCurrency }, - } = swapTx.current.swap; - - const exchange = useMemo( - () => ({ - fromAccount: fromAccount as AccountLike, - fromParentAccount, - fromCurrency: - fromCurrency ?? - (getCurrencyForAccount(fromAccount as AccountLike) as CryptoOrTokenCurrency), - toAccount: toAccount as AccountLike, - toParentAccount, - toCurrency: - toCurrency ?? (getCurrencyForAccount(toAccount as AccountLike) as CryptoOrTokenCurrency), - }), - [fromAccount, fromParentAccount, fromCurrency, toAccount, toParentAccount, toCurrency], - ); - - const [swapData, setSwapData] = useState(null); - const [signedOperation, setSignedOperation] = useState(null); - const mevProtected = useSelector(mevProtectionSelector); - const dispatch = useDispatch(); - const broadcast = useBroadcast({ - account: fromAccount, - parentAccount: fromParentAccount, - broadcastConfig: { - mevProtected, - source: { type: "swap", name: provider }, - }, - }); - const tokenCurrency = - fromAccount && fromAccount.type === "TokenAccount" ? fromAccount.token : null; - const navigation = useNavigation(); - - const onComplete = useCallback( - ({ - magnitudeAwareRate, - ...result - }: { - operation: Operation; - swapId: string; - magnitudeAwareRate: BigNumber; - }) => { - const { operation, swapId } = result; - /** - * If transaction broadcast are disabled, consider the swap as cancelled - * since the partner will never receive the funds - */ - if (getEnv("DISABLE_TRANSACTION_BROADCAST")) { - postSwapCancelled({ - provider, - swapId, - swapStep: "SIGN_COIN_TRANSACTION", - statusCode: "DISABLE_TRANSACTION_BROADCAST", - errorMessage: "DISABLE_TRANSACTION_BROADCAST", - sourceCurrencyId: swapTx.current.swap.from.currency?.id, - targetCurrencyId: swapTx.current.swap.to.currency?.id, - hardwareWalletType: deviceMeta.device.modelId, - swapType: exchangeRate.current.tradeMethod, - }); - } else { - postSwapAccepted({ - provider, - swapId, - transactionId: operation.hash, - sourceCurrencyId: swapTx.current.swap.from.currency?.id, - targetCurrencyId: swapTx.current.swap.to.currency?.id, - hardwareWalletType: deviceMeta.device.modelId, - swapType: exchangeRate.current.tradeMethod, - }); - } - - const mainAccount = fromAccount && getMainAccount(fromAccount, fromParentAccount); - - if (!mainAccount || !exchangeRate) return; - dispatch( - updateAccountWithUpdater({ - accountId: mainAccount.id, - updater: account => - addPendingOperation( - addToSwapHistory({ - account, - operation, - transaction: swapTx.current.transaction as Transaction, - swap: { - exchange, - exchangeRate: { - ...exchangeRate.current, - magnitudeAwareRate, - }, - }, - swapId, - }), - operation, - ), - }), - ); - - if (typeof swapTx.current.swap.from.amount !== "undefined") { - navigation.replace(ScreenName.SwapPendingOperation, { - swapOperation: { - receiverAccountId: (swapTx.current.transaction as Transaction).recipient, - toCurrency, - fromCurrency, - operationId: operation.id, - provider, - swapId, - status: "pending", - fromAmount: swapTx.current.swap.from.amount, - toAmount: exchangeRate.current.toAmount, - }, - }); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - toAccount, - fromAccount, - fromParentAccount, - dispatch, - navigation, - exchange, - toParentAccount, - provider, - ], - ); - - useEffect(() => { - if (swapData && signedOperation) { - const { swapId, magnitudeAwareRate } = swapData; - broadcast(signedOperation).then( - operation => { - onComplete({ operation, swapId, magnitudeAwareRate }); - }, - error => { - onError(error); - }, - ); - } - }, [broadcast, onComplete, onError, signedOperation, swapData]); - - const silentSigningAction = useTransactionDeviceAction(); - const swapAction = useInitSwapDeviceAction(); - - const { t } = useTranslation(); - - const keys = getIncompatibleCurrencyKeys(exchange); - - if (deviceMeta?.device?.modelId === DeviceModelId.nanoS && keys) { - return ( - - - - - } - /> - - ); - } - - return ( - - - - {signedOperation ? ( - renderLoading({ - t, - description: t("transfer.swap.broadcasting"), - lockModal: true, - }) - ) : !swapData ? ( - { - const { initSwapResult, initSwapError, swapId } = result as UnionToIntersection< - typeof result - >; - if (initSwapError) { - onError({ error: initSwapError, swapId }); - } else { - setSwapData(initSwapResult); - } - }} - onError={error => onError({ error })} - onClose={onCancel} - analyticsPropertyFlow="swap" - location={HOOKS_TRACKING_LOCATIONS.swapFlow} - /> - ) : ( - { - const { transactionSignError, signedOperation, swapId } = - result as UnionToIntersection; - if (transactionSignError) { - onError({ error: transactionSignError, swapId }); - } else { - setSignedOperation(signedOperation); - } - }} - onError={error => onError({ error })} - onClose={onCancel} - analyticsPropertyFlow="swap" - location={HOOKS_TRACKING_LOCATIONS.swapFlow} - /> - )} - - } - /> - - ); -} - -const styles = StyleSheet.create({ - footerContainer: { - flexDirection: "row", - }, -}); diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Terms.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Terms.tsx deleted file mode 100644 index 06b8d03ab03..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/Terms.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useCallback } from "react"; -import { Linking } from "react-native"; -import { Button, IconsLegacy, Link } from "@ledgerhq/native-ui"; -import { useTranslation } from "~/context/Locale"; -import { getProviderName } from "@ledgerhq/live-common/exchange/swap/utils/index"; -import { urls } from "~/utils/urls"; -import QueuedDrawer from "~/components/QueuedDrawer"; - -export function Terms({ - provider, - isOpen, - onClose, - onCTA, -}: { - provider: string; - isOpen: boolean; - onClose: () => void; - onCTA: () => void; -}) { - const { t } = useTranslation(); - - const onPressLink = useCallback(() => { - // @ts-expect-error something wrong with providers type - Linking.openURL(urls.swap.providers[provider].tos); - }, [provider]); - - return ( - - - {t("transfer.swap2.form.disclaimer.tos")} - - - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/index.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/index.tsx deleted file mode 100644 index 1b43e8902c8..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Modal/index.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useMemo, useCallback, useState } from "react"; -import { SwapTransactionType, ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; -import { postSwapCancelled } from "@ledgerhq/live-common/exchange/swap/index"; -import { useSelector, useDispatch } from "~/context/hooks"; -import GenericErrorBottomModal from "~/components/GenericErrorBottomModal"; -import { Confirmation, DeviceMeta } from "./Confirmation"; -import { Terms } from "./Terms"; -import { swapAcceptProvider } from "~/actions/settings"; -import { useAnalytics } from "~/analytics"; -import { sharedSwapTracking } from "../../utils"; -import { CompleteExchangeError } from "@ledgerhq/live-common/exchange/error"; -import { lastSeenDeviceSelector } from "~/reducers/settings"; - -export function Modal({ - confirmed, - onClose, - termsAccepted, - swapTx, - deviceMeta, - exchangeRate, -}: { - confirmed: boolean; - termsAccepted: boolean; - onClose: () => void; - swapTx: SwapTransactionType; - deviceMeta?: DeviceMeta; - exchangeRate?: ExchangeRate; -}) { - const { track } = useAnalytics(); - const device = useSelector(lastSeenDeviceSelector); - const dispatch = useDispatch(); - const [error, setError] = useState(); - const provider = exchangeRate?.provider; - - const target = useMemo(() => { - if (!confirmed) { - return Target.None; - } - - if (!termsAccepted) { - return Target.Terms; - } - - if (!deviceMeta) { - return Target.None; - } - - return Target.Confirmation; - }, [confirmed, termsAccepted, deviceMeta]); - - const onAcceptTerms = useCallback(() => { - if (provider) { - dispatch(swapAcceptProvider(provider)); - } - }, [dispatch, provider]); - - const onError = useCallback( - ({ error, swapId }: { error?: Error; swapId?: string }) => { - track("error_message", { - ...sharedSwapTracking, - message: "drawer_error", - page: "Page Swap Drawer", - error: error?.name ?? "unknown", - }); - if (!exchangeRate || !swapId) { - return; - } - // Consider the swap as cancelled (on provider perspective) in case of error - postSwapCancelled({ - provider: exchangeRate.provider, - swapId: swapId ?? "", - ...((error as CompleteExchangeError).step - ? { swapStep: (error as CompleteExchangeError).step } - : {}), - statusCode: error?.name, - errorMessage: error?.message, - sourceCurrencyId: swapTx.swap.from.currency?.id, - targetCurrencyId: swapTx.swap.to.currency?.id, - ...(device ? { hardwareWalletType: device.modelId } : {}), - swapType: exchangeRate?.tradeMethod, - }); - - setError(error); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [exchangeRate, track], - ); - - const resetError = useCallback(() => { - setError(undefined); - onClose(); - }, [onClose]); - - if (!provider) { - return null; - } - - return ( - <> - - - {deviceMeta && confirmed && !error && ( - - )} - - {error && } - - ); -} - -enum Target { - Terms, - Confirmation, - None, -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Summary/Item.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Summary/Item.tsx deleted file mode 100644 index 296c7cfac01..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Summary/Item.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import { TouchableOpacity } from "react-native"; -import { Flex, Icon, Text } from "@ledgerhq/native-ui"; - -interface RowProps { - title: string; - children: React.ReactNode; - onEdit?: false | (() => void); - testID?: string; -} - -export function Item({ title, children, onEdit, testID }: RowProps) { - return ( - - {title} - - - {children} - - {onEdit && ( - - - - - - )} - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/Summary/index.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/Summary/index.tsx deleted file mode 100644 index 208466e668e..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/Summary/index.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import React, { useCallback, useMemo } from "react"; -import { useTranslation } from "~/context/Locale"; -import { BigNumber } from "bignumber.js"; -import { Flex, Icon, Text } from "@ledgerhq/native-ui"; -import { getProviderName } from "@ledgerhq/live-common/exchange/swap/utils/index"; -import { SwapTransactionType } from "@ledgerhq/live-common/exchange/swap/types"; -import { getMainAccount } from "@ledgerhq/live-common/account/index"; -import { CompositeScreenProps, useNavigation, useRoute } from "@react-navigation/native"; -import { useSelector } from "~/context/hooks"; -import { useCalculate } from "@ledgerhq/live-countervalues-react"; -import CurrencyUnitValue from "~/components/CurrencyUnitValue"; -import ProviderIcon from "~/components/ProviderIcon"; -import { Item } from "./Item"; -import { Banner } from "../Banner"; -import { NavigatorName, ScreenName } from "~/const"; -import CurrencyIcon from "~/components/CurrencyIcon"; -import { rateSelector } from "~/actions/swap"; -import { counterValueCurrencySelector } from "~/reducers/settings"; -import { - BaseComposite, - MaterialTopTabNavigatorProps, - StackNavigatorProps, -} from "~/components/RootNavigator/types/helpers"; -import type { SwapNavigatorParamList } from "~/components/RootNavigator/types/SwapNavigator"; -import type { SwapFormNavigatorParamList } from "~/components/RootNavigator/types/SwapFormNavigator"; -import { useAnalytics } from "~/analytics"; -import { sharedSwapTracking } from "../../utils"; -import { EDITABLE_FEE_FAMILIES } from "@ledgerhq/live-common/exchange/swap/const/blockchain"; -import { useMaybeAccountName } from "~/reducers/wallet"; -import { useMaybeAccountUnit } from "LLM/hooks/useAccountUnit"; -import { AddAccountContexts } from "LLM/features/Accounts/screens/AddAccount/enums"; - -interface Props { - provider?: string; - swapTx: SwapTransactionType; -} - -type Navigation = CompositeScreenProps< - StackNavigatorProps, - BaseComposite> ->; - -export function Summary({ provider, swapTx: { swap, status, transaction } }: Props) { - const { track } = useAnalytics(); - const navigation = useNavigation(); - const route = useRoute(); - const { t } = useTranslation(); - - const exchangeRate = useSelector(rateSelector); - const rawCounterValueCurrency = useSelector(counterValueCurrencySelector); - - const name = useMemo(() => provider && getProviderName(provider), [provider]); - - const { from, to } = swap; - - const estimatedFees = useMemo(() => status?.estimatedFees ?? "", [status]); - - const onEditProvider = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - button: "provider", - }); - navigation.navigate(ScreenName.SwapSelectProvider, { - swap, - provider, - selectedRate: exchangeRate, - }); - }, [navigation, swap, provider, exchangeRate, track]); - - const onAddAccount = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - button: "add account", - }); - if (!to.currency) return; - - const params = { - returnToSwap: true, - onSuccess: () => { - navigation.navigate(ScreenName.SwapForm, undefined as never); - }, - analyticsPropertyFlow: "swap", - }; - - if (to.currency.type === "TokenCurrency") { - // navigation.navigate(NavigatorName.AssetSelection, { - // token: to.currency.id, - // currency: to.currency.parentCurrency.id, - // context: AddAccountContexts.AddAccounts, - // sourceScreenName: ScreenName.SwapForm, - // }); - } else { - navigation.navigate(NavigatorName.DeviceSelection, { - screen: ScreenName.SelectDevice, - params: { - ...params, - currency: to.currency, - context: AddAccountContexts.AddAccounts, - inline: true, - }, - }); - } - }, [navigation, to, track]); - - const counterValueCurrency = to.currency || rawCounterValueCurrency; - const effectiveUnit = from.currency?.units[0]; - const valueNum = effectiveUnit && 10 ** effectiveUnit.magnitude; - const rawCounterValue = useCalculate({ - from: from.currency!, - to: counterValueCurrency, - value: valueNum!, - disableRounding: true, - }); - - const counterValue = useMemo(() => { - const rate = exchangeRate?.magnitudeAwareRate; - const valueNum = 10 ** effectiveUnit!.magnitude; - return rate - ? rate.times(valueNum) // NB Allow to override the rate for swap - : typeof rawCounterValue === "number" - ? new BigNumber(rawCounterValue) - : rawCounterValue; - }, [effectiveUnit, exchangeRate?.magnitudeAwareRate, rawCounterValue]); - - const onEditNetworkFees = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - button: "change network fees", - }); - navigation.navigate(ScreenName.SwapSelectFees, { - ...route.params, - swap, - transaction, - }); - }, [track, navigation, route.params, swap, transaction]); - - const onEditTargetAccount = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - button: "change target account", - }); - const selectableCurrencyIds = - to.currency?.type === "TokenCurrency" - ? [to.currency.id, to.currency.parentCurrency.id] - : [to.currency?.id as string]; - navigation.navigate(ScreenName.SwapSelectAccount, { - target: "to", - selectedCurrency: to.currency!, - selectableCurrencyIds, - swap, - }); - }, [track, navigation, to.currency, swap]); - - const fromUnit = from.currency?.units[0]; - const mainFromAccount = from.account && getMainAccount(from.account, from.parentAccount); - const mainAccountUnit = useMaybeAccountUnit(mainFromAccount); - const editableFee = - mainFromAccount && EDITABLE_FEE_FAMILIES.includes(mainFromAccount.currency.family); - - const toAccountName = useMaybeAccountName(to.account); - - if ( - !provider || - !fromUnit || - !mainAccountUnit || - !to.currency || - !estimatedFees || - !exchangeRate - ) { - return null; - } - - return ( - - - - - - - - {name} - - - - - - - - {" = "} - - - - - - - - - - - {to.account ? ( - - - {} - {toAccountName} - - - ) : ( - - )} - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/AmountInput.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/AmountInput.tsx deleted file mode 100644 index 0068cb7e53a..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/AmountInput.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from "react"; -import { Flex, InfiniteLoader, Text } from "@ledgerhq/native-ui"; -import { StyleSheet } from "react-native"; -import { BigNumber } from "bignumber.js"; -import { Unit } from "@ledgerhq/types-cryptoassets"; -import CurrencyInput from "~/components/CurrencyInput"; - -interface Props { - value: BigNumber | undefined; - editable: boolean; - unit: Unit | undefined; - onChange: (_: BigNumber) => void; - error?: Error; - warning?: Error; - loading: boolean; - onFocus?: (_: boolean) => void; - testID?: string; -} - -export function AmountInput({ - value, - onChange, - onFocus, - editable, - unit, - error, - warning, - loading, - testID, -}: Props) { - return ( - - {loading ? ( - - - - ) : unit ? ( - - ) : ( - - - - - )} - - ); -} - -const styles = StyleSheet.create({ - inputText: { - textAlign: "right", - }, -}); diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/CurrencyValue.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/CurrencyValue.tsx deleted file mode 100644 index b19cb305fa4..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/CurrencyValue.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import BigNumber from "bignumber.js"; -import { Flex, Text } from "@ledgerhq/native-ui"; -import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; -import CurrencyUnitValue from "~/components/CurrencyUnitValue"; -import CounterValue from "~/components/CounterValue"; - -interface Props { - currency?: CryptoCurrency | TokenCurrency; - amount?: BigNumber; - isLoading?: boolean; -} - -export function CurrencyValue({ currency, amount, isLoading }: Props) { - if (!currency || !amount || isLoading) { - return ( - - - - - ); - } - - return ( - - - - - - - - - - - - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/From.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/From.tsx deleted file mode 100644 index 98bf8bd88c4..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/From.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import React, { useCallback, useMemo } from "react"; -import { useTranslation } from "~/context/Locale"; -import { useNavigation } from "@react-navigation/native"; -import { Flex, Text } from "@ledgerhq/native-ui"; -import { getAccountSpendableBalance } from "@ledgerhq/live-common/account/index"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; -import { - useFetchCurrencyFrom, - usePickDefaultAccount, - useSwapableAccounts, -} from "@ledgerhq/live-common/exchange/swap/hooks/index"; -import type { SwapTransactionType } from "@ledgerhq/live-common/exchange/swap/types"; -import { WarningSolidMedium } from "@ledgerhq/native-ui/assets/icons"; -import { Currency } from "@ledgerhq/types-cryptoassets"; -import { Selector } from "./Selector"; -import { AmountInput } from "./AmountInput"; -import TranslatedError from "~/components/TranslatedError"; -import { ScreenName } from "~/const"; -import { useAnalytics } from "~/analytics"; -import { sharedSwapTracking } from "../../utils"; -import { flattenAccountsSelector } from "~/reducers/accounts"; -import { useSelector } from "~/context/hooks"; -import { AccountLike } from "@ledgerhq/types-live"; -import { walletSelector } from "~/reducers/wallet"; -import { accountNameWithDefaultSelector } from "@ledgerhq/live-wallet/store"; -import { useMaybeAccountUnit } from "LLM/hooks/useAccountUnit"; -import type { CompositeNavigationProp } from "@react-navigation/native"; -import type { MaterialTopTabNavigationProp } from "@react-navigation/material-top-tabs"; -import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; -import type { SwapNavigatorParamList } from "~/components/RootNavigator/types/SwapNavigator"; -import type { SwapFormNavigatorParamList } from "~/components/RootNavigator/types/SwapFormNavigator"; - -interface Props { - provider?: string; - swapTx: SwapTransactionType; - swapError?: Error; - swapWarning?: Error; - isSendMaxLoading: boolean; -} - -type SwapFormNavigation = CompositeNavigationProp< - MaterialTopTabNavigationProp, - NativeStackNavigationProp ->; - -export function From({ swapTx, provider, swapError, swapWarning, isSendMaxLoading }: Props) { - const { track } = useAnalytics(); - const { t } = useTranslation(); - const navigation = useNavigation(); - const { data: currenciesFrom } = useFetchCurrencyFrom(); - const flattenedAccounts = useSelector(flattenAccountsSelector); - const accounts = useSwapableAccounts({ accounts: flattenedAccounts }); - const walletState = useSelector(walletSelector); - - const getAccountBalance = useCallback( - (inputs: { account?: AccountLike; currency?: Currency }) => { - if (!inputs.account || !inputs.currency) return ""; - const balance = getAccountSpendableBalance(inputs.account); - return formatCurrencyUnit(inputs.currency.units[0], balance, { - showCode: true, - }); - }, - [], - ); - - const { name, balance, account } = useMemo(() => { - const { currency, account } = swapTx.swap.from; - const name = account && accountNameWithDefaultSelector(walletState, account); - return { - account, - name, - balance: getAccountBalance({ account, currency }), - }; - }, [swapTx.swap.from, walletState, getAccountBalance]); - - const unit = useMaybeAccountUnit(account); - - usePickDefaultAccount(accounts, swapTx.swap.from.account, swapTx.setFromAccount); - - const onPress = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - button: "Edit source account", - }); - - navigation.navigate(ScreenName.SwapSelectAccount, { - target: "from", - provider, - selectableCurrencyIds: currenciesFrom ?? [], - swap: swapTx.swap, - }); - }, [navigation, provider, currenciesFrom, swapTx.swap, track]); - - const onFocus = useCallback( - (event: boolean) => { - if (event) { - track("button_clicked", { - ...sharedSwapTracking, - button: "Amount input", - amount: null, - }); - } - }, - [track], - ); - - return ( - - - {t("transfer.swap2.form.from")} - - - - - - - - - - - - - {swapError || swapWarning ? ( - - - - - - - ) : null} - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/Selector.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/Selector.tsx deleted file mode 100644 index ad9e8e4f344..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/Selector.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from "react"; -import { TouchableOpacity } from "react-native"; -import { Flex, IconsLegacy, Text } from "@ledgerhq/native-ui"; -import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { useTranslation } from "~/context/Locale"; -import CurrencyIcon from "~/components/CurrencyIcon"; - -interface Props { - currency?: CryptoCurrency | TokenCurrency; - title?: string; - subTitle: string; - onPress: () => void; - disabled?: boolean; - testID?: string; -} - -export function Selector({ currency, title, subTitle, onPress, disabled = false, testID }: Props) { - const { t } = useTranslation(); - - const Icon = currency ? ( - - ) : ( - - ); - - return ( - - - - - {Icon} - - - - - {title || t("transfer.swap2.form.placeholder")} - - - - {subTitle} - - - - - - - - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/To.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/To.tsx deleted file mode 100644 index fbfb17937bf..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/To.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useCallback } from "react"; -import { useTranslation } from "~/context/Locale"; -import { Flex, Text } from "@ledgerhq/native-ui"; -import { ExchangeRate, SwapTransactionType } from "@ledgerhq/live-common/exchange/swap/types"; -import { useNavigation } from "@react-navigation/native"; -import { - useFetchCurrencyTo, - usePickDefaultCurrency, - useSelectableCurrencies, -} from "@ledgerhq/live-common/exchange/swap/hooks/index"; -import { Selector } from "./Selector"; -import { CurrencyValue } from "./CurrencyValue"; -import { ScreenName } from "~/const"; -import { useAnalytics } from "~/analytics"; -import { sharedSwapTracking } from "../../utils"; -import type { CompositeNavigationProp } from "@react-navigation/native"; -import type { MaterialTopTabNavigationProp } from "@react-navigation/material-top-tabs"; -import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; -import type { SwapNavigatorParamList } from "~/components/RootNavigator/types/SwapNavigator"; -import type { SwapFormNavigatorParamList } from "~/components/RootNavigator/types/SwapFormNavigator"; - -interface Props { - swapTx: SwapTransactionType; - provider?: string; - exchangeRate?: ExchangeRate; -} - -type SwapFormNavigation = CompositeNavigationProp< - MaterialTopTabNavigationProp, - NativeStackNavigationProp ->; - -export function To({ swapTx, provider, exchangeRate }: Props) { - const { track } = useAnalytics(); - const { t } = useTranslation(); - const navigation = useNavigation(); - - const allCurrencies = useFetchCurrencyTo({ fromCurrencyAccount: swapTx.swap.from.account }); - const currencies = useSelectableCurrencies({ allCurrencies: allCurrencies.data ?? [] }); - - usePickDefaultCurrency(currencies, swapTx.swap.to.currency, swapTx.setToCurrency); - - const onPress = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - button: "edit target account", - }); - - navigation.navigate(ScreenName.SwapSelectCurrency, { - currencies, - provider, - }); - }, [navigation, currencies, provider, track]); - - return ( - - - {t("transfer.swap2.form.to")} - - - - - - - - - - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/index.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/index.tsx deleted file mode 100644 index e2c645cacb3..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/TxForm/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import { Flex } from "@ledgerhq/native-ui"; -import { SwapTransactionType, ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; -import { From } from "./From"; -import { To } from "./To"; - -interface Props { - swapTx: SwapTransactionType; - provider?: string; - exchangeRate?: ExchangeRate; - swapError?: Error; - swapWarning?: Error; - isSendMaxLoading: boolean; -} - -export function TxForm({ - swapTx, - provider, - exchangeRate, - swapError, - swapWarning, - isSendMaxLoading, -}: Props) { - return ( - - - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/index.tsx b/apps/ledger-live-mobile/src/screens/Swap/Form/index.tsx deleted file mode 100644 index fad66c50823..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/index.tsx +++ /dev/null @@ -1,407 +0,0 @@ -import React, { useState, useEffect, useCallback, useMemo } from "react"; -import { useNavigation } from "@react-navigation/native"; -import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view"; -import { Button, Flex } from "@ledgerhq/native-ui"; -import { ExchangeRate, OnNoRatesCallback } from "@ledgerhq/live-common/exchange/swap/types"; -import { useSwapTransaction, usePageState } from "@ledgerhq/live-common/exchange/swap/hooks/index"; -import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; -import { useSelector, useDispatch } from "~/context/hooks"; -import { useTranslation } from "~/context/Locale"; -import { - flattenAccounts, - accountWithMandatoryTokens, - getParentAccount, - isTokenAccount, - getFeesUnit, -} from "@ledgerhq/live-common/account/index"; - -import { getProviderName } from "@ledgerhq/live-common/exchange/swap/utils/index"; -import { TokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { accountToWalletAPIAccount } from "@ledgerhq/live-common/wallet-api/converters"; -import { log } from "@ledgerhq/logs"; -import { shallowAccountsSelector } from "~/reducers/accounts"; -import { rateSelector, updateRateAction, updateTransactionAction } from "~/actions/swap"; -import { swapAcceptedProvidersSelector } from "~/reducers/settings"; - -import { TrackScreen, useAnalytics } from "~/analytics"; -import { Loading } from "../Loading"; -import { TxForm } from "./TxForm"; -import { Summary } from "./Summary"; -import { sharedSwapTracking, useTrackSwapError } from "../utils"; -import EmptyState from "./EmptyState"; -import { Max } from "./Max"; -import { Modal } from "./Modal"; -import { Connect } from "./Connect"; -import { DeviceMeta } from "./Modal/Confirmation"; -import { - MaterialTopTabNavigatorProps, - StackNavigatorProps, -} from "~/components/RootNavigator/types/helpers"; -import { ScreenName } from "~/const"; -import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator"; -import { SwapFormNavigatorParamList } from "~/components/RootNavigator/types/SwapFormNavigator"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; -import type { DetailsSwapParamList } from "../types"; -import { - getAvailableProviders, - maybeTezosAccountUnrevealedAccount, - maybeTronEmptyAccount, -} from "@ledgerhq/live-common/exchange/swap/index"; -import { DEFAULT_SWAP_RATES_LLM_INTERVAL_MS } from "@ledgerhq/live-common/exchange/swap/const/timeout"; -import { useSelectedSwapRate } from "./useSelectedSwapRate"; -import { walletSelector } from "~/reducers/wallet"; -import { NotEnoughBalance, NotEnoughBalanceSwap } from "@ledgerhq/errors"; - -type Navigation = StackNavigatorProps; - -export function SwapForm({ - route: { params }, -}: MaterialTopTabNavigatorProps) { - const { track } = useAnalytics(); - const trackSwapError = useTrackSwapError(); - const { t } = useTranslation(); - const dispatch = useDispatch(); - const accounts = useSelector(shallowAccountsSelector); - const exchangeRate = useSelector(rateSelector); - const swapAcceptedProviders = useSelector(swapAcceptedProvidersSelector); - // mobile specific - const [confirmed, setConfirmed] = useState(false); - const [availableProviders, setAvailableProviders] = useState([]); - - const setExchangeRate = useCallback( - (rate?: ExchangeRate) => { - dispatch(updateRateAction(rate)); - }, - [dispatch], - ); - - const walletApiPartnerList = useFeature("swapWalletApiPartnerList"); - const ptxSwapReceiveTRC20WithoutTrx = useFeature("ptxSwapReceiveTRC20WithoutTrx"); - const navigation = useNavigation(); - - const onNoRates: OnNoRatesCallback = useCallback( - ({ toState, fromState }) => { - track("error_message", { - ...sharedSwapTracking, - message: "no_rates", - sourceCurrency: fromState.currency?.name, - targetCurrency: toState.currency?.name, - }); - }, - [track], - ); - - const accountId = (params as DetailsSwapParamList)?.accountId; - const swapTransaction = useSwapTransaction({ - ...params, - accounts, - setExchangeRate, - onNoRates, - excludeFixedRates: true, - refreshRate: DEFAULT_SWAP_RATES_LLM_INTERVAL_MS / 1000, - // Disable refresh when modal is shown - allowRefresh: !confirmed, - }); - - const { provider } = useSelectedSwapRate({ - defaultRate: (params as DetailsSwapParamList).rate, - availableRates: swapTransaction.swap.rates.value, - }); - - useEffect(() => { - if (swapTransaction.account) { - swapTransaction.setFromAccount(swapTransaction.account); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const exchangeRatesState = swapTransaction.swap?.rates; - const { partnersList, exchangeRateList } = useMemo(() => { - const partnerAndExchangeRateDefault = { - partnersList: [], - exchangeRateList: [], - }; - - return ( - exchangeRatesState.value?.reduce<{ - partnersList: string[]; - exchangeRateList: string[]; - }>((prev, curr) => { - return { - partnersList: [...new Set([...prev.partnersList, curr.provider])], - exchangeRateList: [ - ...prev.exchangeRateList, - formatCurrencyUnit(getFeesUnit(swapTransaction.swap.to.currency!), curr.toAmount), - ], - }; - }, partnerAndExchangeRateDefault) ?? partnerAndExchangeRateDefault - ); - }, [exchangeRatesState, swapTransaction.swap.to.currency]); - - const swapError = - swapTransaction.fromAmountError instanceof NotEnoughBalance - ? new NotEnoughBalanceSwap(swapTransaction.fromAmountError.message) - : swapTransaction.fromAmountError || - exchangeRatesState?.error || - maybeTezosAccountUnrevealedAccount(swapTransaction) || - (ptxSwapReceiveTRC20WithoutTrx?.enabled - ? undefined - : maybeTronEmptyAccount(swapTransaction)); - - const swapWarning = swapTransaction.fromAmountWarning; - const pageState = usePageState(swapTransaction, swapError || swapWarning); - - const editRatesTrackingProps = JSON.stringify({ - ...sharedSwapTracking, - provider, - partnersList, - exchangeRateList, - sourceCurrency: swapTransaction.swap.from.currency?.id, - targetCurrency: swapTransaction.swap.from.currency?.id, - }); - - useEffect(() => { - if (pageState === "loaded") { - track("Swap Form - Edit Rates", { - ...sharedSwapTracking, - provider, - partnersList, - exchangeRateList, - sourceCurrency: swapTransaction.swap.from.currency?.id, - targetCurrency: swapTransaction.swap.from.currency?.id, - }); - } - /* - * By stringify-ing editRatesTrackingProps we are guaranteeing that the useEffect will - * only be rerun if there is a change in the editRatesTrackingProps keys & values. - * If we were to pass an object here the useEffect would re-run on - * each new creation of that object even if all the keys and values - * are the same. Causing unnecessary sends to segment/mixpanel. - */ - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pageState, editRatesTrackingProps]); - - useEffect(() => { - dispatch(updateTransactionAction(swapTransaction.transaction)); - }, [swapTransaction.transaction, dispatch]); - - useEffect(() => { - // Whenever an account is added, reselect the currency to pick a default target account. - // (possibly the one that got created) - if (swapTransaction.swap.to.currency && !swapTransaction.swap.to.account) { - swapTransaction.setToCurrency(swapTransaction.swap.to.currency); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [accounts]); - - // Track errors - useEffect( - () => { - const swapErrorOrWarning = swapError || swapWarning; - if (swapErrorOrWarning) { - trackSwapError(swapErrorOrWarning, { - sourcecurrency: swapTransaction.swap.from.currency?.name, - provider, - }); - // eslint-disable-next-line no-console - console.log("Swap Error", swapErrorOrWarning); - log("swap", "failed to fetch swaps", swapErrorOrWarning); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [swapError, swapWarning], - ); - - const isSwapReady = - !swapTransaction.bridgePending && - exchangeRatesState.status !== "loading" && - swapTransaction.transaction && - !swapError && - !swapWarning && - exchangeRate && - swapTransaction.swap.to.account; - - const walletState = useSelector(walletSelector); - - const onSubmit = useCallback(() => { - if (!exchangeRate) return; - const { provider, providerURL, providerType } = exchangeRate; - track("button_clicked", { - ...sharedSwapTracking, - sourceCurrency: swapTransaction.swap.from.currency?.name, - targetCurrency: swapTransaction.swap.to.currency?.name, - provider, - button: "exchange", - }); - - if (providerType === "DEX") { - const from = swapTransaction.swap.from; - const fromAccountId = from.parentAccount?.id || from.account?.id; - - const getAccountId = ({ - accountId, - provider, - }: { - accountId: string | undefined; - provider: string; - }) => { - if ( - !walletApiPartnerList?.enabled || - !walletApiPartnerList?.params?.list.includes(provider) - ) { - return accountId; - } - const account = accounts.find(a => a.id === accountId); - if (!account) return accountId; - const parentAccount = isTokenAccount(account) - ? getParentAccount(account, accounts) - : undefined; - const walletApiId = accountToWalletAPIAccount(walletState, account, parentAccount)?.id; - return walletApiId || accountId; - }; - - const accountId = getAccountId({ accountId: fromAccountId, provider }); - navigation.navigate(ScreenName.PlatformApp, { - platform: getProviderName(provider).toLowerCase(), - name: getProviderName(provider), - accountId, - customDappURL: providerURL, - }); - } else { - swapTransaction.transaction && swapTransaction.transaction.family !== "polkadot" - ? (swapTransaction.transaction.useAllAmount = false) - : null; - setConfirmed(true); - } - }, [ - walletState, - exchangeRate, - track, - swapTransaction.transaction, - swapTransaction.swap.from, - swapTransaction.swap.to.currency?.name, - navigation, - walletApiPartnerList?.enabled, - walletApiPartnerList?.params?.list, - accounts, - ]); - - const onCloseModal = useCallback(() => { - setConfirmed(false); - }, []); - - useEffect(() => { - const { currency, target, transaction } = params as DetailsSwapParamList; - - if (currency) { - swapTransaction.setToCurrency(currency); - } - - if (accountId) { - const enhancedAccounts = - target === "from" - ? accounts - : accounts.map(acc => - accountWithMandatoryTokens(acc, [(currency as TokenCurrency) || []]), - ); - - const account = flattenAccounts(enhancedAccounts).find(a => a.id === accountId); - - if (target === "from") { - track("Page Swap Form - New Source Account", { - ...sharedSwapTracking, - provider, - }); - swapTransaction.setFromAccount(account); - } - - if (target === "to") { - swapTransaction.setToAccount( - swapTransaction.swap.to.currency, - account, - isTokenAccount(account) ? getParentAccount(account, accounts) : undefined, - ); - } - } - - if (transaction) { - swapTransaction.setTransaction(transaction); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [params]); - - useEffect(() => { - const fetchProviders = async () => { - try { - const providers = await getAvailableProviders(); - setAvailableProviders(providers); - } catch (error) { - console.error("Error fetching providers:", error); - } - }; - fetchProviders(); - }, []); - - const termsAccepted = (swapAcceptedProviders || []).includes(provider ?? ""); - const [deviceMeta, setDeviceMeta] = useState(); - - // TODO: investigate this logic - if (confirmed && !deviceMeta && termsAccepted) { - return ; - } - - if (availableProviders.length) { - return ( - - - - - - {(pageState === "empty" || pageState === "loading") && ( - - {pageState === "empty" && } - {pageState === "loading" && } - - )} - - {pageState === "loaded" && ( - <> - {exchangeRate && - swapTransaction.swap.to.currency && - swapTransaction.swap.from.currency && ( - - )} - - )} - - - - - - - - - - - ); - } - - return ; -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/Form/useSelectedSwapRate.ts b/apps/ledger-live-mobile/src/screens/Swap/Form/useSelectedSwapRate.ts deleted file mode 100644 index 336ae3ee712..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/Form/useSelectedSwapRate.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; -import { useCallback, useEffect, useMemo } from "react"; -import { useSelector, useDispatch } from "~/context/hooks"; -import { rateSelector, updateRateAction } from "~/actions/swap"; - -type UseSelectedSwapRateProps = { - defaultRate?: ExchangeRate; - availableRates?: ExchangeRate[]; -}; - -export function useSelectedSwapRate(props: UseSelectedSwapRateProps) { - const { defaultRate, availableRates } = props; - - const dispatch = useDispatch(); - const exchangeRate = useSelector(rateSelector); - - const provider = useMemo(() => { - // Prioritize provider from redux state over navigation parameter - const targetProvider = exchangeRate?.provider ?? defaultRate?.provider; - const fallback = availableRates?.at(0)?.provider; - const hasProvider = availableRates?.some(item => item.provider === targetProvider); - if (hasProvider) { - return targetProvider; - } - // If target provider doesn't exists, fallback to first provider from available rates - return fallback; - }, [defaultRate, exchangeRate?.provider, availableRates]); - - const updateExchangeRate = useCallback( - (rate?: ExchangeRate) => { - dispatch(updateRateAction(rate)); - }, - [dispatch], - ); - - // Update rate using inputs provider only if new rate is different - const onSelectRateFromProvider = useCallback( - (inputs: { provider: ExchangeRate["provider"]; rates: ExchangeRate[] }) => { - const targetRate = inputs.rates.find(item => item.provider === inputs.provider); - - // Compare rates - const isDifferentProvider = provider !== exchangeRate?.provider; - const isDifferentAmount = targetRate?.toAmount !== exchangeRate?.toAmount; - const isDifferentTradeMethod = targetRate?.tradeMethod !== exchangeRate?.tradeMethod; - const shouldUpdateRate = isDifferentProvider || isDifferentAmount || isDifferentTradeMethod; - - /** - * Update rate if needed, due to having SwapTransaction in - * dep array, this effect hook can be triggered per second - */ - if (targetRate && shouldUpdateRate) { - updateExchangeRate(targetRate); - } - }, - [ - updateExchangeRate, - provider, - exchangeRate?.provider, - exchangeRate?.toAmount, - exchangeRate?.tradeMethod, - ], - ); - - useEffect(() => { - // No rates, no point to continue - if (!availableRates) return; - - switch (true) { - // - // Monitor default provider, if is different, we will attempt to update rate - // (most likely from navigation parameters changed) - // - case defaultRate && defaultRate.provider !== provider: - onSelectRateFromProvider({ - provider: defaultRate!.provider, - rates: availableRates, - }); - break; - // - // Update rate using calculated provider - // - case Boolean(provider): - onSelectRateFromProvider({ - provider: provider!, - rates: availableRates, - }); - break; - default: - break; - } - }, [provider, defaultRate, availableRates, updateExchangeRate, onSelectRateFromProvider]); - - return { - provider, - updateExchangeRate, - rate: exchangeRate?.rate, - }; -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectAccount.tsx b/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectAccount.tsx deleted file mode 100644 index 6c8d7985bad..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectAccount.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import React, { useCallback, useMemo } from "react"; -import { StyleSheet, FlatList, TouchableOpacity } from "react-native"; -import { useSelector } from "~/context/hooks"; -import { useTranslation } from "~/context/Locale"; -import { Flex, IconsLegacy, Text, BoxedIcon } from "@ledgerhq/native-ui"; -import { useTheme } from "@react-navigation/native"; -import { Account, AccountLike } from "@ledgerhq/types-live"; -import { getAccountCurrency, flattenAccounts } from "@ledgerhq/live-common/account/index"; -import { - accountWithMandatoryTokens, - getAccountSpendableBalance, -} from "@ledgerhq/live-common/account/helpers"; -import { TrackScreen, useAnalytics } from "~/analytics"; -import AccountCard from "~/components/AccountCard"; -import FilteredSearchBar from "~/components/FilteredSearchBar"; -import KeyboardView from "~/components/KeyboardView"; -import { formatSearchResults, SearchResult } from "~/helpers/formatAccountSearchResults"; -import { ScreenName } from "~/const"; -import { accountsSelector } from "~/reducers/accounts"; -import { sharedSwapTracking } from "../utils"; -import { walletSelector } from "~/reducers/wallet"; -import { accountNameWithDefaultSelector } from "@ledgerhq/live-wallet/store"; -import type { SelectAccountParamList } from "../types"; - -export function SelectAccount({ navigation, route: { params } }: SelectAccountParamList) { - const { provider, target, selectableCurrencyIds, selectedCurrency } = params; - const { track } = useAnalytics(); - const unfilteredAccounts = useSelector(accountsSelector); - - const accounts: Account[] = useMemo( - () => - unfilteredAccounts.filter(acc => selectableCurrencyIds.includes(getAccountCurrency(acc).id)), - [selectableCurrencyIds, unfilteredAccounts], - ); - - const enhancedAccounts = useMemo(() => { - if (!selectedCurrency) { - return accounts.map(acc => accountWithMandatoryTokens(acc, [])); - } - - const filteredAccounts = accounts.filter( - acc => - acc.currency.id === - (selectedCurrency.type === "TokenCurrency" - ? selectedCurrency.parentCurrency.id - : selectedCurrency.id), - ); - if (selectedCurrency.type === "TokenCurrency") { - return filteredAccounts.map(acc => accountWithMandatoryTokens(acc, [selectedCurrency])); - } - return filteredAccounts; - }, [accounts, selectedCurrency]); - - const walletState = useSelector(walletSelector); - - const allAccounts = useMemo(() => { - const accounts: AccountLike[] = flattenAccounts(enhancedAccounts); - - if (target === "to") { - return accounts - .filter(a => { - const c = getAccountCurrency(a); - return c.type === "CryptoCurrency" || c.id === selectedCurrency?.id; - }) - .map(account => { - const c = getAccountCurrency(account); - return { - name: accountNameWithDefaultSelector(walletState, account), - account: { - ...account, - // FIXME flatten disabled back in the top object - disabled: - (selectedCurrency?.type === "TokenCurrency" && c.type === "CryptoCurrency") || - c.id !== selectedCurrency?.id, - }, - }; - }); - } - - return accounts.map(a => ({ - name: accountNameWithDefaultSelector(walletState, a), - account: { - ...a, - // FIXME flatten disabled back in the top object - disabled: !a.balance.gt(0) || !selectableCurrencyIds.includes(getAccountCurrency(a).id), - }, - })); - }, [target, selectedCurrency, enhancedAccounts, selectableCurrencyIds, walletState]); - - const { t } = useTranslation(); - const { colors } = useTheme(); - - const onSelect = useCallback( - (account: AccountLike) => { - navigation.popTo(ScreenName.SwapTab, { - screen: ScreenName.SwapForm, - params: { - accountId: account.id, - currency: selectedCurrency, - target, - }, - }); - }, - [navigation, target, selectedCurrency], - ); - - /** - * Show spendable account balance - */ - const getSpendableAccount = useCallback((item: AccountLike) => { - const balance = getAccountSpendableBalance(item); - const account: AccountLike = { - ...item, - balance, - }; - return account; - }, []); - - const renderItem = useCallback( - ({ item }: { item: SearchResult }) => { - const styleProps = - item.account.type === "TokenAccount" - ? { - marginLeft: 8, - borderLeftWidth: 1, - borderColor: "neutral.c60", - } - : {}; - - return ( - - onSelect(item.account)} - /> - - ); - }, - [onSelect, getSpendableAccount], - ); - - const onAddAccount = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - account: "account", - button: "new source account", - }); - - // navigation.navigate(NavigatorName.AssetSelection, { - // screen: ScreenName.AddAccountsSelectCrypto, - // params: { - // returnToSwap: true, - // filterCurrencyIds: selectableCurrencyIds, - // onSuccess: () => { - // navigation.navigate(ScreenName.SwapSelectAccount, params); - // }, - // analyticsPropertyFlow: "swap", - // context: AddAccountContexts.AddAccounts, - // sourceScreenName: ScreenName.SwapSelectAccount, - // }, - // }); - }, [track]); - - const renderList = useCallback( - (items: typeof allAccounts) => { - const formatedList = formatSearchResults( - items.map(a => a.account), - accounts, - ); - return ( - item.account.id} - showsVerticalScrollIndicator={false} - keyboardDismissMode="on-drag" - ListFooterComponent={() => ( - - - } - variant="circle" - backgroundColor="primary.c20" - borderColor="transparent" - /> - - - {t("transfer.swap.emptyState.CTAButton")} - - - - )} - /> - ); - }, - [accounts, renderItem, t, onAddAccount, colors], - ); - - return ( - - - ( - - {t("transfer.receive.noAccount")} - - )} - /> - - ); -} - -const styles = StyleSheet.create({ - card: { - paddingHorizontal: 16, - backgroundColor: "transparent", - }, -}); diff --git a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectCurrency.tsx b/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectCurrency.tsx deleted file mode 100644 index d5fba16daf6..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectCurrency.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { useCallback } from "react"; -import { FlatList, StyleSheet } from "react-native"; -import { Flex, Text } from "@ledgerhq/native-ui"; -import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { useTranslation } from "~/context/Locale"; -import type { TFunction } from "i18next"; -import { useCurrenciesByMarketcap } from "@ledgerhq/live-common/currencies/hooks"; -import { TrackScreen, useAnalytics } from "~/analytics"; -import FilteredSearchBar from "~/components/FilteredSearchBar"; -import KeyboardView from "~/components/KeyboardView"; -import CurrencyRow from "~/components/CurrencyRow"; -import { ScreenName } from "~/const"; -import { sharedSwapTracking } from "../utils"; -import { getEnv } from "@ledgerhq/live-env"; -import type { SelectCurrencyParamList } from "../types"; - -function keyExtractor({ id }: CryptoCurrency | TokenCurrency) { - return id; -} - -const getItemLayout = (_: unknown, index: number) => ({ - length: 64, - offset: 64 * index, - index, -}); - -export function SelectCurrency({ - navigation, - route: { - params: { provider, currencies }, - }, -}: SelectCurrencyParamList) { - const { t } = useTranslation(); - const { track } = useAnalytics(); - - const onSelect = useCallback( - (currency: CryptoCurrency | TokenCurrency) => { - track("button_clicked", { - ...sharedSwapTracking, - button: "new target currency", - currency: currency.name, - }); - - // Preserve the current swap state if available - navigation.popTo(ScreenName.SwapTab, { - screen: ScreenName.SwapForm, - params: { - currency, - }, - }); - }, - [track, navigation], - ); - - const RenderItem = ({ item }: { item: CryptoCurrency | TokenCurrency }) => { - return ; - }; - - const sortedCurrencies = useCurrenciesByMarketcap(currencies); - - const renderList = useCallback( - (items: (TokenCurrency | CryptoCurrency)[]) => ( - - ), - // eslint-disable-next-line react-hooks/exhaustive-deps - [], - ); - - return ( - - - - - - - - ); -} - -function renderEmptyList(t: TFunction) { - return () => ( - - {t("common.noCryptoFound")} - - ); -} - -const styles = StyleSheet.create({ - list: { - paddingBottom: 32, - }, -}); diff --git a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectFees.tsx b/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectFees.tsx deleted file mode 100644 index b398c436d57..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectFees.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useCallback } from "react"; -import { StyleSheet } from "react-native"; -import { SafeAreaView } from "react-native-safe-area-context"; -import SendRowsFee from "~/components/SendRowsFee"; -import NavigationScrollView from "~/components/NavigationScrollView"; -import { TrackScreen } from "~/analytics"; -import { ScreenName } from "~/const"; -import { SWAP_VERSION } from "../utils"; -import { SelectFeesParamList } from "../types"; - -export function SelectFees({ navigation, route }: SelectFeesParamList) { - const { params } = route; - const { - swap: { - from: { account, parentAccount }, - }, - transaction, - } = params; - const isFixed = params.rate?.tradeMethod === "fixed"; - - const onSetTransaction = useCallback( - (updatedTransaction: NonNullable) => { - // Navigate to the nested SwapForm screen within SwapTab - navigation.popTo(ScreenName.SwapTab, { - screen: ScreenName.SwapForm, - merge: true, - params: { - transaction: updatedTransaction, - }, - }); - }, - [navigation], - ); - - return ( - - - - {account && transaction ? ( - - ) : null} - - - ); -} - -const styles = StyleSheet.create({ - root: { - flex: 1, - }, - scrollView: { - paddingHorizontal: 16, - }, -}); diff --git a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectProvider.tsx b/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectProvider.tsx deleted file mode 100644 index 49cd69296d2..00000000000 --- a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/SelectProvider.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useCallback } from "react"; -import { TouchableOpacity } from "react-native"; -import { BigNumber } from "bignumber.js"; -import { Flex, Text, Icon } from "@ledgerhq/native-ui"; -import { getProviderName } from "@ledgerhq/live-common/exchange/swap/utils/index"; -import { useTranslation } from "~/context/Locale"; -import { ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; -import CurrencyUnitValue from "~/components/CurrencyUnitValue"; -import ProviderIcon from "~/components/ProviderIcon"; -import { SelectProviderParamList } from "../types"; -import CounterValue from "~/components/CounterValue"; -import { TrackScreen, useAnalytics } from "~/analytics"; -import { ScreenName } from "~/const"; -import { sharedSwapTracking, SWAP_VERSION } from "../utils"; -import { useMaybeAccountUnit } from "LLM/hooks/useAccountUnit"; - -export function SelectProvider({ navigation, route }: SelectProviderParamList) { - const { - params: { - provider, - swap: { from, to, rates }, - selectedRate, - }, - } = route; - const { track } = useAnalytics(); - const { t } = useTranslation(); - const fromUnit = useMaybeAccountUnit(from.account); - - const onSelect = useCallback( - (rate: ExchangeRate) => { - track("button_clicked", { - ...sharedSwapTracking, - button: "Partner Chosen", - swapType: rate.tradeMethod, - defaultPartner: selectedRate?.provider ?? "missing", - totalPartners: rates.value?.length ?? "missing", - }); - // Navigate to the nested SwapForm screen within SwapTab - navigation.popTo(ScreenName.SwapTab, { - screen: ScreenName.SwapForm, - merge: true, - params: { - rate, - }, - }); - }, - [track, selectedRate?.provider, rates.value?.length, navigation], - ); - - if (!rates.value || !fromUnit || !to.currency) { - return null; - } - - const toCurrency = to.currency; - - return ( - - - - - {t("transfer.swap2.form.ratesDrawer.quote")} - - - - {t("transfer.swap2.form.ratesDrawer.receive")} - - - - - {rates.value.map(rate => { - const isSelected = selectedRate === rate; - - return ( - onSelect(rate)} - > - - - - - - {getProviderName(rate.provider)} - - - - - - - - {" = "} - - - - - - - - - - - - - - - - - - - ); - })} - - - ); -} diff --git a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/index.ts b/apps/ledger-live-mobile/src/screens/Swap/SubScreens/index.ts index 6a0b53ddbb3..287b3ab03d7 100644 --- a/apps/ledger-live-mobile/src/screens/Swap/SubScreens/index.ts +++ b/apps/ledger-live-mobile/src/screens/Swap/SubScreens/index.ts @@ -1,7 +1,3 @@ -export * from "./SelectAccount"; -export * from "./SelectCurrency"; -export * from "./SelectProvider"; -export * from "./SelectFees"; export * from "./PendingOperation"; export * from "./OperationDetails"; export * from "./SwapCustomError"; diff --git a/apps/ledger-live-mobile/src/screens/Swap/index.ts b/apps/ledger-live-mobile/src/screens/Swap/index.ts index 35054b32033..367e7b2e15f 100644 --- a/apps/ledger-live-mobile/src/screens/Swap/index.ts +++ b/apps/ledger-live-mobile/src/screens/Swap/index.ts @@ -1,3 +1 @@ -export { SwapForm } from "./Form/index"; export * from "./SubScreens/index"; -export type { SwapFormNavParamList } from "./types"; diff --git a/apps/ledger-live-mobile/src/screens/Swap/types.ts b/apps/ledger-live-mobile/src/screens/Swap/types.ts index 735deace8d3..aac1225ff91 100644 --- a/apps/ledger-live-mobile/src/screens/Swap/types.ts +++ b/apps/ledger-live-mobile/src/screens/Swap/types.ts @@ -3,30 +3,10 @@ import type { Transaction } from "@ledgerhq/live-common/generated/types"; import type { CryptoCurrency, Currency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; import type { Account, SwapOperation } from "@ledgerhq/types-live"; import type { NativeStackScreenProps } from "@react-navigation/native-stack"; -import type { BaseComposite } from "~/components/RootNavigator/types/helpers"; import type { SwapNavigatorParamList } from "~/components/RootNavigator/types/SwapNavigator"; import type { SwapWebviewAllowedPageNames } from "~/components/Web3AppWebview/types"; import type { ScreenName } from "~/const"; -export type SelectAccountParamList = NativeStackScreenProps< - SwapNavigatorParamList, - ScreenName.SwapSelectAccount ->; - -export type SelectCurrencyParamList = NativeStackScreenProps< - SwapNavigatorParamList, - ScreenName.SwapSelectCurrency ->; - -export type SelectProviderParamList = NativeStackScreenProps< - SwapNavigatorParamList, - ScreenName.SwapSelectProvider ->; - -export type SelectFeesParamList = BaseComposite< - NativeStackScreenProps ->; - export type PendingOperationParamList = NativeStackScreenProps< SwapNavigatorParamList, ScreenName.SwapPendingOperation @@ -70,12 +50,6 @@ export interface DefaultAccountSwapParamList extends SwapLiveAppNavigationParams affiliate?: string; } -export type SwapFormNavParamList = { - SwapForm: DetailsSwapParamList | DefaultAccountSwapParamList | undefined; - SwapHistory: undefined; - SwapPendingOperation: undefined; -}; - export type SwapLiveAppNavigationParams = { swapNavigationParams?: { tab?: "ACCOUNTS_SELECTION" | "QUOTES_LIST" | null; diff --git a/apps/ledger-live-mobile/src/screens/Swap/utils.ts b/apps/ledger-live-mobile/src/screens/Swap/utils.ts index 5a7f7da00b3..b0f957df41e 100644 --- a/apps/ledger-live-mobile/src/screens/Swap/utils.ts +++ b/apps/ledger-live-mobile/src/screens/Swap/utils.ts @@ -1,35 +1,6 @@ -import { useCallback } from "react"; -import { SwapExchangeRateAmountTooLow } from "@ledgerhq/live-common/errors"; -import { NotEnoughBalanceSwap } from "@ledgerhq/errors"; -import { useAnalytics } from "../../analytics/segment"; - export const SWAP_VERSION = "2.34"; export const sharedSwapTracking = { swapVersion: SWAP_VERSION, flow: "swap", }; - -export const useTrackSwapError = () => { - const { track } = useAnalytics(); - return useCallback( - (error: Error, properties = {}) => { - if (!error) return; - if (error instanceof SwapExchangeRateAmountTooLow) { - track("error_message", { - ...sharedSwapTracking, - ...properties, - message: "min_amount", - }); - } - if (error instanceof NotEnoughBalanceSwap) { - track("error_message", { - ...sharedSwapTracking, - ...properties, - message: "no_funds", - }); - } - }, - [track], - ); -}; diff --git a/libs/ledger-live-common/.unimportedrc.json b/libs/ledger-live-common/.unimportedrc.json index bb93107dc47..6d247ee4d26 100644 --- a/libs/ledger-live-common/.unimportedrc.json +++ b/libs/ledger-live-common/.unimportedrc.json @@ -189,7 +189,6 @@ "src/hw/actions/customLockScreenFetch.ts", "src/hw/actions/customLockScreenLoad.ts", "src/hw/actions/customLockScreenRemove.ts", - "src/hw/actions/initSwap.ts", "src/hw/actions/installLanguage.ts", "src/hw/actions/manager.ts", "src/hw/actions/renameDevice.ts", diff --git a/libs/ledger-live-common/src/exchange/swap/getExchangeRates.ts b/libs/ledger-live-common/src/exchange/swap/getExchangeRates.ts deleted file mode 100644 index 356c6159e6f..00000000000 --- a/libs/ledger-live-common/src/exchange/swap/getExchangeRates.ts +++ /dev/null @@ -1,185 +0,0 @@ -import network from "@ledgerhq/live-network/network"; -import type { Unit } from "@ledgerhq/types-cryptoassets"; -import { BigNumber } from "bignumber.js"; -import { getAccountCurrency } from "../../account"; -import { formatCurrencyUnit } from "../../currencies"; -import { - SwapExchangeRateAmountTooHigh, - SwapExchangeRateAmountTooLow, - SwapExchangeRateAmountTooLowOrTooHigh, -} from "../../errors"; -import { getSwapAPIBaseURL, getSwapAPIError, getSwapUserIP } from "./"; -import { mockGetExchangeRates } from "./mock"; -import type { CustomMinOrMaxError, GetExchangeRates } from "./types"; -import { isIntegrationTestEnv } from "./utils/isIntegrationTestEnv"; -import { getSwapProvider } from "../providers/swap"; - -const getExchangeRates: GetExchangeRates = async ({ - exchange, - transaction, - currencyTo, - providers = [], - timeout, - timeoutErrorMessage, -}) => { - if (isIntegrationTestEnv()) return mockGetExchangeRates(exchange, transaction, currencyTo); - - const from = getAccountCurrency(exchange.fromAccount).id; - const unitFrom = getAccountCurrency(exchange.fromAccount).units[0]; - const to = (currencyTo ?? getAccountCurrency(exchange.toAccount)).id; - const unitTo = - (currencyTo && currencyTo.units[0]) ?? getAccountCurrency(exchange.toAccount).units[0]; - const amountFrom = transaction.amount; - const tenPowMagnitude = new BigNumber(10).pow(unitFrom.magnitude); - const apiAmount = new BigNumber(amountFrom).div(tenPowMagnitude); - - const providerList = providers - .filter(provider => provider.pairs.some(pair => pair.from === from && pair.to === to)) - .map(item => item.provider); - - const request = { - from, - to, - amountFrom: apiAmount.toString(), - providers: providerList, - }; - - const headers = getSwapUserIP(); - const res = await network({ - method: "POST", - url: `${getSwapAPIBaseURL()}/rate`, - ...(timeout ? { timeout } : {}), - ...(timeoutErrorMessage ? { timeoutErrorMessage } : {}), - data: request, - ...(headers !== undefined ? headers : {}), - }); - - const rates = res.data.map(async responseData => { - const { - rate: maybeRate, - payoutNetworkFees: maybePayoutNetworkFees, - rateId, - provider, - providerType, - amountFrom, - amountTo, - tradeMethod, - providerURL, - expirationTime, - } = responseData; - - const error = await inferError(apiAmount, unitFrom, responseData); - if (error) { - return { - provider, - tradeMethod, - error, - }; - } - - const payoutNetworkFees = new BigNumber(maybePayoutNetworkFees || 0); - - const magnitudeAwarePayoutNetworkFees = payoutNetworkFees.times( - new BigNumber(10).pow(unitTo.magnitude), - ); - - const rate = maybeRate - ? new BigNumber(maybeRate) - : new BigNumber(amountTo).minus(payoutNetworkFees).div(amountFrom); - - // NB Allows us to simply multiply satoshi values from/to - const magnitudeAwareRate = rate.div( - new BigNumber(10).pow(unitFrom.magnitude - unitTo.magnitude), - ); - - const toAmount = new BigNumber(amountTo).minus(payoutNetworkFees); - - const magnitudeAwareToAmount = toAmount.times(new BigNumber(10).pow(unitTo.magnitude)); - // Nb no longer need to break it down on UI - - const out = { - magnitudeAwareRate, - provider, - providerType, - rate, - rateId, - expirationTime, - toAmount: magnitudeAwareToAmount, - tradeMethod, - providerURL, - }; - - if (tradeMethod === "fixed") { - return { - ...out, - payoutNetworkFees: magnitudeAwarePayoutNetworkFees, - rateId, - }; - } else { - return { - ...out, - payoutNetworkFees: magnitudeAwarePayoutNetworkFees, - }; - } - }); - return rates; -}; - -const inferError = async ( - apiAmount: BigNumber, - unitFrom: Unit, - responseData: { - amountTo: string; - minAmountFrom: string; - maxAmountFrom: string; - errorCode?: number; - errorMessage?: string; - status?: string; - provider: string; - }, -): Promise => { - const tenPowMagnitude = new BigNumber(10).pow(unitFrom.magnitude); - const { amountTo, minAmountFrom, maxAmountFrom, errorCode, errorMessage, provider, status } = - responseData; - const isDex = (await getSwapProvider(provider)).type === "DEX"; - - // DEX quotes are out of limits error. We do not know if it is a low or high limit, neither the amount. - if ((!minAmountFrom || !maxAmountFrom) && status === "error" && errorCode !== 300 && isDex) { - return new SwapExchangeRateAmountTooLowOrTooHigh(undefined, { - message: "", - }); - } - - if (!amountTo) { - // We are in an error case regardless of api version. - if (errorCode) { - return getSwapAPIError(errorCode, errorMessage); - } - - // For out of range errors we will have a min/max pairing - const hasAmountLimit = minAmountFrom || maxAmountFrom; - if (hasAmountLimit) { - const isTooSmall = minAmountFrom ? new BigNumber(apiAmount).lte(minAmountFrom) : false; - - const MinOrMaxError = isTooSmall - ? SwapExchangeRateAmountTooLow - : SwapExchangeRateAmountTooHigh; - - const key: string = isTooSmall ? "minAmountFromFormatted" : "maxAmountFromFormatted"; - - const amount = isTooSmall ? minAmountFrom : maxAmountFrom; - - return new MinOrMaxError(undefined, { - [key]: formatCurrencyUnit(unitFrom, new BigNumber(amount).times(tenPowMagnitude), { - alwaysShowSign: false, - disableRounding: true, - showCode: true, - }), - amount: new BigNumber(amount), - }); - } - } - return; -}; - -export default getExchangeRates; diff --git a/libs/ledger-live-common/src/exchange/swap/index.ts b/libs/ledger-live-common/src/exchange/swap/index.ts index c24806efe6a..26bce5ec2ed 100644 --- a/libs/ledger-live-common/src/exchange/swap/index.ts +++ b/libs/ledger-live-common/src/exchange/swap/index.ts @@ -16,12 +16,7 @@ import { ValidationError, } from "../../errors"; import getCompleteSwapHistory from "./getCompleteSwapHistory"; -import initSwap from "./initSwap"; import { postSwapAccepted, postSwapCancelled } from "./postSwapState"; -import getExchangeRates from "./getExchangeRates"; -import { maybeTezosAccountUnrevealedAccount } from "./maybeTezosAccountUnrevealedAccount"; -import { maybeTronEmptyAccount } from "./maybeTronEmptyAccount"; -import { maybeKeepTronAccountAlive } from "./maybeKeepTronAccountAlive"; import { getIncompatibleCurrencyKeys } from "./getIncompatibleCurrencyKeys"; export { getAvailableProviders } from "../providers"; @@ -141,12 +136,7 @@ export { getSwapAPIVersion, getCompleteSwapHistory, postSwapAccepted, - getExchangeRates, - maybeTezosAccountUnrevealedAccount, - maybeTronEmptyAccount, - maybeKeepTronAccountAlive, postSwapCancelled, - initSwap, USStates, countries, getIncompatibleCurrencyKeys, diff --git a/libs/ledger-live-common/src/exchange/swap/initSwap.ts b/libs/ledger-live-common/src/exchange/swap/initSwap.ts deleted file mode 100644 index bac926ace9d..00000000000 --- a/libs/ledger-live-common/src/exchange/swap/initSwap.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets"; -import { - TransportStatusError, - WrongDeviceForAccountPayout, - WrongDeviceForAccountRefund, -} from "@ledgerhq/errors"; -import Exchange, { - decodePayloadProtobuf, - ExchangeTypes, - RateTypes, -} from "@ledgerhq/hw-app-exchange"; -import network from "@ledgerhq/live-network/network"; -import { log } from "@ledgerhq/logs"; -import { BigNumber } from "bignumber.js"; -import invariant from "invariant"; -import { firstValueFrom, from, Observable } from "rxjs"; -import { secp256k1 } from "@noble/curves/secp256k1"; -import { getCurrencyExchangeConfig } from "../"; -import { getAccountCurrency, getMainAccount } from "../../account"; -import { getAccountBridge } from "../../bridge"; -import { getEnv } from "@ledgerhq/live-env"; -import { - SwapGenericAPIError, - SwapRateExpiredError, - TransactionRefusedOnDevice, -} from "../../errors"; -import { withDevice } from "../../hw/deviceAccess"; -import { delay } from "../../promise"; -import { getSwapAPIBaseURL, getSwapUserIP } from "./"; -import { mockInitSwap } from "./mock"; -import type { InitSwapInput, SwapRequestEvent } from "./types"; -import { convertToAppExchangePartnerKey, getSwapProvider } from "../providers"; -import { getDefaultAccountName } from "@ledgerhq/live-wallet/accountName"; -import { CEXProviderConfig } from "../providers/swap"; - -const withDevicePromise = (deviceId, fn) => - firstValueFrom(withDevice(deviceId)(transport => 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: InitSwapInput): Observable => { - let swapId; - let { transaction } = input; - const { exchange, exchangeRate, deviceId } = input; - - if (getEnv("MOCK")) return mockInitSwap(exchange, exchangeRate, transaction); - return new Observable(o => { - let unsubscribed = false; - - const confirmSwap = async () => { - let ignoreTransportError; - let magnitudeAwareRate; - log("swap", `attempt to connect to ${deviceId}`); - await withDevicePromise(deviceId, async transport => { - const ratesFlag = - exchangeRate.tradeMethod === "fixed" ? RateTypes.Fixed : RateTypes.Floating; - const swap = new Exchange(transport, 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 = getAccountCurrency(fromAccount); - const unitFrom = getAccountCurrency(exchange.fromAccount).units[0]; - const payoutCurrency = getAccountCurrency(toAccount); - const refundAccount = getMainAccount(fromAccount, fromParentAccount); - const payoutAccount = getMainAccount(toAccount, toParentAccount); - const apiAmount = new BigNumber(amount).div(new 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 getSwapProvider(provider); - - const headers = { - EquipmentId: getEnv("USER_ID"), - - ...(getSwapUserIP() !== undefined ? 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 === RateTypes.Fixed - ? { - rateId, - } - : {}), // NB float rates dont need rate ids. - }; - - try { - res = await network({ - method: "POST", - url: `${getSwapAPIBaseURL()}/swap`, - headers, - data, - }); - - if (unsubscribed || !res || !res.data) return; - } catch (e: any) { - if (e.msg.messageKey == "WRONG_OR_EXPIRED_RATE_ID") { - o.next({ - type: "init-swap-error", - error: new SwapRateExpiredError(), - swapId, - }); - } - o.next({ - type: "init-swap-error", - error: new SwapGenericAPIError(), - swapId, - }); - o.complete(); - return; - } - - const swapResult = res.data; - swapId = swapResult.swapId; - - const accountBridge = getAccountBridge(refundAccount); - transaction = accountBridge.updateTransaction(transaction, { - recipient: swapResult.payinAddress, - }); - - if (refundCurrency.id === "ripple") { - transaction = accountBridge.updateTransaction(transaction, { - tag: new BigNumber(swapResult.payinExtraId).toNumber(), - }); - invariant(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", - }); - invariant(transaction.memoValue, "Refusing to swap xlm without a destination memo"); - } - - // Triplecheck we're not working with an abandonseed recipient anymore - invariant( - transaction.recipient !== - 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( - convertToAppExchangePartnerKey(swapProviderConfig as CEXProviderConfig), - ); - if (unsubscribed) return; - - await swap.checkPartner((swapProviderConfig as CEXProviderConfig).signature!); - if (unsubscribed) return; - - await swap.processTransaction(Buffer.from(swapResult.binaryPayload, "hex"), estimatedFees); - if (unsubscribed) return; - const goodSign = (() => { - const sig = secp256k1.Signature.fromCompact(Buffer.from(swapResult.signature, "hex")); - return Buffer.from(sig.toDERRawBytes()); - })(); - await swap.checkTransactionSignature(goodSign); - if (unsubscribed) return; - const mainPayoutCurrency = getAccountCurrency(payoutAccount); - invariant(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 = 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 getCurrencyExchangeConfig(payoutCurrency); - - try { - await swap.validatePayoutOrAsset( - payoutAddressConfig, - payoutAddressConfigSignature, - payoutAddressParameters, - ); - } catch (e) { - if (e instanceof TransportStatusError && e.statusCode === 0x6a83) { - throw new WrongDeviceForAccountPayout(undefined, { - accountName: getDefaultAccountName(payoutAccount), - }); - } - - throw e; - } - - if (unsubscribed) return; - const mainRefundCurrency = getAccountCurrency(refundAccount); - invariant(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 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 decodePayloadProtobuf(swapResult.binaryPayload); - amountExpectedTo = new BigNumber(decodePayload.amountToWallet.toString()); - magnitudeAwareRate = transaction.amount && amountExpectedTo.dividedBy(transaction.amount); - } - - let amountExpectedFrom; - if (swapResult.binaryPayload) { - const decodePayload = await decodePayloadProtobuf(swapResult.binaryPayload); - amountExpectedFrom = new 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 TransportStatusError && e.statusCode === 0x6a83) { - throw new WrongDeviceForAccountRefund(undefined, { - accountName: getDefaultAccountName(refundAccount), - }); - } - - throw e; - } - - if (unsubscribed) return; - ignoreTransportError = true; - await swap.signCoinTransaction(); - }).catch(e => { - if (ignoreTransportError) return; - - if (e instanceof TransportStatusError && e.statusCode === 0x6a84) { - throw new TransactionRefusedOnDevice("", { step: "SIGN_COIN_TRANSACTION" }); - } - - throw e; - }); - if (!swapId) return; - log("swap", "awaiting device disconnection"); - await 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; - }; - }); -}; - -export default initSwap; diff --git a/libs/ledger-live-common/src/exchange/swap/maybeKeepTronAccountAlive.ts b/libs/ledger-live-common/src/exchange/swap/maybeKeepTronAccountAlive.ts deleted file mode 100644 index 3d86a367bc7..00000000000 --- a/libs/ledger-live-common/src/exchange/swap/maybeKeepTronAccountAlive.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { MaybeKeepTronAccountAlive } from "@ledgerhq/errors"; -import { SwapTransactionType } from "./types"; -export const maybeKeepTronAccountAlive = ( - swapTransaction: SwapTransactionType, -): Error | undefined => { - const isTron = swapTransaction?.swap?.from?.currency?.id === "tron"; - const balance = swapTransaction.swap.from.account?.balance; - const swapAmount = swapTransaction.swap.from?.amount; - - if ( - isTron && - balance && - swapAmount?.lte(balance) && - balance.minus(swapAmount || 0).lt(1_200_000) // keep 1.1 TRX for fees and 0.1 TRX for keeping the account alive - ) { - return new MaybeKeepTronAccountAlive("PREVENT_RECEIVING_TRC20_ON_EMPTY_ACCOUNT", { - links: [ - "https://support.ledger.com/hc/en-us/articles/6516823445533-Activate-Tron-account-to-send-or-receive-Tron-tokens?support=true", - ], - }); - } -}; diff --git a/libs/ledger-live-common/src/exchange/swap/maybeTezosAccountUnrevealedAccount.ts b/libs/ledger-live-common/src/exchange/swap/maybeTezosAccountUnrevealedAccount.ts deleted file mode 100644 index d3e56303334..00000000000 --- a/libs/ledger-live-common/src/exchange/swap/maybeTezosAccountUnrevealedAccount.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SwapTransactionType } from "./types"; - -export const maybeTezosAccountUnrevealedAccount = ( - swapTransaction: SwapTransactionType, -): Error | undefined => { - if ( - swapTransaction?.transaction?.family == "tezos" && - swapTransaction?.transaction?.estimatedFees && - !swapTransaction?.transaction?.fees?.eq(swapTransaction?.transaction?.estimatedFees) - ) { - const tezosError = new Error("Cannot swap with an unrevealed Tezos account"); - tezosError.name = "TezosUnrevealedAccount"; - return tezosError; - } -}; diff --git a/libs/ledger-live-common/src/exchange/swap/maybeTronEmptyAccount.ts b/libs/ledger-live-common/src/exchange/swap/maybeTronEmptyAccount.ts deleted file mode 100644 index bdf5d15aea4..00000000000 --- a/libs/ledger-live-common/src/exchange/swap/maybeTronEmptyAccount.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SwapTransactionType } from "./types"; -import { TronEmptyAccount } from "@ledgerhq/errors"; -export const maybeTronEmptyAccount = (swapTransaction: SwapTransactionType): Error | undefined => { - if ( - swapTransaction?.swap?.to?.currency?.type === "TokenCurrency" && - swapTransaction?.swap?.to?.currency?.tokenType === "trc20" && - swapTransaction?.swap?.to?.parentAccount?.balance.lt(100_000) - ) { - return new TronEmptyAccount("PREVENT_RECEIVING_TRC20_ON_EMPTY_ACCOUNT", { - links: [ - "https://support.ledger.com/hc/en-us/articles/6516823445533-Activate-Tron-account-to-send-or-receive-Tron-tokens?support=true", - ], - }); - } -}; diff --git a/libs/ledger-live-common/src/exchange/swap/mock.ts b/libs/ledger-live-common/src/exchange/swap/mock.ts index 52bf615b519..2d1d40e016d 100644 --- a/libs/ledger-live-common/src/exchange/swap/mock.ts +++ b/libs/ledger-live-common/src/exchange/swap/mock.ts @@ -1,5 +1,4 @@ import { BigNumber } from "bignumber.js"; -import { Observable, of } from "rxjs"; import { getAccountCurrency } from "../../account"; import { formatCurrencyUnit } from "../../currencies"; import { SwapExchangeRateAmountTooHigh, SwapExchangeRateAmountTooLow } from "../../errors"; @@ -10,7 +9,6 @@ import type { GetProviders, PostSwapAccepted, PostSwapCancelled, - SwapRequestEvent, } from "./types"; import type { Transaction } from "../../generated/types"; import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; @@ -108,21 +106,6 @@ export const mockGetExchangeRates = async ( ]; }; -export const mockInitSwap = ( - exchange: ExchangeSwap, - exchangeRate: ExchangeRate, - transaction: Transaction, -): Observable => { - return of({ - type: "init-swap-result", - initSwapResult: { - transaction, - swapId: "mockedSwapId", - magnitudeAwareRate: new BigNumber(50000), - }, - }); -}; - // Need to understand how and why this gets used export const mockGetProviders: GetProviders = async () => { //Fake delay to show loading UI diff --git a/libs/ledger-live-common/src/exchange/swap/types.ts b/libs/ledger-live-common/src/exchange/swap/types.ts index 4a6241c0ff3..dbd76924242 100644 --- a/libs/ledger-live-common/src/exchange/swap/types.ts +++ b/libs/ledger-live-common/src/exchange/swap/types.ts @@ -3,7 +3,7 @@ import { CryptoCurrency, CryptoOrTokenCurrency, TokenCurrency } from "@ledgerhq/ import { Account, AccountLike, AccountRaw, AccountRawLike, Operation } from "@ledgerhq/types-live"; import { BigNumber } from "bignumber.js"; import { Result as UseBridgeTransactionResult } from "../../bridge/useBridgeTransaction"; -import { Transaction, TransactionRaw } from "../../generated/types"; +import { Transaction } from "../../generated/types"; export type { SwapLiveError } from "@ledgerhq/wallet-api-exchange-module"; @@ -165,12 +165,6 @@ export type GetExchangeRates = ( exchangeObject: ExchangeObject, ) => Promise<(ExchangeRate & { expirationDate?: Date })[]>; -export type InitSwapResult = { - transaction: Transaction; - swapId: string; - magnitudeAwareRate: BigNumber; -}; - type ValidSwapStatus = "pending" | "onhold" | "expired" | "finished" | "refunded"; export type SwapStatusRequest = { @@ -226,22 +220,6 @@ export type PostSwapCancelled = (arg0: SwapStateCancelledRequest) => Promise Promise; export type GetMultipleStatus = (arg0: SwapStatusRequest[]) => Promise; -export type SwapRequestEvent = - | { type: "init-swap" } - | { - type: "init-swap-requested"; - amountExpectedTo?: string; - estimatedFees: BigNumber; - } - | { - type: "init-swap-error"; - error: Error; - swapId: string; - } - | { - type: "init-swap-result"; - initSwapResult: InitSwapResult; - }; export type SwapHistorySection = { day: Date; @@ -281,20 +259,6 @@ export type SwapTransaction = Transaction & { memoType?: string; }; -export type InitSwapInput = { - exchange: ExchangeSwap; - exchangeRate: ExchangeRate; - transaction: SwapTransaction; - deviceId: string; -}; - -export type InitSwapInputRaw = { - exchange: ExchangeSwapRaw; - exchangeRate: ExchangeRateRaw; - transaction: TransactionRaw; - deviceId: string; -}; - export interface CustomMinOrMaxError extends Error { amount: BigNumber; } diff --git a/libs/ledger-live-common/src/hw/actions/initSwap.ts b/libs/ledger-live-common/src/hw/actions/initSwap.ts deleted file mode 100644 index a3561663841..00000000000 --- a/libs/ledger-live-common/src/hw/actions/initSwap.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { log } from "@ledgerhq/logs"; -import React, { useEffect, useState, useMemo } from "react"; -import { concat, Observable, of } from "rxjs"; -import { catchError, scan, tap } from "rxjs/operators"; -import { getMainAccount } from "../../account"; -import type { - ExchangeSwap, - ExchangeRate, - InitSwapInput, - InitSwapResult, - SwapRequestEvent, - SwapTransaction, -} from "../../exchange/swap/types"; -import type { ConnectAppEvent, Input as ConnectAppInput } from "../connectApp"; -import type { AppRequest, AppState } from "./app"; -import { createAction as createAppAction } from "./app"; -import type { Action, Device } from "./types"; -import type { TransactionStatus } from "../../generated/types"; - -type State = { - initSwapResult: InitSwapResult | null | undefined; - initSwapRequested: boolean; - initSwapError: Error | null | undefined; - swapId?: string; - error?: Error; - isLoading: boolean; - freezeReduxDevice: boolean; -}; - -type InitSwapState = AppState & State; - -type InitSwapRequest = { - exchange: ExchangeSwap; - exchangeRate: ExchangeRate; - transaction: SwapTransaction; - requireLatestFirmware?: boolean; - status?: TransactionStatus; - device?: React.RefObject; -}; - -type Result = - | { - initSwapResult: InitSwapResult; - } - | { - initSwapError: Error; - swapId?: string; - }; - -type InitSwapAction = Action; - -const mapResult = ({ - initSwapResult, - initSwapError, - swapId, -}: InitSwapState): Result | null | undefined => - initSwapResult - ? { - initSwapResult, - } - : initSwapError - ? { - initSwapError, - swapId, - } - : null; - -const initialState: State = { - initSwapResult: null, - initSwapError: null, - swapId: undefined, - initSwapRequested: false, - isLoading: true, - freezeReduxDevice: false, -}; - -const reducer = (state: State, e: SwapRequestEvent) => { - switch (e.type) { - case "init-swap": - return { ...state, freezeReduxDevice: true }; - - case "init-swap-error": - return { - ...state, - initSwapError: e.error, - swapId: e.swapId, - isLoading: false, - }; - - case "init-swap-requested": - return { - ...state, - initSwapRequested: true, - isLoading: false, - amountExpectedTo: e.amountExpectedTo, - estimatedFees: e.estimatedFees, - }; - - case "init-swap-result": - return { - ...state, - initSwapResult: e.initSwapResult, - isLoading: false, - }; - } -}; - -function useFrozenValue(value: T, frozen: boolean): T { - const [state, setState] = useState(value); - useEffect(() => { - if (!frozen) { - setState(value); - } - }, [value, frozen]); - return state; -} - -export const createAction = ( - connectAppExec: (arg0: ConnectAppInput) => Observable, - initSwapExec: (arg0: InitSwapInput) => Observable, -): InitSwapAction => { - const useHook = ( - reduxDevice: Device | null | undefined, - initSwapRequest: InitSwapRequest, - ): InitSwapState => { - const [state, setState] = useState(initialState); - const reduxDeviceFrozen = useFrozenValue(reduxDevice, state.freezeReduxDevice); - - const { exchange, exchangeRate, transaction, requireLatestFirmware } = initSwapRequest; - - const { fromAccount, fromParentAccount, toAccount, toParentAccount } = exchange; - const mainFromAccount = getMainAccount(fromAccount, fromParentAccount); - const maintoAccount = getMainAccount(toAccount, toParentAccount); - - const request: AppRequest = useMemo(() => { - return { - appName: "Exchange", - dependencies: [ - { - account: mainFromAccount, - }, - { - account: maintoAccount, - }, - ], - requireLatestFirmware, - }; - }, [mainFromAccount, maintoAccount, requireLatestFirmware]); - const appState = createAppAction(connectAppExec).useHook(reduxDeviceFrozen, request); - const { device, opened, error } = appState; - const hasError = error || state.error; - useEffect(() => { - if (!opened || !device) { - setState({ ...initialState, isLoading: !!device }); - return; - } - - const sub = concat( - of({ - type: "init-swap", - }), - initSwapExec({ - exchange, - exchangeRate, - transaction, - deviceId: device.deviceId, - }), - ) - .pipe( - tap(e => { - log("actions-initSwap-event", e.type, e); - }), - catchError((error: Error) => - of({ - type: "init-swap-error", - error, - }), - ), - scan(reducer, { ...initialState, isLoading: !hasError }), - ) - .subscribe(setState); - return () => { - sub.unsubscribe(); - }; - }, [exchange, exchangeRate, transaction, device, opened, hasError]); - - return { - ...appState, - ...state, - } as InitSwapState; - }; - - return { - useHook, - mapResult, - }; -}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8ad1db6edf..b6e1c24e314 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1584,9 +1584,6 @@ importers: react-native-image-picker: specifier: 8.2.0 version: 8.2.0(react-native@0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0))(react@19.0.0) - react-native-keyboard-aware-scroll-view: - specifier: 0.9.5 - version: 0.9.5(react-native@0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0)) react-native-keychain: specifier: 10.0.0 version: 10.0.0 @@ -32454,11 +32451,6 @@ packages: react: '*' react-native: '*' - react-native-iphone-x-helper@1.3.1: - resolution: {integrity: sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==} - peerDependencies: - react-native: '>=0.42.0' - react-native-is-edge-to-edge@1.1.7: resolution: {integrity: sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==} peerDependencies: @@ -32471,11 +32463,6 @@ packages: react: '*' react-native: '*' - react-native-keyboard-aware-scroll-view@0.9.5: - resolution: {integrity: sha512-XwfRn+T/qBH9WjTWIBiJD2hPWg0yJvtaEw6RtPCa5/PYHabzBaWxYBOl0usXN/368BL1XktnZPh8C2lmTpOREA==} - peerDependencies: - react-native: '>=0.48.4' - react-native-keychain@10.0.0: resolution: {integrity: sha512-YzPKSAnSzGEJ12IK6CctNLU79T1W15WDrElRQ+1/FsOazGX9ucFPTQwgYe8Dy8jiSEDJKM4wkVa3g4lD2Z+Pnw==} engines: {node: '>=16'} @@ -69122,10 +69109,6 @@ snapshots: react: 19.0.0 react-native: 0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0) - react-native-iphone-x-helper@1.3.1(react-native@0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0)): - dependencies: - react-native: 0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0) - react-native-is-edge-to-edge@1.1.7(react-native@0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@15.1.0(typescript@5.4.3))(@types/react@19.0.14)(metro-transform-worker@0.81.5)(metro@0.81.5)(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 @@ -69146,12 +69129,6 @@ snapshots: react: 19.0.0 react-native: 0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0) - react-native-keyboard-aware-scroll-view@0.9.5(react-native@0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0)): - dependencies: - prop-types: 15.8.1 - react-native: 0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0) - react-native-iphone-x-helper: 1.3.1(react-native@0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0)) - react-native-keychain@10.0.0: {} react-native-launch-arguments@4.0.2(react-native@0.79.7(patch_hash=461d44d8d4f5eecdc7a3b2b8cc6a48cfbe0c4a73d6d130be09381aba6618e877)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(typescript@5.4.3))(@types/react@19.0.14)(react@19.0.0))(react@19.0.0):