Skip to content

Commit

Permalink
feat: rsk ledger integration
Browse files Browse the repository at this point in the history
  • Loading branch information
ahsan-javaiid committed Nov 8, 2022
1 parent 9fe635b commit f7b2260
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 11 deletions.
2 changes: 1 addition & 1 deletion background/constants/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -582,6 +583,10 @@ export default class Main extends BaseService<never> {
return this.ledgerService.refreshConnectedLedger()
}

ledgerAppInfo(): ResponseAppInfo | null {
return this.ledgerService.getLedgerAppInfo()
}

async getAccountEthBalanceUncached(
addressNetwork: AddressOnNetwork
): Promise<bigint> {
Expand Down
1 change: 1 addition & 0 deletions background/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions background/redux-slices/ledger.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -31,6 +32,7 @@ export type LedgerState = {
/** Devices by ID */
devices: Record<string, LedgerDeviceState>
usbDeviceCount: number
ledgerAppInfo: ResponseAppInfo | null
}

export type Events = {
Expand Down Expand Up @@ -58,6 +60,7 @@ export const initialState: LedgerState = {
currentDeviceID: null,
devices: {},
usbDeviceCount: 0,
ledgerAppInfo: null,
}

const ledgerSlice = createSlice({
Expand Down Expand Up @@ -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,
{
Expand Down Expand Up @@ -216,6 +225,7 @@ export const {
setDeviceConnectionStatus,
addLedgerAccount,
setUsbDeviceCount,
setLedgerAppInfo,
} = ledgerSlice.actions

export default ledgerSlice.reducer
Expand All @@ -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))
}
)

Expand Down
1 change: 1 addition & 0 deletions background/redux-slices/migrations/to-6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default (prevState: Record<string, unknown>): NewState => {
currentDeviceID: null,
devices: {},
usbDeviceCount: 0,
ledgerAppInfo: null,
},
...typedPrevState,
}
Expand Down
48 changes: 43 additions & 5 deletions background/services/ledger/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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"

Expand Down Expand Up @@ -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

Expand All @@ -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]
}
Expand All @@ -172,6 +183,8 @@ async function generateLedgerId(
export default class LedgerService extends BaseService<Events> {
#currentLedgerId: string | null = null

ledgerAppInfo: ResponseAppInfo | null = null

transport: Transport | undefined = undefined

#lastOperationPromise = Promise.resolve()
Expand Down Expand Up @@ -209,7 +222,25 @@ export default class LedgerService extends BaseService<Events> {

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!")
Expand Down Expand Up @@ -239,7 +270,7 @@ export default class LedgerService extends BaseService<Events> {
this.emitter.emit("ledgerAdded", {
id: this.#currentLedgerId,
type,
accountIDs: [idDerivationPath],
accountIDs: [derivationPath],
metadata: {
ethereumVersion: appData.version,
isArbitraryDataSigningEnabled: appData.arbitraryDataEnabled !== 0,
Expand All @@ -250,6 +281,13 @@ export default class LedgerService extends BaseService<Events> {
})
}

/**
* Returns app info about active app on ledger
*/
getLedgerAppInfo(): ResponseAppInfo | null {
return this.ledgerAppInfo
}

#handleUSBConnect = async (event: USBConnectionEvent): Promise<void> => {
this.emitter.emit(
"usbDeviceCount",
Expand Down
15 changes: 13 additions & 2 deletions ui/components/Onboarding/OnboardingDerivationPathSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand Down Expand Up @@ -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<number>()

// 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<number>(
appInfo?.appName === "RSK" ? 4 : 0
)

// Reset value to display placeholder after adding a custom path
const customPathValue = customPath.isReset
Expand All @@ -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)
Expand Down
14 changes: 11 additions & 3 deletions ui/pages/Ledger/LedgerImportAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -167,7 +168,10 @@ function LedgerAccountList({
{balance === null && <div className="balance_loading" />}
{balance !== null && (
<div className="balance">
{balance} {selectedNetwork.baseAsset.symbol}
{balance}{" "}
{appInfo?.appName === "RSK"
? "RBTC"
: selectedNetwork.baseAsset.symbol}
</div>
)}
<div className="etherscan_link_container">
Expand All @@ -179,7 +183,11 @@ function LedgerAccountList({
window
.open(
`${
scanWebsite[selectedNetwork.chainID].url
scanWebsite[
appInfo?.appName === "RSK"
? RSK.chainID
: selectedNetwork.chainID
].url
}/address/${address}`,
"_blank"
)
Expand Down

0 comments on commit f7b2260

Please sign in to comment.