From 49b539618f11a2b7846ab92c628c0d2ad4f9fe4f Mon Sep 17 00:00:00 2001 From: Matthew Maxwell <44885822+maxwellmattryan@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:07:45 -0500 Subject: [PATCH] enhancement: handle Ledger EVM transactions when blind signing is disabled (#434) * Add Ledger verification prompt for L2 tx * Refactor function for calculating tx value * Align UI wording with that of the ETH Ledger app * Cleanup code * Fix type check * Add logic for getting app settings * Get logic hooked up * Fix imports * Make changes per PR review * Bring close popup outside of if statement * Add logic for detecting and displaying blind signing popup * Refine logic around the send flow * Remove global polling * Fix logic around polling for Ethereum app settings * Remove unnecessary callback * refactor: combine ledger status * refactor: cleanup files * rename ledgerNanoStatus -> ledgerNanoState * refactor: more name improvements * fix use of app name * Rename to Ledger device state * Fix conditional in component * Use alert box from UI kit * Cleanup import * fix: code quality * Fix PR comments * Fix linting issue * fix: ledger api polling Co-authored-by: Matthew Maxwell * chore: update polling intervals * Change constant name * Cleanup logic and bugs for EVM * Fix blind signing for L1 transactions * Make small improvements --------- Co-authored-by: Nicole O'Brien Co-authored-by: Matthew Maxwell --- .../popups/ConnectLedgerPopup.svelte | 4 +- .../EnableLedgerBlindSigningPopup.svelte | 59 +++---- .../lib/electron/processes/ledger.process.ts | 16 +- .../lib/electron/processes/main.process.ts | 19 ++- .../lib/electron/utils/ledger.utils.ts | 13 ++ .../desktop/views/dashboard/Dashboard.svelte | 6 +- .../views/ConnectLedgerDeviceDrawer.svelte | 4 +- .../views/InstallLedgerView.svelte | 6 +- .../views/ClaimFinderView.svelte | 8 +- .../actions/initialiseOnboardingProfile.ts | 4 +- .../platform-event-map.interface.ts | 2 + .../ledger/actions/checkOrConnectLedger.ts | 4 +- .../actions/getAndUpdateLedgerDeviceState.ts | 32 ++++ .../actions/getAndUpdateLedgerNanoStatus.ts | 21 --- .../ledger/actions/getLedgerDeviceStatus.ts | 30 ---- .../src/lib/core/ledger/actions/index.ts | 5 +- .../ledger/actions/pollLedgerDeviceState.ts | 29 ++++ .../ledger/actions/pollLedgerNanoStatus.ts | 29 ---- .../actions/promptUserToConnectLedger.ts | 22 --- .../lib/core/ledger/classes/ledger.class.ts | 152 ++++++++++++------ ...ult-ledger-api-request-options.constant.ts | 5 +- ...ger-device-state-poll-interval.constant.ts | 5 + ...dger-nano-status-poll-interval.constant.ts | 5 - .../src/lib/core/ledger/constants/index.ts | 2 +- .../shared/src/lib/core/ledger/enums/index.ts | 1 + .../ledger/enums/ledger-api-method.enum.ts | 1 + .../ledger-connection-state.enum.ts} | 0 ...uctLedgerNanoStatusPollingConfiguration.ts | 16 -- .../src/lib/core/ledger/helpers/index.ts | 1 - .../base-ledger-app-settings.interface.ts | 4 + .../ledger/interfaces/app-settings/index.ts | 3 + .../ledger-app-settings.interface.ts | 8 + .../ledger-ethereum-app-settings.interface.ts | 7 + .../src/lib/core/ledger/interfaces/index.ts | 6 +- .../interfaces/ledger-api-bridge.interface.ts | 1 + ...-state-polling-configuration.interface.ts} | 2 +- .../ledger-device-state.interface.ts | 12 ++ .../src/lib/core/ledger/stores/index.ts | 4 +- .../is-polling-ledger-device-state.stores.ts | 3 + .../is-polling-ledger-device-status.store.ts | 3 - .../stores/ledger-connection-state.store.ts | 8 +- .../stores/ledger-device-state.store.ts | 19 +++ .../types/ledger-api-request-response.type.ts | 5 +- .../ledger/utils/buildLedgerDeviceState.ts | 27 ++++ .../utils/determineLedgerConnectionState.ts | 17 +- .../core/ledger/utils/handleLedgerError.ts | 21 ++- .../shared/src/lib/core/ledger/utils/index.ts | 3 +- ...isBlindSigningRequiredForEvmTransaction.ts | 5 + .../lib/core/ledger/utils/isLedgerAppOpen.ts | 4 +- .../utils/openLedgerNotConnectedPopup.ts | 18 --- .../handleTransactionProgressEvent.ts | 24 ++- .../api/getLedgerNanoStatus.ts | 2 +- .../profile/actions/active-profile/login.ts | 4 +- .../profile/actions/active-profile/logout.ts | 6 +- .../actions/send/signAndSendEvmTransaction.ts | 10 +- packages/shared/src/locales/en.json | 8 +- 56 files changed, 425 insertions(+), 310 deletions(-) create mode 100644 packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerDeviceState.ts delete mode 100644 packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerNanoStatus.ts delete mode 100644 packages/shared/src/lib/core/ledger/actions/getLedgerDeviceStatus.ts create mode 100644 packages/shared/src/lib/core/ledger/actions/pollLedgerDeviceState.ts delete mode 100644 packages/shared/src/lib/core/ledger/actions/pollLedgerNanoStatus.ts delete mode 100644 packages/shared/src/lib/core/ledger/actions/promptUserToConnectLedger.ts create mode 100644 packages/shared/src/lib/core/ledger/constants/default-ledger-device-state-poll-interval.constant.ts delete mode 100644 packages/shared/src/lib/core/ledger/constants/default-ledger-nano-status-poll-interval.constant.ts rename packages/shared/src/lib/core/ledger/{interfaces/ledger-connection-state.interface.ts => enums/ledger-connection-state.enum.ts} (100%) delete mode 100644 packages/shared/src/lib/core/ledger/helpers/deconstructLedgerNanoStatusPollingConfiguration.ts create mode 100644 packages/shared/src/lib/core/ledger/interfaces/app-settings/base-ledger-app-settings.interface.ts create mode 100644 packages/shared/src/lib/core/ledger/interfaces/app-settings/index.ts create mode 100644 packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-app-settings.interface.ts create mode 100644 packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-ethereum-app-settings.interface.ts rename packages/shared/src/lib/core/ledger/interfaces/{ledger-nano-status-polling-configuration.interface.ts => ledger-device-state-polling-configuration.interface.ts} (74%) create mode 100644 packages/shared/src/lib/core/ledger/interfaces/ledger-device-state.interface.ts create mode 100644 packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-state.stores.ts delete mode 100644 packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-status.store.ts create mode 100644 packages/shared/src/lib/core/ledger/stores/ledger-device-state.store.ts create mode 100644 packages/shared/src/lib/core/ledger/utils/buildLedgerDeviceState.ts create mode 100644 packages/shared/src/lib/core/ledger/utils/isBlindSigningRequiredForEvmTransaction.ts delete mode 100644 packages/shared/src/lib/core/ledger/utils/openLedgerNotConnectedPopup.ts diff --git a/packages/desktop/components/popups/ConnectLedgerPopup.svelte b/packages/desktop/components/popups/ConnectLedgerPopup.svelte index 9a44be8f36..52bf595173 100644 --- a/packages/desktop/components/popups/ConnectLedgerPopup.svelte +++ b/packages/desktop/components/popups/ConnectLedgerPopup.svelte @@ -5,7 +5,7 @@ LedgerAppName, LedgerConnectionState, determineLedgerConnectionState, - ledgerNanoStatus, + ledgerDeviceState, } from '@core/ledger' import { isFunction } from '@core/utils' import { closePopup } from '@desktop/auxiliary/popup' @@ -15,7 +15,7 @@ export let onCancel: () => void export let onContinue: () => void - $: ledgerConnectionState = determineLedgerConnectionState($ledgerNanoStatus, ledgerAppName) + $: ledgerConnectionState = determineLedgerConnectionState($ledgerDeviceState, ledgerAppName) $: isNotConnected = ledgerConnectionState === LedgerConnectionState.NotConnected $: isLocked = ledgerConnectionState === LedgerConnectionState.Locked diff --git a/packages/desktop/components/popups/EnableLedgerBlindSigningPopup.svelte b/packages/desktop/components/popups/EnableLedgerBlindSigningPopup.svelte index 9577512c0d..d6b8d1502b 100644 --- a/packages/desktop/components/popups/EnableLedgerBlindSigningPopup.svelte +++ b/packages/desktop/components/popups/EnableLedgerBlindSigningPopup.svelte @@ -1,50 +1,35 @@ {localize('popups.enableLedgerBlindSigning.title')}
-
- - - {localize('popups.enableLedgerBlindSigning.info')} - -
-
- {#each STEPS as step} - - {step}. {localize(`popups.enableLedgerBlindSigning.step_${step}`)} - - {/each} -
+ + {#each STEPS as step} + + {step}. {localize(`popups.enableLedgerBlindSigning.step_${step}`, { appName })} + + {/each}
diff --git a/packages/desktop/lib/electron/processes/ledger.process.ts b/packages/desktop/lib/electron/processes/ledger.process.ts index 54e8a032c7..4fb60a99c9 100644 --- a/packages/desktop/lib/electron/processes/ledger.process.ts +++ b/packages/desktop/lib/electron/processes/ledger.process.ts @@ -8,7 +8,13 @@ import { LedgerApiMethod } from '@core/ledger/enums' import type { ILedgerProcessMessage } from '../interfaces/ledger-process-message.interface' -import { closeTransport, getEvmAddress, openTransport, signTransactionData } from '../utils/ledger.utils' +import { + closeTransport, + getEthereumAppSettings, + getEvmAddress, + openTransport, + signTransactionData, +} from '../utils/ledger.utils' /** * CAUTION: `process` is initialized using `utilityProcess.fork()`. @@ -29,6 +35,10 @@ async function messageHandler(message: ILedgerProcessMessage): Promise { data = await getEvmAddress(payload[0] as string) break } + case LedgerApiMethod.GetEthereumAppSettings: { + data = await getEthereumAppSettings() + break + } case LedgerApiMethod.SignEvmTransaction: { data = await signTransactionData(payload[0] as string, payload[1] as string) break @@ -37,10 +47,10 @@ async function messageHandler(message: ILedgerProcessMessage): Promise { break } - await closeTransport() - process.parentPort.postMessage({ method, payload: data }) } catch (error) { process.parentPort.postMessage({ error }) + } finally { + await closeTransport() } } diff --git a/packages/desktop/lib/electron/processes/main.process.ts b/packages/desktop/lib/electron/processes/main.process.ts index 33743a9c28..21e9e6c838 100644 --- a/packages/desktop/lib/electron/processes/main.process.ts +++ b/packages/desktop/lib/electron/processes/main.process.ts @@ -1,16 +1,16 @@ // Modules to control application life and create native browser window import { app, + BrowserWindow, dialog, ipcMain, - shell, - BrowserWindow, - session, - utilityProcess, nativeTheme, PopupOptions, - UtilityProcess, powerMonitor, + session, + shell, + utilityProcess, + UtilityProcess, } from 'electron' import { WebPreferences } from 'electron/main' import path from 'path' @@ -296,6 +296,9 @@ ipcMain.on('start-ledger-process', () => { case LedgerApiMethod.GenerateEvmAddress: windows.main.webContents.send('evm-address', payload) break + case LedgerApiMethod.GetEthereumAppSettings: + windows.main.webContents.send('ethereum-app-settings', payload) + break case LedgerApiMethod.SignEvmTransaction: windows.main.webContents.send('evm-signed-transaction', payload) break @@ -317,12 +320,14 @@ ipcMain.on(LedgerApiMethod.GenerateEvmAddress, (_e, bip32Path, verify) => { ledgerProcess?.postMessage({ method: LedgerApiMethod.GenerateEvmAddress, payload: [bip32Path, verify] }) }) +ipcMain.on(LedgerApiMethod.GetEthereumAppSettings, () => { + ledgerProcess?.postMessage({ method: LedgerApiMethod.GetEthereumAppSettings }) +}) + ipcMain.on(LedgerApiMethod.SignEvmTransaction, (_e, transactionHex, bip32Path) => { ledgerProcess?.postMessage({ method: LedgerApiMethod.SignEvmTransaction, payload: [transactionHex, bip32Path] }) }) -export const getWindow = (windowName: string): BrowserWindow => windows[windowName] - export function getOrInitWindow(windowName: string): BrowserWindow { if (!windows[windowName]) { if (windowName === 'main') { diff --git a/packages/desktop/lib/electron/utils/ledger.utils.ts b/packages/desktop/lib/electron/utils/ledger.utils.ts index 2b77cbca46..ec98d3290c 100644 --- a/packages/desktop/lib/electron/utils/ledger.utils.ts +++ b/packages/desktop/lib/electron/utils/ledger.utils.ts @@ -1,6 +1,7 @@ import AppEth from '@ledgerhq/hw-app-eth' import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' import { listen } from '@ledgerhq/logs' +import type { ILedgerEthereumAppSettings } from '@core/ledger/interfaces' // import specifity for core modules to prevent circular dependencies import { IEvmTransactionSignature } from '@core/layer-2/interfaces' @@ -24,6 +25,18 @@ export async function closeTransport(): Promise { } } +export async function getEthereumAppSettings(): Promise { + const appEth = new AppEth(transport) + const settings = await appEth.getAppConfiguration() + return { + version: settings.version, + blindSigningEnabled: Boolean(settings.arbitraryDataEnabled), + erc20ProvisioningNecessary: Boolean(settings.erc20ProvisioningNecessary), + starkEnabled: Boolean(settings.starkEnabled), + starkv2Supported: Boolean(settings.starkv2Supported), + } +} + export async function getEvmAddress(bip32Path: string): Promise<{ evmAddress: string; bip32Path: string }> { const appEth = new AppEth(transport) const data = await appEth.getAddress(bip32Path) diff --git a/packages/desktop/views/dashboard/Dashboard.svelte b/packages/desktop/views/dashboard/Dashboard.svelte index b13fbd803f..39cfe13eea 100644 --- a/packages/desktop/views/dashboard/Dashboard.svelte +++ b/packages/desktop/views/dashboard/Dashboard.svelte @@ -2,7 +2,6 @@ import { selectedAccount, selectedAccountIndex } from '@core/account/stores' import { Platform } from '@core/app' import { clearLayer2TokensPoll, pollLayer2Tokens } from '@core/layer-2/actions' - import { stopPollingLedgerNanoStatus } from '@core/ledger' import { addNftsToDownloadQueue, downloadNextNftInQueue, @@ -10,7 +9,7 @@ } from '@core/nfts/actions' import { downloadingNftId, nftDownloadQueue, resetNftDownloadQueue, selectedAccountNfts } from '@core/nfts/stores' import { logout, reflectLockedStronghold } from '@core/profile/actions' - import { hasStrongholdLocked, isActiveLedgerProfile } from '@core/profile/stores' + import { hasStrongholdLocked } from '@core/profile/stores' import { appRouter, dashboardRoute } from '@core/router' import features from '@features/features' import { Idle } from '@ui' @@ -64,9 +63,6 @@ onDestroy(() => { Platform.DeepLinkManager.clearDeepLinkRequest() - if ($isActiveLedgerProfile) { - stopPollingLedgerNanoStatus() - } clearLayer2TokensPoll() }) diff --git a/packages/desktop/views/dashboard/drawers/network-config/views/ConnectLedgerDeviceDrawer.svelte b/packages/desktop/views/dashboard/drawers/network-config/views/ConnectLedgerDeviceDrawer.svelte index 893b9d6286..206d88d4aa 100644 --- a/packages/desktop/views/dashboard/drawers/network-config/views/ConnectLedgerDeviceDrawer.svelte +++ b/packages/desktop/views/dashboard/drawers/network-config/views/ConnectLedgerDeviceDrawer.svelte @@ -6,7 +6,7 @@ LedgerAppName, LedgerConnectionState, determineLedgerConnectionState, - ledgerNanoStatus, + ledgerDeviceState, } from '@core/ledger' import { Router } from '@core/router' import { Animation, FontWeight, Icon, Pane, Text, TextType } from '@ui' @@ -16,7 +16,7 @@ const LOCALE_BASE_PATH = 'views.dashboard.drawers.networkConfig.connectLedgerDevice' - $: ledgerConnectionState = determineLedgerConnectionState($ledgerNanoStatus, LedgerAppName.Ethereum) + $: ledgerConnectionState = determineLedgerConnectionState($ledgerDeviceState, LedgerAppName.Ethereum) $: isConnectedAndUnlocked = ledgerConnectionState !== LedgerConnectionState.NotConnected && ledgerConnectionState !== LedgerConnectionState.Locked diff --git a/packages/desktop/views/onboarding/views/create-from-ledger/views/InstallLedgerView.svelte b/packages/desktop/views/onboarding/views/create-from-ledger/views/InstallLedgerView.svelte index b560f8e8fc..42cf2477ea 100644 --- a/packages/desktop/views/onboarding/views/create-from-ledger/views/InstallLedgerView.svelte +++ b/packages/desktop/views/onboarding/views/create-from-ledger/views/InstallLedgerView.svelte @@ -1,6 +1,6 @@ diff --git a/packages/desktop/views/onboarding/views/restore-profile/views/ClaimFinderView.svelte b/packages/desktop/views/onboarding/views/restore-profile/views/ClaimFinderView.svelte index f0c2f1cb4a..8a28ba7e7d 100644 --- a/packages/desktop/views/onboarding/views/restore-profile/views/ClaimFinderView.svelte +++ b/packages/desktop/views/onboarding/views/restore-profile/views/ClaimFinderView.svelte @@ -26,8 +26,8 @@ import { checkOrConnectLedger, handleLedgerError, - pollLedgerNanoStatus, - stopPollingLedgerNanoStatus, + pollLedgerDeviceState, + stopPollingLedgerDeviceState, } from '@core/ledger' import { unsubscribeFromWalletApiEvents } from '@core/profile-manager' import { closePopup } from '@desktop/auxiliary/popup' @@ -63,14 +63,14 @@ async function ledgerRaceConditionProtectionWrapper(_function: () => unknown): Promise { try { if ($isOnboardingLedgerProfile) { - stopPollingLedgerNanoStatus() + stopPollingLedgerDeviceState() } await _function() } catch (err) { console.error('Error in ledgerRaceConditionProtectionWrapper') } finally { if ($isOnboardingLedgerProfile) { - pollLedgerNanoStatus() + pollLedgerDeviceState() } } } diff --git a/packages/shared/src/lib/contexts/onboarding/actions/initialiseOnboardingProfile.ts b/packages/shared/src/lib/contexts/onboarding/actions/initialiseOnboardingProfile.ts index d994c1fcbc..33996c4416 100644 --- a/packages/shared/src/lib/contexts/onboarding/actions/initialiseOnboardingProfile.ts +++ b/packages/shared/src/lib/contexts/onboarding/actions/initialiseOnboardingProfile.ts @@ -1,5 +1,5 @@ import { get } from 'svelte/store' -import { stopPollingLedgerNanoStatus } from '@core/ledger' +import { stopPollingLedgerDeviceState } from '@core/ledger' import { destroyProfileManager, profileManager } from '@core/profile-manager' import { waitForPreviousManagerToBeDestroyed } from '@core/profile/utils' import { buildInitialOnboardingProfile } from '../helpers' @@ -13,7 +13,7 @@ export async function initialiseOnboardingProfile(): Promise { if (get(profileManager)) { if (get(isOnboardingLedgerProfile)) { - stopPollingLedgerNanoStatus() + stopPollingLedgerDeviceState() } await destroyProfileManager() } diff --git a/packages/shared/src/lib/core/app/interfaces/platform-event-map.interface.ts b/packages/shared/src/lib/core/app/interfaces/platform-event-map.interface.ts index e8f59b4204..a457b7623e 100644 --- a/packages/shared/src/lib/core/app/interfaces/platform-event-map.interface.ts +++ b/packages/shared/src/lib/core/app/interfaces/platform-event-map.interface.ts @@ -1,5 +1,6 @@ import { IAppUpdateDownloadProgress, IAppVersionDetails, INFTDownloadState } from '.' import { IEvmAddress, IEvmTransactionSignature } from '@core/layer-2/interfaces' +import { ILedgerEthereumAppSettings } from '@core/ledger/interfaces' export interface IPlatformEventMap { 'menu-logout': void @@ -17,6 +18,7 @@ export interface IPlatformEventMap { 'notification-activated': unknown 'nft-download-done': INFTDownloadState 'nft-download-interrupted': INFTDownloadState + 'ethereum-app-settings': ILedgerEthereumAppSettings 'evm-address': IEvmAddress 'evm-signed-transaction': IEvmTransactionSignature 'ledger-error': Error diff --git a/packages/shared/src/lib/core/ledger/actions/checkOrConnectLedger.ts b/packages/shared/src/lib/core/ledger/actions/checkOrConnectLedger.ts index 218397bb5b..20ebc4042d 100644 --- a/packages/shared/src/lib/core/ledger/actions/checkOrConnectLedger.ts +++ b/packages/shared/src/lib/core/ledger/actions/checkOrConnectLedger.ts @@ -1,7 +1,7 @@ import { openPopup, PopupId, popupState } from '../../../../../../desktop/lib/auxiliary/popup' import { get } from 'svelte/store' import { handleError } from '@core/error/handlers/handleError' -import { determineLedgerConnectionState, LedgerAppName, LedgerConnectionState, ledgerNanoStatus } from '..' +import { determineLedgerConnectionState, LedgerAppName, LedgerConnectionState, ledgerDeviceState } from '..' export function checkOrConnectLedger( callback: () => Promise = async (): Promise => {}, @@ -19,7 +19,7 @@ export function checkOrConnectLedger( } } try { - const ledgerConnectionState = determineLedgerConnectionState(get(ledgerNanoStatus), ledgerAppName) + const ledgerConnectionState = determineLedgerConnectionState(get(ledgerDeviceState), ledgerAppName) const ledgerConnected = ledgerConnectionState === LedgerConnectionState.CorrectAppOpen if (ledgerConnected) { return callback() diff --git a/packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerDeviceState.ts b/packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerDeviceState.ts new file mode 100644 index 0000000000..ae3a984839 --- /dev/null +++ b/packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerDeviceState.ts @@ -0,0 +1,32 @@ +import { profileManager as _profileManager } from '@core/profile-manager/stores' +import { getLedgerNanoStatus } from '@lib/core/profile-manager/api' +import { Ledger } from '../classes' +import { resetLedgerDeviceState, setLedgerDeviceState } from '../stores' +import { sleep } from '@core/utils' + +let isMakingRequest: boolean = false + +export async function getAndUpdateLedgerDeviceState( + profileManager = _profileManager, + forwardErrors = false +): Promise { + try { + if (!isMakingRequest) { + isMakingRequest = true + const ledgerNanoStatus = await getLedgerNanoStatus(profileManager) + // sleep to make sure ledger process on iota sdk is wrapped up + await sleep(50) + const ethereumAppSettings = await Ledger.getEthereumAppSettings() + setLedgerDeviceState(ledgerNanoStatus, ethereumAppSettings) + } + } catch (err) { + resetLedgerDeviceState() + if (forwardErrors) { + return Promise.reject(err) + } else { + console.error(err) + } + } finally { + isMakingRequest = false + } +} diff --git a/packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerNanoStatus.ts b/packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerNanoStatus.ts deleted file mode 100644 index 731586f459..0000000000 --- a/packages/shared/src/lib/core/ledger/actions/getAndUpdateLedgerNanoStatus.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { profileManager as _profileManager } from '@core/profile-manager' -import { getLedgerNanoStatus } from '@lib/core/profile-manager/api' - -import { resetLedgerNanoStatus, updateLedgerNanoStatus } from '../stores' - -export async function getAndUpdateLedgerNanoStatus( - profileManager = _profileManager, - forwardErrors = false -): Promise { - try { - const ledgerNanoStatusResponse = await getLedgerNanoStatus(profileManager) - updateLedgerNanoStatus(ledgerNanoStatusResponse) - } catch (err) { - resetLedgerNanoStatus() - if (forwardErrors) { - return Promise.reject(err) - } else { - console.error(err) - } - } -} diff --git a/packages/shared/src/lib/core/ledger/actions/getLedgerDeviceStatus.ts b/packages/shared/src/lib/core/ledger/actions/getLedgerDeviceStatus.ts deleted file mode 100644 index 2bf7df3d30..0000000000 --- a/packages/shared/src/lib/core/ledger/actions/getLedgerDeviceStatus.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { get } from 'svelte/store' -import { getLedgerNanoStatus } from '@lib/core/profile-manager/api' -import { PopupId, closePopup, popupState } from '../../../../../../desktop/lib/auxiliary/popup' -import { ledgerNanoStatus, updateLedgerNanoStatus } from '../stores' - -export async function getLedgerDeviceStatus( - onConnected = (): void => {}, - onDisconnected = (): void => {}, - onError = (): void => {} -): Promise { - try { - const status = await getLedgerNanoStatus() - updateLedgerNanoStatus(status) - - if (get(ledgerNanoStatus).connected) { - const isLedgerNotConnectedPopupOpened = - get(popupState).active && get(popupState).id === PopupId.ConnectLedger - - if (isLedgerNotConnectedPopupOpened) { - closePopup() - } - - onConnected() - } else { - onDisconnected() - } - } catch (err) { - onError() - } -} diff --git a/packages/shared/src/lib/core/ledger/actions/index.ts b/packages/shared/src/lib/core/ledger/actions/index.ts index d4849b3af9..5385fd4900 100644 --- a/packages/shared/src/lib/core/ledger/actions/index.ts +++ b/packages/shared/src/lib/core/ledger/actions/index.ts @@ -1,5 +1,4 @@ export * from './checkOrConnectLedger' -export * from './getLedgerDeviceStatus' -export * from './pollLedgerNanoStatus' -export * from './promptUserToConnectLedger' +export * from './getAndUpdateLedgerDeviceState' +export * from './pollLedgerDeviceState' export * from './registerLedgerDeviceEventHandlers' diff --git a/packages/shared/src/lib/core/ledger/actions/pollLedgerDeviceState.ts b/packages/shared/src/lib/core/ledger/actions/pollLedgerDeviceState.ts new file mode 100644 index 0000000000..6e13adf4ef --- /dev/null +++ b/packages/shared/src/lib/core/ledger/actions/pollLedgerDeviceState.ts @@ -0,0 +1,29 @@ +import { get } from 'svelte/store' +import { profileManager as _profileManager } from '@core/profile-manager/stores' +import { DEFAULT_LEDGER_DEVICE_STATE_POLL_INTERVAL } from '../constants' +import { ILedgerDeviceStatePollingConfiguration } from '../interfaces' +import { isPollingLedgerDeviceState } from '../stores' +import { getAndUpdateLedgerDeviceState } from './getAndUpdateLedgerDeviceState' + +let intervalTimer: number | undefined + +export function pollLedgerDeviceState(config?: ILedgerDeviceStatePollingConfiguration): void { + const pollInterval = config?.pollInterval ?? DEFAULT_LEDGER_DEVICE_STATE_POLL_INTERVAL + const profileManager = config?.profileManager ?? _profileManager + + if (!get(isPollingLedgerDeviceState)) { + void getAndUpdateLedgerDeviceState(profileManager) + intervalTimer = window.setInterval(() => { + void getAndUpdateLedgerDeviceState(profileManager) + }, pollInterval) + isPollingLedgerDeviceState.set(true) + } +} + +export function stopPollingLedgerDeviceState(): void { + if (get(isPollingLedgerDeviceState)) { + window.clearInterval(intervalTimer) + intervalTimer = undefined + isPollingLedgerDeviceState.set(false) + } +} diff --git a/packages/shared/src/lib/core/ledger/actions/pollLedgerNanoStatus.ts b/packages/shared/src/lib/core/ledger/actions/pollLedgerNanoStatus.ts deleted file mode 100644 index bf1aba6fec..0000000000 --- a/packages/shared/src/lib/core/ledger/actions/pollLedgerNanoStatus.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { get } from 'svelte/store' - -import { deconstructLedgerNanoStatusPollingConfiguration } from '../helpers' -import { ILedgerNanoStatusPollingConfiguration } from '../interfaces' -import { isPollingLedgerDeviceStatus } from '../stores' - -import { getAndUpdateLedgerNanoStatus } from './getAndUpdateLedgerNanoStatus' - -let intervalTimer: ReturnType - -export function pollLedgerNanoStatus(config?: ILedgerNanoStatusPollingConfiguration): void { - const { pollInterval, profileManager } = deconstructLedgerNanoStatusPollingConfiguration(config) - - if (!get(isPollingLedgerDeviceStatus)) { - void getAndUpdateLedgerNanoStatus(profileManager) - intervalTimer = setInterval(() => { - void getAndUpdateLedgerNanoStatus(profileManager) - }, pollInterval) - isPollingLedgerDeviceStatus.set(true) - } -} - -export function stopPollingLedgerNanoStatus(): void { - if (get(isPollingLedgerDeviceStatus)) { - clearInterval(intervalTimer) - intervalTimer = null - isPollingLedgerDeviceStatus.set(false) - } -} diff --git a/packages/shared/src/lib/core/ledger/actions/promptUserToConnectLedger.ts b/packages/shared/src/lib/core/ledger/actions/promptUserToConnectLedger.ts deleted file mode 100644 index 75dd1b5b74..0000000000 --- a/packages/shared/src/lib/core/ledger/actions/promptUserToConnectLedger.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { get } from 'svelte/store' - -import { popupState } from '../../../../../../desktop/lib/auxiliary/popup' - -import { openLedgerNotConnectedPopup } from '../utils' - -import { getLedgerDeviceStatus } from './getLedgerDeviceStatus' -import { pollLedgerNanoStatus } from './pollLedgerNanoStatus' - -export function promptUserToConnectLedger( - onConnected = (): void => {}, - onCancel = (): void => {}, - overridePopup: boolean = false -): void { - function onDisconnected(): void { - if (!get(popupState).active || overridePopup) { - openLedgerNotConnectedPopup(onCancel, () => pollLedgerNanoStatus(), overridePopup) - } - } - - void getLedgerDeviceStatus(onConnected, onDisconnected, onCancel) -} diff --git a/packages/shared/src/lib/core/ledger/classes/ledger.class.ts b/packages/shared/src/lib/core/ledger/classes/ledger.class.ts index e7a6d87f9d..295f18730c 100644 --- a/packages/shared/src/lib/core/ledger/classes/ledger.class.ts +++ b/packages/shared/src/lib/core/ledger/classes/ledger.class.ts @@ -3,7 +3,6 @@ import { Platform } from '@core/app/classes' import { IPlatformEventMap } from '@core/app/interfaces' import { localize } from '@core/i18n' import { IEvmAddress, IEvmTransactionSignature } from '@core/layer-2/interfaces' -import { EvmTransactionData } from '@core/layer-2/types' import { calculateMaxGasFeeFromTransactionData, getAmountFromEvmTransactionValue, @@ -14,9 +13,14 @@ import { TxData } from '@ethereumjs/tx' import type { Bip44 } from '@iota/sdk/out/types' import { PopupId, openPopup } from '../../../../../../desktop/lib/auxiliary/popup' import { DEFAULT_LEDGER_API_REQUEST_OPTIONS } from '../constants' -import { LedgerApiMethod } from '../enums' +import { LedgerApiMethod, LedgerAppName } from '../enums' import { ILedgerApiBridge } from '../interfaces' import { LedgerApiRequestResponse } from '../types' +import { + ILedgerApiRequestOptions, + ILedgerEthereumAppSettings, + isBlindSigningRequiredForEvmTransaction, +} from '@core/ledger' import { EvmChainId } from '@core/network/enums' declare global { @@ -28,6 +32,24 @@ declare global { const ledgerApiBridge: ILedgerApiBridge = window['__LEDGER__'] export class Ledger { + static async getEthereumAppSettings(): Promise { + try { + return await this.callLedgerApiAsync( + () => ledgerApiBridge.makeRequest(LedgerApiMethod.GetEthereumAppSettings), + 'ethereum-app-settings', + { + timeout: 0.5 * MILLISECONDS_PER_SECOND, + } + ) + } catch (err) { + return undefined + } + } + + static async isBlindSigningEnabledForEvm(): Promise { + return Boolean((await this.getEthereumAppSettings())?.blindSigningEnabled) + } + static async generateEvmAddress(accountIndex: number, coinType: number, verify?: boolean): Promise { const bip32Path = buildBip32PathFromBip44({ coinType, @@ -41,62 +63,92 @@ export class Ledger { } static async signEvmTransaction( - txData: TxData, + transactionData: TxData, chainId: EvmChainId, - bip44: Bip44, - promptVerification = true - ): Promise { - const unsignedTransactionMessageHex = prepareEvmTransaction(txData as EvmTransactionData, chainId) - const bip32Path = buildBip32PathFromBip44(bip44) - - const maxGasFee = calculateMaxGasFeeFromTransactionData(txData) - // TODO: https://github.com/bloomwalletio/bloom/issues/432 - if (promptVerification) { - openPopup({ - id: PopupId.VerifyLedgerTransaction, - hideClose: true, - preventClose: true, - props: { - isEvmTransaction: true, - toAmount: getAmountFromEvmTransactionValue(txData?.value?.toString()).toString(), - toAddress: txData.to, - chainId, - maxGasFee, - }, - }) - } + bip44: Bip44 + ): Promise { + /* eslint-disable no-async-promise-executor */ + /* eslint-disable @typescript-eslint/no-misused-promises */ + return new Promise(async (resolve, reject) => { + const unsignedTransactionMessageHex = prepareEvmTransaction(transactionData, chainId) + const bip32Path = buildBip32PathFromBip44(bip44) + const maxGasFee = calculateMaxGasFeeFromTransactionData(transactionData) - const transactionSignature = await this.callLedgerApiAsync( - () => - ledgerApiBridge.makeRequest( - LedgerApiMethod.SignEvmTransaction, - unsignedTransactionMessageHex, - bip32Path - ), - 'evm-signed-transaction' - ) + const mustEnableBlindSigning = + isBlindSigningRequiredForEvmTransaction(transactionData) && !(await this.isBlindSigningEnabledForEvm()) + if (mustEnableBlindSigning) { + let canResolve = true + openPopup({ + id: PopupId.EnableLedgerBlindSigning, + props: { + appName: LedgerAppName.Ethereum, + onEnabled: async () => { + canResolve = false + try { + resolve(await this.signEvmTransaction(transactionData, chainId, bip44)) + } catch (err) { + reject(err) + } + }, + onClose: () => { + if (canResolve) { + canResolve = false + resolve() + } + }, + }, + }) + } else { + openPopup({ + id: PopupId.VerifyLedgerTransaction, + hideClose: true, + preventClose: true, + props: { + isEvmTransaction: true, + toAmount: getAmountFromEvmTransactionValue(transactionData?.value?.toString()), + toAddress: transactionData.to, + chainId, + maxGasFee, + }, + }) - if (promptVerification) { - openPopup( - { - id: PopupId.SendFlow, - }, - true - ) - } + const transactionSignature = await this.callLedgerApiAsync( + () => + ledgerApiBridge.makeRequest( + LedgerApiMethod.SignEvmTransaction, + unsignedTransactionMessageHex, + bip32Path + ), + 'evm-signed-transaction' + ) - const { r, v, s } = transactionSignature - if (r && v && s) { - return prepareEvmTransaction(txData, chainId, { r, v, s }) - } + openPopup( + { + id: PopupId.SendFlow, + }, + true + ) + + const { r, v, s } = transactionSignature + if (r && v && s) { + resolve(prepareEvmTransaction(transactionData, chainId, { r, v, s })) + } else { + reject(localize('error.send.cancelled')) + } + } + }) } private static async callLedgerApiAsync( callback: () => void, - responseEvent: keyof IPlatformEventMap + responseEvent: keyof IPlatformEventMap, + requestOptions: Partial = {} ): Promise { - const { timeout, pollingInterval } = DEFAULT_LEDGER_API_REQUEST_OPTIONS - const iterationCount = (timeout * MILLISECONDS_PER_SECOND) / pollingInterval + // TODO: Do we need to stop / start polling here? Get's slightly complicated in that + // the Ethereum app settings polling uses this function. + + const { timeout, pollingInterval } = { ...DEFAULT_LEDGER_API_REQUEST_OPTIONS, ...requestOptions } + const iterationCount = timeout / pollingInterval callback() @@ -110,6 +162,7 @@ export class Ledger { for (let count = 0; count < iterationCount; count++) { if (!isGenerating) { + Platform.removeListenersForEvent(responseEvent) if (returnValue && Object.keys(returnValue).length !== 0) { return returnValue } else { @@ -119,6 +172,7 @@ export class Ledger { await sleep(pollingInterval) } + Platform.removeListenersForEvent(responseEvent) return Promise.reject(localize('error.ledger.timeout')) } } diff --git a/packages/shared/src/lib/core/ledger/constants/default-ledger-api-request-options.constant.ts b/packages/shared/src/lib/core/ledger/constants/default-ledger-api-request-options.constant.ts index f57983e227..8f14f1e00f 100644 --- a/packages/shared/src/lib/core/ledger/constants/default-ledger-api-request-options.constant.ts +++ b/packages/shared/src/lib/core/ledger/constants/default-ledger-api-request-options.constant.ts @@ -1,6 +1,7 @@ +import { MILLISECONDS_PER_SECOND } from '@core/utils' import { ILedgerApiRequestOptions } from '../interfaces' export const DEFAULT_LEDGER_API_REQUEST_OPTIONS: ILedgerApiRequestOptions = { - timeout: 60, - pollingInterval: 100, + timeout: 60 * MILLISECONDS_PER_SECOND, // In milliseconds + pollingInterval: 100, // In milliseconds } diff --git a/packages/shared/src/lib/core/ledger/constants/default-ledger-device-state-poll-interval.constant.ts b/packages/shared/src/lib/core/ledger/constants/default-ledger-device-state-poll-interval.constant.ts new file mode 100644 index 0000000000..7bef48bac9 --- /dev/null +++ b/packages/shared/src/lib/core/ledger/constants/default-ledger-device-state-poll-interval.constant.ts @@ -0,0 +1,5 @@ +import { MILLISECONDS_PER_SECOND } from '@core/utils' + +const INTERVAL_IN_SECONDS = 1 + +export const DEFAULT_LEDGER_DEVICE_STATE_POLL_INTERVAL = INTERVAL_IN_SECONDS * MILLISECONDS_PER_SECOND diff --git a/packages/shared/src/lib/core/ledger/constants/default-ledger-nano-status-poll-interval.constant.ts b/packages/shared/src/lib/core/ledger/constants/default-ledger-nano-status-poll-interval.constant.ts deleted file mode 100644 index eff784f11d..0000000000 --- a/packages/shared/src/lib/core/ledger/constants/default-ledger-nano-status-poll-interval.constant.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { MILLISECONDS_PER_SECOND } from '@core/utils' - -const INTERVAL_IN_SECONDS = 1 - -export const DEFAULT_LEDGER_NANO_STATUS_POLL_INTERVAL = INTERVAL_IN_SECONDS * MILLISECONDS_PER_SECOND diff --git a/packages/shared/src/lib/core/ledger/constants/index.ts b/packages/shared/src/lib/core/ledger/constants/index.ts index 89b5fc7b5a..fcdc0fb7aa 100644 --- a/packages/shared/src/lib/core/ledger/constants/index.ts +++ b/packages/shared/src/lib/core/ledger/constants/index.ts @@ -1,4 +1,4 @@ export * from './default-ledger-api-request-options.constant' -export * from './default-ledger-nano-status-poll-interval.constant' +export * from './default-ledger-device-state-poll-interval.constant' export * from './ledger-error-locales.constant' export * from './use-ledger-simulator.constant' diff --git a/packages/shared/src/lib/core/ledger/enums/index.ts b/packages/shared/src/lib/core/ledger/enums/index.ts index 62f73b6d2d..a5c642cdb8 100644 --- a/packages/shared/src/lib/core/ledger/enums/index.ts +++ b/packages/shared/src/lib/core/ledger/enums/index.ts @@ -1,3 +1,4 @@ export * from './ledger-api-method.enum' export * from './ledger-app-name.enum' +export * from './ledger-connection-state.enum' export * from './ledger-error.enum' diff --git a/packages/shared/src/lib/core/ledger/enums/ledger-api-method.enum.ts b/packages/shared/src/lib/core/ledger/enums/ledger-api-method.enum.ts index cbe3f11ae3..6faaf7e2c9 100644 --- a/packages/shared/src/lib/core/ledger/enums/ledger-api-method.enum.ts +++ b/packages/shared/src/lib/core/ledger/enums/ledger-api-method.enum.ts @@ -1,4 +1,5 @@ export enum LedgerApiMethod { GenerateEvmAddress = 'generate-evm-address', + GetEthereumAppSettings = 'get-ethereum-app-settings', SignEvmTransaction = 'sign-evm-transaction', } diff --git a/packages/shared/src/lib/core/ledger/interfaces/ledger-connection-state.interface.ts b/packages/shared/src/lib/core/ledger/enums/ledger-connection-state.enum.ts similarity index 100% rename from packages/shared/src/lib/core/ledger/interfaces/ledger-connection-state.interface.ts rename to packages/shared/src/lib/core/ledger/enums/ledger-connection-state.enum.ts diff --git a/packages/shared/src/lib/core/ledger/helpers/deconstructLedgerNanoStatusPollingConfiguration.ts b/packages/shared/src/lib/core/ledger/helpers/deconstructLedgerNanoStatusPollingConfiguration.ts deleted file mode 100644 index e13183c59c..0000000000 --- a/packages/shared/src/lib/core/ledger/helpers/deconstructLedgerNanoStatusPollingConfiguration.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { profileManager as _profileManager } from '@core/profile-manager/stores' - -import { DEFAULT_LEDGER_NANO_STATUS_POLL_INTERVAL } from '../constants' -import { ILedgerNanoStatusPollingConfiguration } from '../interfaces' - -export function deconstructLedgerNanoStatusPollingConfiguration( - config: ILedgerNanoStatusPollingConfiguration -): ILedgerNanoStatusPollingConfiguration { - const pollInterval = config?.pollInterval ?? DEFAULT_LEDGER_NANO_STATUS_POLL_INTERVAL - const profileManager = config?.profileManager ?? _profileManager - - return { - pollInterval, - profileManager, - } -} diff --git a/packages/shared/src/lib/core/ledger/helpers/index.ts b/packages/shared/src/lib/core/ledger/helpers/index.ts index 20f01de3f0..a33cc00d4e 100644 --- a/packages/shared/src/lib/core/ledger/helpers/index.ts +++ b/packages/shared/src/lib/core/ledger/helpers/index.ts @@ -1,3 +1,2 @@ -export * from './deconstructLedgerNanoStatusPollingConfiguration' export * from './deconstructLedgerVerificationProps' export * from './deriveLedgerError' diff --git a/packages/shared/src/lib/core/ledger/interfaces/app-settings/base-ledger-app-settings.interface.ts b/packages/shared/src/lib/core/ledger/interfaces/app-settings/base-ledger-app-settings.interface.ts new file mode 100644 index 0000000000..2f7d334b5c --- /dev/null +++ b/packages/shared/src/lib/core/ledger/interfaces/app-settings/base-ledger-app-settings.interface.ts @@ -0,0 +1,4 @@ +export interface IBaseLedgerAppSettings { + blindSigningEnabled: boolean + version: string +} diff --git a/packages/shared/src/lib/core/ledger/interfaces/app-settings/index.ts b/packages/shared/src/lib/core/ledger/interfaces/app-settings/index.ts new file mode 100644 index 0000000000..061a194729 --- /dev/null +++ b/packages/shared/src/lib/core/ledger/interfaces/app-settings/index.ts @@ -0,0 +1,3 @@ +export * from './base-ledger-app-settings.interface' +export * from './ledger-app-settings.interface' +export * from './ledger-ethereum-app-settings.interface' diff --git a/packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-app-settings.interface.ts b/packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-app-settings.interface.ts new file mode 100644 index 0000000000..6faf79bae2 --- /dev/null +++ b/packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-app-settings.interface.ts @@ -0,0 +1,8 @@ +import { LedgerAppName } from '../../enums' +import { ILedgerEthereumAppSettings } from './ledger-ethereum-app-settings.interface' +import { IBaseLedgerAppSettings } from './base-ledger-app-settings.interface' + +export interface ILedgerAppSettings { + [LedgerAppName.Shimmer]?: IBaseLedgerAppSettings + [LedgerAppName.Ethereum]?: ILedgerEthereumAppSettings +} diff --git a/packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-ethereum-app-settings.interface.ts b/packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-ethereum-app-settings.interface.ts new file mode 100644 index 0000000000..9963ab81d1 --- /dev/null +++ b/packages/shared/src/lib/core/ledger/interfaces/app-settings/ledger-ethereum-app-settings.interface.ts @@ -0,0 +1,7 @@ +import { IBaseLedgerAppSettings } from './base-ledger-app-settings.interface' + +export interface ILedgerEthereumAppSettings extends IBaseLedgerAppSettings { + erc20ProvisioningNecessary: boolean + starkEnabled: boolean + starkv2Supported: boolean +} diff --git a/packages/shared/src/lib/core/ledger/interfaces/index.ts b/packages/shared/src/lib/core/ledger/interfaces/index.ts index ff06a8e3ae..dcba5b6942 100644 --- a/packages/shared/src/lib/core/ledger/interfaces/index.ts +++ b/packages/shared/src/lib/core/ledger/interfaces/index.ts @@ -1,4 +1,6 @@ +export * from './app-settings' + export * from './ledger-api-bridge.interface' export * from './ledger-api-request-options.interface' -export * from './ledger-connection-state.interface' -export * from './ledger-nano-status-polling-configuration.interface' +export * from './ledger-device-state.interface' +export * from './ledger-device-state-polling-configuration.interface' diff --git a/packages/shared/src/lib/core/ledger/interfaces/ledger-api-bridge.interface.ts b/packages/shared/src/lib/core/ledger/interfaces/ledger-api-bridge.interface.ts index 3ccfb4a000..b04c3db8df 100644 --- a/packages/shared/src/lib/core/ledger/interfaces/ledger-api-bridge.interface.ts +++ b/packages/shared/src/lib/core/ledger/interfaces/ledger-api-bridge.interface.ts @@ -2,5 +2,6 @@ import { LedgerApiMethod } from '../enums' export interface ILedgerApiBridge { makeRequest(method: LedgerApiMethod.GenerateEvmAddress, bip32Path: string, verify: boolean): void + makeRequest(method: LedgerApiMethod.GetEthereumAppSettings): void makeRequest(method: LedgerApiMethod.SignEvmTransaction, transactionHex: string, bip32Path: string): void } diff --git a/packages/shared/src/lib/core/ledger/interfaces/ledger-nano-status-polling-configuration.interface.ts b/packages/shared/src/lib/core/ledger/interfaces/ledger-device-state-polling-configuration.interface.ts similarity index 74% rename from packages/shared/src/lib/core/ledger/interfaces/ledger-nano-status-polling-configuration.interface.ts rename to packages/shared/src/lib/core/ledger/interfaces/ledger-device-state-polling-configuration.interface.ts index 0da66b01c3..28c3117d1d 100644 --- a/packages/shared/src/lib/core/ledger/interfaces/ledger-nano-status-polling-configuration.interface.ts +++ b/packages/shared/src/lib/core/ledger/interfaces/ledger-device-state-polling-configuration.interface.ts @@ -2,7 +2,7 @@ import { Writable } from 'svelte/store' import { IProfileManager } from '@core/profile-manager' -export interface ILedgerNanoStatusPollingConfiguration { +export interface ILedgerDeviceStatePollingConfiguration { pollInterval?: number profileManager?: Writable } diff --git a/packages/shared/src/lib/core/ledger/interfaces/ledger-device-state.interface.ts b/packages/shared/src/lib/core/ledger/interfaces/ledger-device-state.interface.ts new file mode 100644 index 0000000000..f03305d22e --- /dev/null +++ b/packages/shared/src/lib/core/ledger/interfaces/ledger-device-state.interface.ts @@ -0,0 +1,12 @@ +import { LedgerDeviceType } from '@iota/sdk/out/types' +import { LedgerAppName } from '../enums' +import { ILedgerAppSettings } from './app-settings' + +export interface ILedgerDeviceState { + connected?: boolean + locked?: boolean + device?: LedgerDeviceType + app?: LedgerAppName + settings?: ILedgerAppSettings + bufferSize?: number +} diff --git a/packages/shared/src/lib/core/ledger/stores/index.ts b/packages/shared/src/lib/core/ledger/stores/index.ts index 15deb19c41..c14e6f430a 100644 --- a/packages/shared/src/lib/core/ledger/stores/index.ts +++ b/packages/shared/src/lib/core/ledger/stores/index.ts @@ -1,5 +1,5 @@ -export * from './is-polling-ledger-device-status.store' +export * from './is-polling-ledger-device-state.stores' export * from './ledger-connection-state.store' -export * from './ledger-nano-status.store' +export * from './ledger-device-state.store' export * from './ledger-prepared-output.store' export * from './show-internal-verification-popup' diff --git a/packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-state.stores.ts b/packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-state.stores.ts new file mode 100644 index 0000000000..7b22e967a7 --- /dev/null +++ b/packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-state.stores.ts @@ -0,0 +1,3 @@ +import { writable } from 'svelte/store' + +export const isPollingLedgerDeviceState = writable(false) diff --git a/packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-status.store.ts b/packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-status.store.ts deleted file mode 100644 index 7b1b7bef52..0000000000 --- a/packages/shared/src/lib/core/ledger/stores/is-polling-ledger-device-status.store.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { writable } from 'svelte/store' - -export const isPollingLedgerDeviceStatus = writable(false) diff --git a/packages/shared/src/lib/core/ledger/stores/ledger-connection-state.store.ts b/packages/shared/src/lib/core/ledger/stores/ledger-connection-state.store.ts index 670679822a..1b13b40ac4 100644 --- a/packages/shared/src/lib/core/ledger/stores/ledger-connection-state.store.ts +++ b/packages/shared/src/lib/core/ledger/stores/ledger-connection-state.store.ts @@ -1,9 +1,9 @@ import { Readable, derived } from 'svelte/store' -import { LedgerConnectionState } from '../interfaces' +import { LedgerConnectionState } from '../enums' import { determineLedgerConnectionState } from '../utils' -import { ledgerNanoStatus } from './ledger-nano-status.store' +import { ledgerDeviceState } from './ledger-device-state.store' export const ledgerConnectionState: Readable = derived( - [ledgerNanoStatus], - ([$ledgerNanoStatus]) => determineLedgerConnectionState($ledgerNanoStatus) + [ledgerDeviceState], + ([$ledgerNanoState]) => determineLedgerConnectionState($ledgerNanoState) ) diff --git a/packages/shared/src/lib/core/ledger/stores/ledger-device-state.store.ts b/packages/shared/src/lib/core/ledger/stores/ledger-device-state.store.ts new file mode 100644 index 0000000000..e75a604133 --- /dev/null +++ b/packages/shared/src/lib/core/ledger/stores/ledger-device-state.store.ts @@ -0,0 +1,19 @@ +import { writable } from 'svelte/store' +import { LedgerNanoStatus } from '@iota/sdk/out/types' +import { ILedgerEthereumAppSettings, ILedgerDeviceState } from '../interfaces' +import { buildLedgerDeviceState } from '../utils' + +const DEFAULT_LEDGER_DEVICE_STATE: ILedgerDeviceState = { + connected: false, + locked: true, +} + +export const ledgerDeviceState = writable(DEFAULT_LEDGER_DEVICE_STATE) + +export function setLedgerDeviceState(status: LedgerNanoStatus, ethereumAppSettings?: ILedgerEthereumAppSettings): void { + return ledgerDeviceState.set(buildLedgerDeviceState(status, ethereumAppSettings)) +} + +export function resetLedgerDeviceState(): void { + ledgerDeviceState.set(DEFAULT_LEDGER_DEVICE_STATE) +} diff --git a/packages/shared/src/lib/core/ledger/types/ledger-api-request-response.type.ts b/packages/shared/src/lib/core/ledger/types/ledger-api-request-response.type.ts index 504dc4792b..7ce9e1c0d0 100644 --- a/packages/shared/src/lib/core/ledger/types/ledger-api-request-response.type.ts +++ b/packages/shared/src/lib/core/ledger/types/ledger-api-request-response.type.ts @@ -1,3 +1,4 @@ -import { IEvmAddress, IEvmTransactionSignature } from '@core/layer-2/interfaces' +import type { IEvmAddress, IEvmTransactionSignature } from '@core/layer-2/interfaces' +import { ILedgerEthereumAppSettings } from '../interfaces' -export type LedgerApiRequestResponse = IEvmAddress | IEvmTransactionSignature +export type LedgerApiRequestResponse = ILedgerEthereumAppSettings | IEvmAddress | IEvmTransactionSignature diff --git a/packages/shared/src/lib/core/ledger/utils/buildLedgerDeviceState.ts b/packages/shared/src/lib/core/ledger/utils/buildLedgerDeviceState.ts new file mode 100644 index 0000000000..d2bfebf98b --- /dev/null +++ b/packages/shared/src/lib/core/ledger/utils/buildLedgerDeviceState.ts @@ -0,0 +1,27 @@ +import { LedgerNanoStatus } from '@iota/sdk/out/types' +import { LedgerAppName } from '../enums' +import { ILedgerAppSettings, ILedgerDeviceState, ILedgerEthereumAppSettings } from '../interfaces' + +export function buildLedgerDeviceState( + status: LedgerNanoStatus, + ethereumAppSettings?: ILedgerEthereumAppSettings +): ILedgerDeviceState { + const app = status.app + + return { + connected: ethereumAppSettings ? true : status.connected, + locked: ethereumAppSettings ? false : status.locked, + device: status.device, + app: app?.name as LedgerAppName, + settings: { + ...(app && + (app?.name as LedgerAppName) === LedgerAppName.Shimmer && { + [LedgerAppName.Shimmer]: { + version: app.version, + blindSigningEnabled: status?.blindSigningEnabled, + }, + }), + ...(ethereumAppSettings && { [LedgerAppName.Ethereum]: ethereumAppSettings }), + }, + } +} diff --git a/packages/shared/src/lib/core/ledger/utils/determineLedgerConnectionState.ts b/packages/shared/src/lib/core/ledger/utils/determineLedgerConnectionState.ts index 29ff818a84..712e83d800 100644 --- a/packages/shared/src/lib/core/ledger/utils/determineLedgerConnectionState.ts +++ b/packages/shared/src/lib/core/ledger/utils/determineLedgerConnectionState.ts @@ -1,21 +1,20 @@ -import { LedgerNanoStatus } from '@iota/sdk/out/types' -import { LedgerConnectionState } from '../interfaces' -import { LedgerAppName } from '../enums' +import { LedgerAppName, LedgerConnectionState } from '../enums' +import { ILedgerDeviceState } from '../interfaces' export function determineLedgerConnectionState( - status: LedgerNanoStatus, + status: ILedgerDeviceState, appName = LedgerAppName.Shimmer ): LedgerConnectionState { - const { connected, app } = status + const { connected, locked, app } = status if (connected) { - if (app) { - if (app.name === (appName as string)) { + if (locked) { + return LedgerConnectionState.Locked + } else { + if (app === appName) { return LedgerConnectionState.CorrectAppOpen } else { return LedgerConnectionState.AppNotOpen } - } else { - return LedgerConnectionState.Locked } } else { return LedgerConnectionState.NotConnected diff --git a/packages/shared/src/lib/core/ledger/utils/handleLedgerError.ts b/packages/shared/src/lib/core/ledger/utils/handleLedgerError.ts index 91a4db2a10..127d6e9489 100644 --- a/packages/shared/src/lib/core/ledger/utils/handleLedgerError.ts +++ b/packages/shared/src/lib/core/ledger/utils/handleLedgerError.ts @@ -3,13 +3,15 @@ import { get } from 'svelte/store' import { localize } from '@core/i18n/i18n' import { resetMintTokenDetails, resetMintNftDetails } from '@core/wallet/stores' import { IError } from '@core/error/interfaces' -import { handleGenericError } from '@core/error/handlers' +import { handleError, handleGenericError } from '@core/error/handlers' import { showNotification } from '@auxiliary/notification' import { closePopup, openPopup, PopupId, popupState } from '../../../../../../desktop/lib/auxiliary/popup' import { LEDGER_ERROR_LOCALES } from '../constants' -import { LedgerError } from '../enums' +import { LedgerAppName, LedgerError } from '../enums' import { deriveLedgerError } from '../helpers' +import { checkOrConnectLedger, ledgerPreparedOutput, resetLedgerPreparedOutput } from '@core/ledger' +import { sendOutput } from '@core/wallet' export function handleLedgerError(error: IError, resetConfirmationPropsOnDenial = true): void { const ledgerError = deriveLedgerError(error?.error) @@ -39,6 +41,21 @@ export function handleLedgerError(error: IError, resetConfirmationPropsOnDenial if (hadToEnableBlindSinging) { openPopup({ id: PopupId.EnableLedgerBlindSigning, + props: { + appName: LedgerAppName.Shimmer, + onEnabled: () => { + checkOrConnectLedger(async () => { + try { + if (get(ledgerPreparedOutput)) { + await sendOutput(get(ledgerPreparedOutput)) + resetLedgerPreparedOutput() + } + } catch (err) { + handleError(err) + } + }) + }, + }, }) } else { showNotification({ diff --git a/packages/shared/src/lib/core/ledger/utils/index.ts b/packages/shared/src/lib/core/ledger/utils/index.ts index e1f54ba7fe..8f37e7039c 100644 --- a/packages/shared/src/lib/core/ledger/utils/index.ts +++ b/packages/shared/src/lib/core/ledger/utils/index.ts @@ -1,7 +1,8 @@ +export * from './buildLedgerDeviceState' export * from './determineLedgerConnectionState' export * from './formatAddressForLedger' export * from './handleLedgerError' +export * from './isBlindSigningRequiredForEvmTransaction' export * from './isLedgerAppOpen' export * from './isLedgerDeviceMatchingActiveProfile' export * from './isLedgerError' -export * from './openLedgerNotConnectedPopup' diff --git a/packages/shared/src/lib/core/ledger/utils/isBlindSigningRequiredForEvmTransaction.ts b/packages/shared/src/lib/core/ledger/utils/isBlindSigningRequiredForEvmTransaction.ts new file mode 100644 index 0000000000..18c1abc5fa --- /dev/null +++ b/packages/shared/src/lib/core/ledger/utils/isBlindSigningRequiredForEvmTransaction.ts @@ -0,0 +1,5 @@ +import type { EvmTransactionData } from '@core/layer-2/types' + +export function isBlindSigningRequiredForEvmTransaction(transactionData: EvmTransactionData): boolean { + return Boolean(transactionData?.data) +} diff --git a/packages/shared/src/lib/core/ledger/utils/isLedgerAppOpen.ts b/packages/shared/src/lib/core/ledger/utils/isLedgerAppOpen.ts index 477755c034..7d2e398c32 100644 --- a/packages/shared/src/lib/core/ledger/utils/isLedgerAppOpen.ts +++ b/packages/shared/src/lib/core/ledger/utils/isLedgerAppOpen.ts @@ -1,8 +1,8 @@ import { get } from 'svelte/store' import { LedgerAppName } from '../enums' -import { ledgerNanoStatus } from '../stores' +import { ledgerDeviceState } from '../stores' export function isLedgerAppOpen(appName: LedgerAppName): boolean { - return get(ledgerNanoStatus)?.app?.name === (appName as string) + return get(ledgerDeviceState)?.app === appName } diff --git a/packages/shared/src/lib/core/ledger/utils/openLedgerNotConnectedPopup.ts b/packages/shared/src/lib/core/ledger/utils/openLedgerNotConnectedPopup.ts deleted file mode 100644 index 123610a947..0000000000 --- a/packages/shared/src/lib/core/ledger/utils/openLedgerNotConnectedPopup.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { get } from 'svelte/store' -import { openPopup, PopupId, popupState } from '../../../../../../desktop/lib/auxiliary/popup' - -export function openLedgerNotConnectedPopup( - cancel: () => void = () => {}, - poll: () => void = () => {}, - overridePopup: boolean = false -): void { - if (!get(popupState).active || overridePopup) { - openPopup({ - id: PopupId.ConnectLedger, - props: { - onClose: () => cancel(), - onPoll: () => poll(), - }, - }) - } -} diff --git a/packages/shared/src/lib/core/profile-manager/actions/events-handlers/handleTransactionProgressEvent.ts b/packages/shared/src/lib/core/profile-manager/actions/events-handlers/handleTransactionProgressEvent.ts index 1aa392d59f..23e658439b 100644 --- a/packages/shared/src/lib/core/profile-manager/actions/events-handlers/handleTransactionProgressEvent.ts +++ b/packages/shared/src/lib/core/profile-manager/actions/events-handlers/handleTransactionProgressEvent.ts @@ -1,6 +1,7 @@ import { isOnboardingLedgerProfile } from '@contexts/onboarding' import { selectedAccountIndex } from '@core/account/stores' -import { ledgerNanoStatus } from '@core/ledger' +import { LedgerAppName } from '@core/ledger/enums' +import { ledgerDeviceState, ledgerPreparedOutput, resetLedgerPreparedOutput } from '@core/ledger/stores' import { deconstructLedgerVerificationProps } from '@core/ledger/helpers' import { isActiveLedgerProfile } from '@core/profile/stores' import { @@ -15,6 +16,9 @@ import { get } from 'svelte/store' import { PopupId, closePopup, openPopup } from '../../../../../../../desktop/lib/auxiliary/popup' import { MissingTransactionProgressEventPayloadError } from '../../errors' import { validateWalletApiEvent } from '../../utils' +import { checkOrConnectLedger } from '@core/ledger/actions' +import { handleError } from '@core/error/handlers' +import { sendOutput } from '@core/wallet/actions' export function handleTransactionProgressEvent(error: Error, event: Event): void { const walletEvent = validateWalletApiEvent( @@ -54,12 +58,13 @@ function openPopupIfVerificationNeeded(progress: TransactionProgress): void { }, }) } else if (type === TransactionProgressType.PreparedTransactionEssenceHash) { - if (get(ledgerNanoStatus)?.blindSigningEnabled) { + if (get(ledgerDeviceState)?.settings?.[LedgerAppName.Shimmer]?.blindSigningEnabled) { openPopup({ id: PopupId.VerifyLedgerTransaction, hideClose: true, preventClose: true, props: { + useBlindSigning: true, hash: (progress as PreparedTransactionEssenceHashProgress).hash, }, }) @@ -68,6 +73,21 @@ function openPopupIfVerificationNeeded(progress: TransactionProgress): void { id: PopupId.EnableLedgerBlindSigning, hideClose: true, preventClose: true, + props: { + appName: LedgerAppName.Shimmer, + onEnabled: () => { + checkOrConnectLedger(async () => { + try { + if (get(ledgerPreparedOutput)) { + await sendOutput(get(ledgerPreparedOutput)) + resetLedgerPreparedOutput() + } + } catch (err) { + handleError(err) + } + }) + }, + }, }) } } else if (type === TransactionProgressType.PerformingPow) { diff --git a/packages/shared/src/lib/core/profile-manager/api/getLedgerNanoStatus.ts b/packages/shared/src/lib/core/profile-manager/api/getLedgerNanoStatus.ts index 7d4eccf10b..556ce1c801 100644 --- a/packages/shared/src/lib/core/profile-manager/api/getLedgerNanoStatus.ts +++ b/packages/shared/src/lib/core/profile-manager/api/getLedgerNanoStatus.ts @@ -1,6 +1,6 @@ -import { LedgerNanoStatus } from '@iota/sdk/out/types' import { get } from 'svelte/store' import { profileManager as _profileManager } from '@core/profile-manager/stores' +import type { LedgerNanoStatus } from '@iota/sdk/out/types' export function getLedgerNanoStatus(profileManager = _profileManager): Promise { const manager = get(profileManager) diff --git a/packages/shared/src/lib/core/profile/actions/active-profile/login.ts b/packages/shared/src/lib/core/profile/actions/active-profile/login.ts index 6d508baccd..577a263396 100644 --- a/packages/shared/src/lib/core/profile/actions/active-profile/login.ts +++ b/packages/shared/src/lib/core/profile/actions/active-profile/login.ts @@ -8,7 +8,7 @@ import { generateAndStoreActivitiesForAllAccounts } from '@core/activity/actions import { Platform } from '@core/app/classes' import { AppContext } from '@core/app/enums' import { handleError } from '@core/error/handlers' -import { pollLedgerNanoStatus } from '@core/ledger/actions' +import { pollLedgerDeviceState } from '@core/ledger/actions' import { pollMarketPrices } from '@core/market/actions' import { pollChainStatuses, pollNetworkStatus } from '@core/network/actions' import { loadNftsForActiveProfile } from '@core/nfts/actions' @@ -136,7 +136,7 @@ export async function login(loginOptions?: ILoginOptions): Promise { // Step 9: finish login incrementLoginProgress() if (isLedgerProfile(type)) { - pollLedgerNanoStatus() + pollLedgerDeviceState() } setSelectedAccount(lastUsedAccountIndex ?? get(activeAccounts)?.[0]?.index ?? null) diff --git a/packages/shared/src/lib/core/profile/actions/active-profile/logout.ts b/packages/shared/src/lib/core/profile/actions/active-profile/logout.ts index 81924fcd57..d7f659ec51 100644 --- a/packages/shared/src/lib/core/profile/actions/active-profile/logout.ts +++ b/packages/shared/src/lib/core/profile/actions/active-profile/logout.ts @@ -6,8 +6,8 @@ import { resetProposalOverviews, resetRegisteredProposals, } from '@contexts/governance/stores' -import { stopPollingLedgerNanoStatus } from '@core/ledger/actions' -import { isPollingLedgerDeviceStatus } from '@core/ledger/stores' +import { stopPollingLedgerDeviceState } from '@core/ledger/actions' +import { isPollingLedgerDeviceState } from '@core/ledger/stores' import { clearMarketPricesPoll } from '@core/market/actions' import { clearChainStatusesPoll, clearNetworkPoll } from '@core/network/actions' import { stopDownloadingNftMediaFromQueue } from '@core/nfts/actions' @@ -29,7 +29,7 @@ export function logout(clearActiveProfile = true, _lockStronghold = true): void _lockStronghold && lockStronghold() } else if (isLedgerProfile(get(activeProfile).type)) { Platform.killLedgerProcess() - get(isPollingLedgerDeviceStatus) && stopPollingLedgerNanoStatus() + get(isPollingLedgerDeviceState) && stopPollingLedgerDeviceState() } clearNetworkPoll() diff --git a/packages/shared/src/lib/core/wallet/actions/send/signAndSendEvmTransaction.ts b/packages/shared/src/lib/core/wallet/actions/send/signAndSendEvmTransaction.ts index fc0105d7f6..7cb61e046d 100644 --- a/packages/shared/src/lib/core/wallet/actions/send/signAndSendEvmTransaction.ts +++ b/packages/shared/src/lib/core/wallet/actions/send/signAndSendEvmTransaction.ts @@ -36,18 +36,16 @@ export async function signAndSendEvmTransaction( if (get(isSoftwareProfile)) { signedTransaction = await signEvmTransactionWithStronghold(transaction, bip44Path, chainId) } else if (get(isActiveLedgerProfile)) { - signedTransaction = await Ledger.signEvmTransaction(txData, chainId, bip44Path) + signedTransaction = (await Ledger.signEvmTransaction(txData, chainId, bip44Path)) as string } if (signedTransaction) { return await provider?.eth.sendSignedTransaction(signedTransaction) - } else { - if (get(isActiveLedgerProfile)) { - closePopup(true) - } - throw new Error('No signature provided') } } catch (err) { + if (get(isActiveLedgerProfile)) { + closePopup(true) + } handleError(err) } finally { updateSelectedAccount({ isTransferring: false }) diff --git a/packages/shared/src/locales/en.json b/packages/shared/src/locales/en.json index 9ad43bda99..06dc060b62 100644 --- a/packages/shared/src/locales/en.json +++ b/packages/shared/src/locales/en.json @@ -559,10 +559,10 @@ "popups": { "enableLedgerBlindSigning": { "title": "Enable Blind Signing", - "info": "In order to confirm this transaction you need to enable Blind Signing on your ledger device.", - "step_1": "Connect and unlock your Ledger Device", - "step_2": "Open Shimmer Application", - "step_3": "Press the right button to navigate to Settings. Then press both buttons to validate. Your Ledger device displays Blind Signing.", + "info": "In order to confirm this transaction you need to enable Blind Signing on your Ledger device.", + "step_1": "Connect and unlock your Ledger device", + "step_2": "Open the {appName} application", + "step_3": "Press the right button to navigate to Settings, then press both buttons to validate. Your Ledger device displays Blind Signing.", "step_4": "Press both buttons to enable transaction blind signing. The device displays Enabled. You're done." }, "balanceBreakdown": {