Skip to content

Commit 9f3bb9c

Browse files
committed
feat: Add ADR8 option for opening Ledger wallet
1 parent aea0915 commit 9f3bb9c

File tree

14 files changed

+615
-85
lines changed

14 files changed

+615
-85
lines changed

src/app/components/Toolbar/Features/AccountSelector/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const Account = memo((props: AccountProps) => {
7575
</Box>
7676
<Box direction="row-responsive">
7777
<Box align="start" flex="grow" direction="row">
78-
{walletTypes[props.type]} {props.details && <Text size="small">({props.details})</Text>}
78+
{walletTypes[props.type]} {props.details && <Text size="small">&nbsp;({props.details})</Text>}
7979
</Box>
8080
<Box>
8181
<AmountFormatter amount={props.balance} />

src/app/lib/ledger.test.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Ledger, LedgerSigner } from './ledger'
1+
import { DerivationPathTypeLegacy, DerivationPathTypeAdr8, Ledger, LedgerSigner } from './ledger'
22
import OasisApp from '@oasisprotocol/ledger'
33
import { WalletError, WalletErrors } from 'types/errors'
44
import { Wallet, WalletType } from 'app/state/wallet/types'
@@ -18,29 +18,46 @@ describe('Ledger Library', () => {
1818
describe('Ledger', () => {
1919
it('enumerateAccounts should pass when Oasis App is open', async () => {
2020
mockAppIsOpen('Oasis')
21-
const accounts = Ledger.enumerateAccounts({} as any, 0)
21+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 0)
22+
await expect(accounts).resolves.toEqual([])
23+
})
24+
it('enumerateAccounts should pass when Oasis App is open', async () => {
25+
mockAppIsOpen('Oasis')
26+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeAdr8, 0)
2227
await expect(accounts).resolves.toEqual([])
2328
})
2429

2530
it('Should catch "Oasis App is not open"', async () => {
2631
mockAppIsOpen('BOLOS')
27-
const accountsMainMenu = Ledger.enumerateAccounts({} as any, 0)
32+
const accountsMainMenu = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 0)
2833
await expect(accountsMainMenu).rejects.toThrowError(WalletError)
2934
await expect(accountsMainMenu).rejects.toHaveProperty('type', WalletErrors.LedgerOasisAppIsNotOpen)
3035

3136
mockAppIsOpen('Ethereum')
32-
const accountsEth = Ledger.enumerateAccounts({} as any, 0)
37+
const accountsEth = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 0)
3338
await expect(accountsEth).rejects.toThrowError(WalletError)
3439
await expect(accountsEth).rejects.toHaveProperty('type', WalletErrors.LedgerOasisAppIsNotOpen)
3540
})
3641

37-
it('Should enumerate and return the accounts', async () => {
42+
it('Should enumerate and return adr8 accounts', async () => {
43+
mockAppIsOpen('Oasis')
44+
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
45+
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([1, 2, 3])) })
46+
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([4, 5, 6])) })
47+
48+
const accounts = await Ledger.enumerateAccounts({} as any, DerivationPathTypeAdr8, 2)
49+
expect(accounts).toHaveLength(2)
50+
expect(accounts).toContainEqual({ path: [44, 474, 0], publicKey: new Uint8Array([1, 2, 3]) })
51+
expect(accounts).toContainEqual({ path: [44, 474, 1], publicKey: new Uint8Array([4, 5, 6]) })
52+
})
53+
54+
it('Should enumerate and return legacy accounts', async () => {
3855
mockAppIsOpen('Oasis')
3956
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
4057
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([1, 2, 3])) })
4158
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([4, 5, 6])) })
4259

