diff --git a/background/constants/networks.ts b/background/constants/networks.ts index c4f8b2ce4f..222dbbd68a 100644 --- a/background/constants/networks.ts +++ b/background/constants/networks.ts @@ -85,7 +85,7 @@ export const TEST_NETWORK_BY_CHAIN_ID = new Set( [GOERLI].map((network) => network.chainID) ) -export const NETWORK_FOR_LEDGER_SIGNING = [ETHEREUM, POLYGON] +export const NETWORK_FOR_LEDGER_SIGNING = [ETHEREUM, POLYGON, RSK] // Networks that are not added to this struct will // not have an in-wallet NFT tab diff --git a/background/main.ts b/background/main.ts index 8b2aee2ea7..2b7b78ef18 100644 --- a/background/main.ts +++ b/background/main.ts @@ -3,6 +3,7 @@ import { alias, wrapStore } from "webext-redux" import deepDiff from "webext-redux/lib/strategies/deepDiff/diff" import { configureStore, isPlain, Middleware } from "@reduxjs/toolkit" import { devToolsEnhancer } from "@redux-devtools/remote" +import { ResponseAppInfo } from "@zondax/ledger-icp" import { PermissionRequest } from "@tallyho/provider-bridge-shared" import { debounce } from "lodash" @@ -582,6 +583,10 @@ export default class Main extends BaseService { return this.ledgerService.refreshConnectedLedger() } + ledgerAppInfo(): ResponseAppInfo | null { + return this.ledgerService.getLedgerAppInfo() + } + async getAccountEthBalanceUncached( addressNetwork: AddressOnNetwork ): Promise { diff --git a/background/package.json b/background/package.json index f87e064fa7..a6c2037918 100644 --- a/background/package.json +++ b/background/package.json @@ -44,6 +44,7 @@ "@tallyho/window-provider": "0.0.1", "@types/w3c-web-usb": "^1.0.5", "@uniswap/token-lists": "^1.0.0-beta.30", + "@zondax/ledger-icp": "^0.6.0", "ajv": "^8.6.2", "ajv-formats": "^2.1.0", "assert": "^2.0.0", diff --git a/background/redux-slices/ledger.ts b/background/redux-slices/ledger.ts index e6ab75cf54..a06d8056d7 100644 --- a/background/redux-slices/ledger.ts +++ b/background/redux-slices/ledger.ts @@ -1,4 +1,5 @@ import { createSlice } from "@reduxjs/toolkit" +import { ResponseAppInfo } from "@zondax/ledger-icp" import { EVMNetwork } from "../networks" import { DisplayDetails } from "../services/ledger" import { HexString } from "../types" @@ -31,6 +32,7 @@ export type LedgerState = { /** Devices by ID */ devices: Record usbDeviceCount: number + ledgerAppInfo: ResponseAppInfo | null } export type Events = { @@ -58,6 +60,7 @@ export const initialState: LedgerState = { currentDeviceID: null, devices: {}, usbDeviceCount: 0, + ledgerAppInfo: null, } const ledgerSlice = createSlice({ @@ -95,6 +98,12 @@ const ledgerSlice = createSlice({ if (!(deviceID in immerState.devices)) return immerState.currentDeviceID = deviceID }, + setLedgerAppInfo: ( + immerState, + { payload: appInfo }: { payload: ResponseAppInfo | null } + ) => { + immerState.ledgerAppInfo = appInfo + }, setDeviceConnectionStatus: ( immerState, { @@ -216,6 +225,7 @@ export const { setDeviceConnectionStatus, addLedgerAccount, setUsbDeviceCount, + setLedgerAppInfo, } = ledgerSlice.actions export default ledgerSlice.reducer @@ -230,6 +240,9 @@ export const connectLedger = createBackgroundAsyncThunk( dispatch(ledgerSlice.actions.addLedgerDevice(deviceID)) dispatch(ledgerSlice.actions.setCurrentDevice(deviceID)) + + const appInfo = main.ledgerAppInfo() + dispatch(ledgerSlice.actions.setLedgerAppInfo(appInfo)) } ) diff --git a/background/redux-slices/migrations/to-6.ts b/background/redux-slices/migrations/to-6.ts index 1acfe3c777..7dc856b4e0 100644 --- a/background/redux-slices/migrations/to-6.ts +++ b/background/redux-slices/migrations/to-6.ts @@ -23,6 +23,7 @@ export default (prevState: Record): NewState => { currentDeviceID: null, devices: {}, usbDeviceCount: 0, + ledgerAppInfo: null, }, ...typedPrevState, } diff --git a/background/services/ledger/index.ts b/background/services/ledger/index.ts index b87a81632c..f1cada952e 100644 --- a/background/services/ledger/index.ts +++ b/background/services/ledger/index.ts @@ -1,5 +1,7 @@ import Transport from "@ledgerhq/hw-transport" import TransportWebUSB from "@ledgerhq/hw-transport-webusb" +import InternetComputerApp, { ResponseAppInfo } from "@zondax/ledger-icp" +import { toChecksumAddress } from "@tallyho/hd-keyring" import Eth from "@ledgerhq/hw-app-eth" import { DeviceModelId } from "@ledgerhq/devices" import { @@ -25,7 +27,7 @@ import { ServiceCreatorFunction, ServiceLifecycleEvents } from "../types" import logger from "../../lib/logger" import { getOrCreateDB, LedgerAccount, LedgerDatabase } from "./db" import { ethersTransactionFromTransactionRequest } from "../chain/utils" -import { NETWORK_FOR_LEDGER_SIGNING } from "../../constants" +import { NETWORK_FOR_LEDGER_SIGNING, RSK } from "../../constants" import { normalizeEVMAddress } from "../../lib/utils" import { AddressOnNetwork } from "../../accounts" @@ -113,15 +115,24 @@ type Events = ServiceLifecycleEvents & { export const idDerivationPath = "44'/60'/0'/0/0" +const RSK_DERIVATION_PATH = "44'/137'/0'/0" + async function deriveAddressOnLedger(path: string, eth: Eth) { const derivedIdentifiers = await eth.getAddress(path) + + if (path.includes(RSK_DERIVATION_PATH.slice(0, 8))) { + // Using @tallyho/hd-keyring to calculate checksum because ethersGetAddress rejects RSK addresses + return toChecksumAddress(derivedIdentifiers.address, +RSK.chainID) + } + const address = ethersGetAddress(derivedIdentifiers.address) return address } async function generateLedgerId( transport: Transport, - eth: Eth + eth: Eth, + derivationPath: string ): Promise<[string | undefined, LedgerType]> { let extensionDeviceType = LedgerType.UNKNOWN @@ -147,7 +158,7 @@ async function generateLedgerId( return [undefined, extensionDeviceType] } - const address = await deriveAddressOnLedger(idDerivationPath, eth) + const address = await deriveAddressOnLedger(derivationPath, eth) return [address, extensionDeviceType] } @@ -172,6 +183,8 @@ async function generateLedgerId( export default class LedgerService extends BaseService { #currentLedgerId: string | null = null + ledgerAppInfo: ResponseAppInfo | null = null + transport: Transport | undefined = undefined #lastOperationPromise = Promise.resolve() @@ -209,7 +222,25 @@ export default class LedgerService extends BaseService { const eth = new Eth(this.transport) - const [id, type] = await generateLedgerId(this.transport, eth) + const ledgerClient = new InternetComputerApp(this.transport) + + // Get appInfo and check which app is active on ledger device + this.ledgerAppInfo = await ledgerClient.getAppInfo() + + let derivationPath = idDerivationPath // Default derivation path + + // Passing ethereum derivation path in case of active RSK app on ledger will result in ledger disconnect + // So deciding derivation path based on running app on ledger + // Using RSK derivation path if RSK app is running + if (this.ledgerAppInfo?.appName === RSK.name) { + derivationPath = RSK_DERIVATION_PATH + } + + const [id, type] = await generateLedgerId( + this.transport, + eth, + derivationPath + ) if (!id) { throw new Error("Can't derive meaningful identification address!") @@ -239,7 +270,7 @@ export default class LedgerService extends BaseService { this.emitter.emit("ledgerAdded", { id: this.#currentLedgerId, type, - accountIDs: [idDerivationPath], + accountIDs: [derivationPath], metadata: { ethereumVersion: appData.version, isArbitraryDataSigningEnabled: appData.arbitraryDataEnabled !== 0, @@ -250,6 +281,13 @@ export default class LedgerService extends BaseService { }) } + /** + * Returns app info about active app on ledger + */ + getLedgerAppInfo(): ResponseAppInfo | null { + return this.ledgerAppInfo + } + #handleUSBConnect = async (event: USBConnectionEvent): Promise => { this.emitter.emit( "usbDeviceCount", diff --git a/ui/components/Onboarding/OnboardingDerivationPathSelect.tsx b/ui/components/Onboarding/OnboardingDerivationPathSelect.tsx index 5ff3fb1282..dcfbffad9d 100644 --- a/ui/components/Onboarding/OnboardingDerivationPathSelect.tsx +++ b/ui/components/Onboarding/OnboardingDerivationPathSelect.tsx @@ -11,6 +11,7 @@ import SharedButton from "../Shared/SharedButton" import SharedInput from "../Shared/SharedInput" import SharedModal from "../Shared/SharedModal" import SharedSelect, { Option } from "../Shared/SharedSelect" +import { useBackgroundSelector } from "../../hooks" // TODO make this network specific const initialDerivationPaths: Option[] = [ @@ -51,11 +52,15 @@ export default function OnboardingDerivationPathSelect({ }): ReactElement { const { t } = useTranslation("translation", { keyPrefix: "onboarding" }) const [derivationPaths, setDerivationPaths] = useState(initialDerivationPaths) - + const appInfo = useBackgroundSelector((state) => state.ledger.ledgerAppInfo) const [modalStep, setModalStep] = useState(0) const [customPath, setCustomPath] = useState(initialCustomPath) const [customPathLabel, setCustomPathLabel] = useState("") - const [defaultIndex, setDefaultIndex] = useState() + + // Auto select RSK dPath if RSK app is active on ledger because default eth dPath will be rejected by rsk ledger app + const [defaultIndex, setDefaultIndex] = useState( + appInfo?.appName === "RSK" ? 4 : 0 + ) // Reset value to display placeholder after adding a custom path const customPathValue = customPath.isReset @@ -71,6 +76,12 @@ export default function OnboardingDerivationPathSelect({ } }, [modalStep]) + useEffect(() => { + if (appInfo && appInfo.appName && appInfo.appName === "RSK") { + setDefaultIndex(4) + } + }, [appInfo]) + const handleAddCustomPath = () => { setModalStep(0) onChange(customPathValue) diff --git a/ui/pages/Ledger/LedgerImportAccounts.tsx b/ui/pages/Ledger/LedgerImportAccounts.tsx index c33ee999a6..f28ff68c1b 100644 --- a/ui/pages/Ledger/LedgerImportAccounts.tsx +++ b/ui/pages/Ledger/LedgerImportAccounts.tsx @@ -8,6 +8,7 @@ import { import classNames from "classnames" import React, { ReactElement, useEffect, useState } from "react" import { selectCurrentNetwork } from "@tallyho/tally-background/redux-slices/selectors" +import { RSK } from "@tallyho/tally-background/constants" import { EVMNetwork } from "@tallyho/tally-background/networks" import { useTranslation } from "react-i18next" import { useBackgroundDispatch, useBackgroundSelector } from "../../hooks" @@ -123,7 +124,7 @@ function LedgerAccountList({ }) const [pageIndex, setPageIndex] = useState(0) const selectedNetwork = useBackgroundSelector(selectCurrentNetwork) - + const appInfo = useBackgroundSelector((state) => state.ledger.ledgerAppInfo) const pageData = usePageData({ device, parentPath, @@ -167,7 +168,10 @@ function LedgerAccountList({ {balance === null &&
} {balance !== null && (
- {balance} {selectedNetwork.baseAsset.symbol} + {balance}{" "} + {appInfo?.appName === "RSK" + ? "RBTC" + : selectedNetwork.baseAsset.symbol}
)}
@@ -179,7 +183,11 @@ function LedgerAccountList({ window .open( `${ - scanWebsite[selectedNetwork.chainID].url + scanWebsite[ + appInfo?.appName === "RSK" + ? RSK.chainID + : selectedNetwork.chainID + ].url }/address/${address}`, "_blank" )