Skip to content

Commit 15a79ef

Browse files
committed
feat: add support for ledger live and mew address path in ledger import
1 parent 0cffbc1 commit 15a79ef

File tree

5 files changed

+101
-20
lines changed

5 files changed

+101
-20
lines changed

packages/consts/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ export const ETH_SEPOLIA_EXPLORER_URL = 'https://sepolia.etherscan.io'
113113
export const DEFAULT_CFX_HDPATH = `m/44'/503'/0'/0`
114114
export const DEFAULT_ETH_HDPATH = `m/44'/60'/0'/0`
115115

116+
// for ledger import address path
117+
export const LEDGER_LIVE_PATH = `m/44'/60'/0'/0/0`
118+
// for MEW or MyCrypto import address path
119+
export const MEW_PATH = `m/44'/60'/0'`
120+
116121
export const REGENERATE = 'REGENERATE'
117122

118123
export const CFX_LOCALNET_RPC_ENDPOINT = 'http://localhost:12537'

packages/ledger/const.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const INS = {
3434
export const HDPATH = {
3535
CONFLUX: "44'/503'/0'/0/",
3636
ETHEREUM: "44'/60'/0'/0/",
37+
LEDGER_LIVE: "m/44'/60'/0'/0/0",
3738
}
3839

3940
export const ERROR = {

packages/ledger/ethereum.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,14 @@ export default class Ethereum {
169169
}
170170
}
171171

172-
async getAddressList(indexArray) {
172+
async getAddressList(indexArray, path = HDPATH.ETHEREUM) {
173173
if (!Array.isArray(indexArray)) return []
174-
const isNumber = indexArray.every(function (item) {
175-
return typeof item === 'number'
176-
})
174+
const isNumber = indexArray.every(item => typeof item === 'number')
177175
if (!isNumber) return []
178176
const addressArr = []
179177
try {
180178
for (const index of indexArray) {
181-
const hdPath = `${HDPATH.ETHEREUM}${index}`
179+
const hdPath = this.getAddressesPathForIndex(index, path)
182180
const {address} = await this.getAddress(hdPath)
183181
addressArr.push({
184182
address,
@@ -224,4 +222,10 @@ export default class Ethereum {
224222
}
225223
return error
226224
}
225+
getAddressesPathForIndex = (index, path) => {
226+
if (path === HDPATH.LEDGER_LIVE) {
227+
return `m/44'/60'/${index}'/0/0`
228+
}
229+
return `${path}/${index}`
230+
}
227231
}

packages/popup/src/pages/ImportHwAccount/index.js

+83-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import PropTypes from 'prop-types'
2-
import {useState, useEffect, useMemo} from 'react'
2+
import {useState, useEffect, useMemo, useCallback} from 'react'
33
import {useAsync} from 'react-use'
44
import {useSWRConfig} from 'swr'
55
import {isUndefined} from '@fluent-wallet/checks'
@@ -17,7 +17,12 @@ import {
1717
CheckCircleFilled,
1818
QuestionCircleOutlined,
1919
} from '@fluent-wallet/component-icons'
20-
import {DEFAULT_CFX_HDPATH, DEFAULT_ETH_HDPATH} from '@fluent-wallet/consts'
20+
import {
21+
DEFAULT_CFX_HDPATH,
22+
DEFAULT_ETH_HDPATH,
23+
LEDGER_LIVE_PATH,
24+
MEW_PATH,
25+
} from '@fluent-wallet/consts'
2126
import {encode} from '@fluent-wallet/base32-address'
2227
import {TitleNav, CompWithLabel, Avatar, DisplayBalance} from '../../components'
2328
import {
@@ -34,12 +39,35 @@ import {useLedgerBindingApi} from '../../hooks'
3439
import useLoading from '../../hooks/useLoading'
3540
import useImportHWParams from './useImportHWParams'
3641
import {request} from '../../utils'
42+
import Dropdown from '@fluent-wallet/component-dropdown'
43+
import Menu from '../../../../ui/components/Menu'
44+
import MenuItem from '../../../../ui/components/Menu/MenuItem'
3745

3846
const {
3947
WALLET_IMPORT_HARDWARE_WALLET_ACCOUNT_GROUP_OR_ACCOUNT,
4048
WALLETDB_REFETCH_BALANCE,
4149
} = RPC_METHODS
4250

51+
const confluxLedgerPath = {
52+
name: 'BIP 44 Standard',
53+
value: DEFAULT_CFX_HDPATH,
54+
}
55+
56+
const ethereumLedgerPath = [
57+
{
58+
name: 'BIP 44 Standard',
59+
value: DEFAULT_ETH_HDPATH,
60+
},
61+
{
62+
name: 'Ledger Live',
63+
value: LEDGER_LIVE_PATH,
64+
},
65+
{
66+
name: 'Legacy',
67+
value: MEW_PATH,
68+
},
69+
]
70+
4371
function ImportingResults({importStatus}) {
4472
const {t} = useTranslation()
4573
const {mutate} = useSWRConfig()
@@ -114,14 +142,18 @@ function ImportHwAccount() {
114142
const {data: importedAddressData} = useQueryImportedAddress(networkId)
115143
const ledgerBindingApi = useLedgerBindingApi()
116144

145+
const [selectedPath, setSelectedPath] = useState()
146+
117147
const {value: addressList, loading} = useAsync(async () => {
118148
let addresses = []
149+
if (selectedPath === undefined) return addresses
119150
try {
120151
if (ledgerBindingApi) {
121152
addresses = await ledgerBindingApi.getAddressList(
122153
new Array(HARDWARE_ACCOUNT_PAGE_LIMIT)
123154
.fill('')
124155
.map((_item, index) => index + offset),
156+
selectedPath?.value,
125157
)
126158
}
127159
} catch (e) {
@@ -138,7 +170,13 @@ function ImportHwAccount() {
138170
return {address: address?.toLowerCase?.(), hdPath}
139171
})
140172
: addresses
141-
}, [offset, ledgerBindingApi])
173+
}, [selectedPath, offset, ledgerBindingApi])
174+
175+
const handleSelectChange = useCallback(path => {
176+
// if user change the hd path, reset the offset
177+
setSelectedPath(path)
178+
setOffset(0)
179+
}, [])
142180

143181
const {value: deviceInfo} = useAsync(async () => {
144182
if (ledgerBindingApi) {
@@ -183,6 +221,16 @@ function ImportHwAccount() {
183221
)
184222
}, [checkboxStatusObj])
185223

224+
useEffect(() => {
225+
if (selectedPath === undefined) {
226+
if (type === NETWORK_TYPE.CFX) {
227+
setSelectedPath(confluxLedgerPath)
228+
} else if (type === NETWORK_TYPE.ETH) {
229+
setSelectedPath(ethereumLedgerPath[0])
230+
}
231+
}
232+
}, [type, selectedPath])
233+
186234
const onSelectAllAccount = () => {
187235
const ret = {}
188236
Object.keys(checkboxStatusObj).forEach(
@@ -278,17 +326,38 @@ function ImportHwAccount() {
278326
className="mt-5"
279327
label={<p className="text-sm text-gray-40">{t('hdPath')}</p>}
280328
>
281-
<Input
282-
value={`BIP 44 Standard(${
283-
type === NETWORK_TYPE.CFX
284-
? DEFAULT_CFX_HDPATH
285-
: DEFAULT_ETH_HDPATH
286-
})`}
287-
width="w-full box-border"
288-
readOnly
289-
className="pointer-events-none"
290-
id="hd-path"
291-
/>
329+
{type === NETWORK_TYPE.CFX ? (
330+
<Input
331+
value={`${confluxLedgerPath?.name}(${confluxLedgerPath?.value})`}
332+
width="w-full box-border"
333+
readOnly
334+
className="pointer-events-none"
335+
id="hd-path"
336+
/>
337+
) : (
338+
<Dropdown
339+
overlay={
340+
<Menu>
341+
{ethereumLedgerPath.map(({name, value}) => (
342+
<MenuItem
343+
onClick={() => handleSelectChange({name, value})}
344+
selected={selectedPath?.value === value}
345+
containerClassName="w-full"
346+
key={value}
347+
itemKey={value}
348+
>{`${name}(${value})`}</MenuItem>
349+
))}
350+
</Menu>
351+
}
352+
>
353+
<Input
354+
value={`${selectedPath?.name}(${selectedPath?.value})`}
355+
width="w-full box-border"
356+
readOnly
357+
id="hd-path"
358+
/>
359+
</Dropdown>
360+
)}
292361
</CompWithLabel>
293362
<CompWithLabel
294363
label={

packages/ui/components/Menu/MenuItem.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function MenuItem({
1010
icon,
1111
onClick,
1212
disabled,
13+
containerClassName = '',
1314
}) {
1415
const style = useMemo(() => {
1516
if (disabled) return 'text-gray-40 cursor-not-allowed'
@@ -37,7 +38,7 @@ function MenuItem({
3738
onClick={() => !disabled && onClick && onClick(itemKey)}
3839
className={`w-50 h-12 ${
3940
icon ? 'px-4' : 'pl-12 pr-4'
40-
} flex items-center ${style}`}
41+
} flex items-center ${style} ${containerClassName}`}
4142
>
4243
<div className="flex items-center flex-1">
4344
{iconComp}
@@ -74,6 +75,7 @@ MenuItem.propTypes = {
7475
icon: PropTypes.node,
7576
onClick: PropTypes.func,
7677
disabled: PropTypes.bool,
78+
containerClassName: PropTypes.string,
7779
}
7880

7981
export default MenuItem

0 commit comments

Comments
 (0)