43-
const accounts = await Ledger.enumerateAccounts({} as any, 2)
60+
const accounts = await Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 2)
4461
expect(accounts).toHaveLength(2)
4562
expect(accounts).toContainEqual({ path: [44, 474, 0, 0, 0], publicKey: new Uint8Array([1, 2, 3]) })
4663
expect(accounts).toContainEqual({ path: [44, 474, 0, 0, 1], publicKey: new Uint8Array([4, 5, 6]) })
@@ -51,7 +68,7 @@ describe('Ledger Library', () => {
5168
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
5269
pubKey.mockResolvedValueOnce({ return_code: 0x6804 })
5370

54-
const accounts = Ledger.enumerateAccounts({} as any)
71+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy)
5572
await expect(accounts).rejects.toThrowError(WalletError)
5673
await expect(accounts).rejects.toHaveProperty('type', WalletErrors.LedgerCannotOpenOasisApp)
5774
})
@@ -61,7 +78,7 @@ describe('Ledger Library', () => {
6178
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
6279
pubKey.mockResolvedValueOnce({ return_code: 0x6400 })
6380

64-
const accounts = Ledger.enumerateAccounts({} as any)
81+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy)
6582
await expect(accounts).rejects.toThrowError(WalletError)
6683
await expect(accounts).rejects.toHaveProperty('type', WalletErrors.LedgerAppVersionNotSupported)
6784
})
@@ -71,7 +88,7 @@ describe('Ledger Library', () => {
7188
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
7289
pubKey.mockResolvedValueOnce({ return_code: -1, error_message: 'unknown dummy error' })
7390

74-
const accounts = Ledger.enumerateAccounts({} as any)
91+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy)
7592
await expect(accounts).rejects.toThrowError(WalletError)
7693
await expect(accounts).rejects.toThrow(/unknown dummy error/)
7794
await expect(accounts).rejects.toHaveProperty('type', WalletErrors.LedgerUnknownError)
@@ -96,7 +113,7 @@ describe('Ledger Library', () => {
96113
it('Should fail without USB transport', () => {
97114
const signer = new LedgerSigner({
98115
type: WalletType.Ledger,
99-
path: [44, 474, 0, 0, 0],
116+
path: Ledger.mustGetPath(DerivationPathTypeAdr8, 0),
100117
publicKey: '00',
101118
} as Wallet)
102119

@@ -111,7 +128,7 @@ describe('Ledger Library', () => {
111128

112129
const signer = new LedgerSigner({
113130
type: WalletType.Ledger,
114-
path: [44, 474, 0, 0, 0],
131+
path: Ledger.mustGetPath(DerivationPathTypeAdr8, 0),
115132
publicKey: 'aabbcc',
116133
} as Wallet)
117134

@@ -124,7 +141,7 @@ describe('Ledger Library', () => {
124141

125142
const signer = new LedgerSigner({
126143
type: WalletType.Ledger,
127-
path: [44, 474, 0, 0, 0],
144+
path: Ledger.mustGetPath(DerivationPathTypeAdr8, 0),
128145
publicKey: '00',
129146
} as Wallet)
130147

@@ -140,7 +157,7 @@ describe('Ledger Library', () => {
140157

141158
const signer = new LedgerSigner({
142159
type: WalletType.Ledger,
143-
path: [44, 474, 0, 0, 0],
160+
path: Ledger.mustGetPath(DerivationPathTypeAdr8, 0),
144161
publicKey: '00',
145162
} as Wallet)
146163

src/app/lib/ledger.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { WalletError, WalletErrors } from 'types/errors'
55
import { hex2uint } from './helpers'
66
import type Transport from '@ledgerhq/hw-transport'
77

8+
export const DerivationPathTypeAdr8 = 'adr8'
9+
export const DerivationPathTypeLegacy = 'legacy'
10+
811
interface Response {
912
return_code: number
1013
error_message: string
@@ -34,7 +37,18 @@ const successOrThrow = (response: Response, message: string) => {
3437
}
3538

3639
export class Ledger {
37-
public static async enumerateAccounts(transport: Transport, count = 5) {
40+
public static mustGetPath(pathType: string, i: number) {
41+
switch (pathType) {
42+
case DerivationPathTypeAdr8:
43+
return [44, 474, i]
44+
case DerivationPathTypeLegacy:
45+
return [44, 474, 0, 0, i]
46+
}
47+
48+
throw new TypeError('invalid pathType: ' + pathType)
49+
}
50+
51+
public static async enumerateAccounts(transport: Transport, pathType: string, count = 5) {
3852
const accounts: LedgerAccount[] = []
3953

4054
try {
@@ -44,7 +58,7 @@ export class Ledger {
4458
throw new WalletError(WalletErrors.LedgerOasisAppIsNotOpen, 'Oasis App is not open')
4559
}
4660
for (let i = 0; i < count; i++) {
47-
const path = [44, 474, 0, 0, i]
61+
const path = Ledger.mustGetPath(pathType, i)
4862
const publicKeyResponse = successOrThrow(await app.publicKey(path), 'ledger public key')
4963
accounts.push({ path, publicKey: new Uint8Array(publicKeyResponse.pk as Buffer) })
5064
}

0 commit comments

Comments
 (0)