Skip to content

Commit ab06b8c

Browse files
committed
app: UI fixes and translation
1 parent 7068d1d commit ab06b8c

11 files changed

Lines changed: 117 additions & 46 deletions

File tree

src/app/lib/ledger.test.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DerivationPathLegacy, DerivationPathAdr8, 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,34 +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, DerivationPathLegacy, 0)
21+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 0)
2222
await expect(accounts).resolves.toEqual([])
2323
})
2424
it('enumerateAccounts should pass when Oasis App is open', async () => {
2525
mockAppIsOpen('Oasis')
26-
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathAdr8, 0)
26+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeAdr8, 0)
2727
await expect(accounts).resolves.toEqual([])
2828
})
2929

3030
it('Should catch "Oasis App is not open"', async () => {
3131
mockAppIsOpen('BOLOS')
32-
const accountsMainMenu = Ledger.enumerateAccounts({} as any, DerivationPathLegacy, 0)
32+
const accountsMainMenu = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 0)
3333
await expect(accountsMainMenu).rejects.toThrowError(WalletError)
3434
await expect(accountsMainMenu).rejects.toHaveProperty('type', WalletErrors.LedgerOasisAppIsNotOpen)
3535

3636
mockAppIsOpen('Ethereum')
37-
const accountsEth = Ledger.enumerateAccounts({} as any, DerivationPathLegacy, 0)
37+
const accountsEth = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 0)
3838
await expect(accountsEth).rejects.toThrowError(WalletError)
3939
await expect(accountsEth).rejects.toHaveProperty('type', WalletErrors.LedgerOasisAppIsNotOpen)
4040
})
4141

42-
it('Should enumerate and return the accounts', async () => {
42+
it('Should enumerate and return adr8 accounts', async () => {
4343
mockAppIsOpen('Oasis')
4444
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
4545
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([1, 2, 3])) })
4646
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([4, 5, 6])) })
4747

48-
const accounts = await Ledger.enumerateAccounts({} as any, DerivationPathLegacy, 2)
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 () => {
55+
mockAppIsOpen('Oasis')
56+
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
57+
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([1, 2, 3])) })
58+
pubKey.mockResolvedValueOnce({ return_code: 0x9000, pk: Buffer.from(new Uint8Array([4, 5, 6])) })
59+
60+
const accounts = await Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy, 2)
4961
expect(accounts).toHaveLength(2)
5062
expect(accounts).toContainEqual({ path: [44, 474, 0, 0, 0], publicKey: new Uint8Array([1, 2, 3]) })
5163
expect(accounts).toContainEqual({ path: [44, 474, 0, 0, 1], publicKey: new Uint8Array([4, 5, 6]) })
@@ -56,7 +68,7 @@ describe('Ledger Library', () => {
5668
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
5769
pubKey.mockResolvedValueOnce({ return_code: 0x6804 })
5870

59-
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathLegacy)
71+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy)
6072
await expect(accounts).rejects.toThrowError(WalletError)
6173
await expect(accounts).rejects.toHaveProperty('type', WalletErrors.LedgerCannotOpenOasisApp)
6274
})
@@ -66,7 +78,7 @@ describe('Ledger Library', () => {
6678
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
6779
pubKey.mockResolvedValueOnce({ return_code: 0x6400 })
6880

69-
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathLegacy)
81+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy)
7082
await expect(accounts).rejects.toThrowError(WalletError)
7183
await expect(accounts).rejects.toHaveProperty('type', WalletErrors.LedgerAppVersionNotSupported)
7284
})
@@ -76,7 +88,7 @@ describe('Ledger Library', () => {
7688
const pubKey: jest.Mock<any> = OasisApp.prototype.publicKey
7789
pubKey.mockResolvedValueOnce({ return_code: -1, error_message: 'unknown dummy error' })
7890

79-
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathLegacy)
91+
const accounts = Ledger.enumerateAccounts({} as any, DerivationPathTypeLegacy)
8092
await expect(accounts).rejects.toThrowError(WalletError)
8193
await expect(accounts).rejects.toThrow(/unknown dummy error/)
8294
await expect(accounts).rejects.toHaveProperty('type', WalletErrors.LedgerUnknownError)
@@ -116,7 +128,7 @@ describe('Ledger Library', () => {
116128

117129
const signer = new LedgerSigner({
118130
type: WalletType.Ledger,
119-
path: Ledger.mustGetPath(DerivationPathLegacy, 0),
131+
path: Ledger.mustGetPath(DerivationPathTypeAdr8, 0),
120132
publicKey: 'aabbcc',
121133
} as Wallet)
122134

src/app/lib/ledger.ts

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

8-
export const DerivationPathAdr8 = 'adr8';
9-
export const DerivationPathLegacy = 'legacy';
8+
export const DerivationPathTypeAdr8 = 'adr8'
9+
export const DerivationPathTypeLegacy = 'legacy'
1010

1111
interface Response {
1212
return_code: number
@@ -39,13 +39,13 @@ const successOrThrow = (response: Response, message: string) => {
3939
export class Ledger {
4040
public static mustGetPath(pathType: string, i: number) {
4141
switch (pathType) {
42-
case DerivationPathAdr8:
43-
return [44, 474, i];
44-
case DerivationPathLegacy:
45-
return [44, 474, 0, 0, i];
42+
case DerivationPathTypeAdr8:
43+
return [44, 474, i]
44+
case DerivationPathTypeLegacy:
45+
return [44, 474, 0, 0, i]
4646
}
4747

48-
throw new TypeError("invalid pathType: "+pathType);
48+
throw new TypeError('invalid pathType: ' + pathType)
4949
}
5050

5151
public static async enumerateAccounts(transport: Transport, pathType: string, count = 5) {
@@ -58,7 +58,7 @@ export class Ledger {
5858
throw new WalletError(WalletErrors.LedgerOasisAppIsNotOpen, 'Oasis App is not open')
5959
}
6060
for (let i = 0; i < count; i++) {
61-
const path = Ledger.mustGetPath(pathType, i);
61+
const path = Ledger.mustGetPath(pathType, i)
6262
const publicKeyResponse = successOrThrow(await app.publicKey(path), 'ledger public key')
6363
accounts.push({ path, publicKey: new Uint8Array(publicKeyResponse.pk as Buffer) })
6464
}

src/app/pages/OpenWalletPage/Features/FromLedger/index.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ErrorFormatter } from 'app/components/ErrorFormatter'
77
import { LedgerStepFormatter } from 'app/components/LedgerStepFormatter'
88
import { ResponsiveLayer } from 'app/components/ResponsiveLayer'
99
import { Account } from 'app/components/Toolbar/Features/AccountSelector'
10-
import { DerivationPathAdr8, DerivationPathLegacy } from 'app/lib/ledger'
10+
import { DerivationPathTypeAdr8, DerivationPathTypeLegacy } from 'app/lib/ledger'
1111
import { ledgerActions, useLedgerSlice } from 'app/state/ledger'
1212
import { selectLedger, selectSelectedLedgerAccounts } from 'app/state/ledger/selectors'
1313
import { LedgerAccount, LedgerStep } from 'app/state/ledger/types'
@@ -105,22 +105,37 @@ export function FromLedgerModal(props: FromLedgerModalProps) {
105105
const error = ledger.error
106106
const selectedAccounts = useSelector(selectSelectedLedgerAccounts)
107107
const dispatch = useDispatch()
108+
const [initDerivationPathType, derivationPathSetValue] = useState(DerivationPathTypeAdr8)
109+
110+
const derivationPathTypes = [
111+
{
112+
label: t('ledger.derivationPathType.adr8', 'ADR 8 derivation path'),
113+
value: DerivationPathTypeAdr8,
114+
},
115+
{
116+
label: t('ledger.derivationPathType.legacy', 'Legacy derivation path'),
117+
value: DerivationPathTypeLegacy,
118+
},
119+
]
108120

109121
const openAccounts = () => {
110122
dispatch(walletActions.openWalletsFromLedger(selectedAccounts))
111123
}
112124

125+
const onChangeDerivationPathType = (arg: any) => {
126+
derivationPathSetValue(arg.value)
127+
dispatch(ledgerActions.enumerateAccounts(arg.value))
128+
}
129+
113130
useEffect(() => {
114-
dispatch(ledgerActions.enumerateAccounts())
131+
dispatch(ledgerActions.enumerateAccounts(initDerivationPathType))
115132
return () => {
116133
dispatch(ledgerActions.clear())
117134
}
118135
}, [dispatch, ledgerActions])
119136

120137
const cancelDisabled = ledger.step === LedgerStep.Done || error ? false : true
121138
const confirmDisabled = ledger.step !== LedgerStep.Done || selectedAccounts.length === 0
122-
// TODO
123-
const [value, setValue] = useState('');
124139

125140
return (
126141
<ResponsiveLayer position="center" modal>
@@ -129,10 +144,13 @@ export function FromLedgerModal(props: FromLedgerModalProps) {
129144
{t('openWallet.ledger.selectWallets', 'Select the wallets to open')}
130145
</Heading>
131146
<Select
132-
id='DerivationPathSelect'
133-
options={[ DerivationPathAdr8, DerivationPathLegacy ]}
134-
value={DerivationPathAdr8}
135-
onChange={({ value: nextValue }) => setValue(nextValue)}
147+
id="DerivationPathSelect"
148+
labelKey="label"
149+
margin={{ bottom: 'small', top: 'none' }}
150+
onChange={onChangeDerivationPathType}
151+
options={derivationPathTypes}
152+
value={initDerivationPathType}
153+
valueKey={{ key: 'value', reduce: true }}
136154
/>
137155
{ledger.step && ledger.step !== LedgerStep.Done && (
138156
<Box direction="row" gap="medium" alignContent="center">

src/app/state/ledger/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ const slice = createSlice({
1313
reducers: {
1414
clear(state, action: PayloadAction<void>) {
1515
state.accounts = []
16+
state.derivationPathType = undefined
1617
state.error = undefined
1718
},
18-
enumerateAccounts(state, action: PayloadAction<void>) {
19+
enumerateAccounts(state, action: PayloadAction<string>) {
1920
state.step = undefined
21+
state.derivationPathType = action.payload
2022
state.accounts = []
2123
},
2224
toggleAccount(state, action: PayloadAction<number>) {
@@ -29,6 +31,10 @@ const slice = createSlice({
2931
setStep(state, action: PayloadAction<LedgerStep>) {
3032
state.step = action.payload
3133
},
34+
setDerivationPathType(state, action: PayloadAction<string>) {
35+
state.derivationPathType = action.payload
36+
state.accounts = []
37+
},
3238
operationFailed(state, action: PayloadAction<ErrorPayload>) {
3339
state.error = action.payload
3440
state.step = undefined

src/app/state/ledger/saga.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expectSaga } from 'redux-saga-test-plan'
33
import { ledgerActions } from '.'
44
import { ledgerSaga, sign } from './saga'
55
import * as matchers from 'redux-saga-test-plan/matchers'
6-
import {DerivationPathAdr8, DerivationPathLegacy, Ledger, LedgerSigner} from 'app/lib/ledger'
6+
import { DerivationPathTypeAdr8, DerivationPathTypeLegacy, Ledger, LedgerSigner } from 'app/lib/ledger'
77
import { getBalance } from '../wallet/saga'
88
import { addressToPublicKey } from 'app/lib/helpers'
99
import { LedgerStep } from './types'
@@ -15,7 +15,7 @@ describe('Ledger Sagas', () => {
1515
it('should list ADR8 accounts', async () => {
1616
const validAccount = {
1717
publicKey: await addressToPublicKey('oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk'),
18-
path: Ledger.mustGetPath(DerivationPathAdr8, 0),
18+
path: Ledger.mustGetPath(DerivationPathTypeAdr8, 0),
1919
}
2020

2121
return expectSaga(ledgerSaga)
@@ -25,7 +25,7 @@ describe('Ledger Sagas', () => {
2525
[matchers.call.fn(Ledger.enumerateAccounts), [validAccount]],
2626
[matchers.call.fn(getBalance), {}],
2727
])
28-
.dispatch(ledgerActions.enumerateAccounts())
28+
.dispatch(ledgerActions.enumerateAccounts(DerivationPathTypeAdr8))
2929
.put(ledgerActions.setStep(LedgerStep.Done))
3030
.put.actionType(ledgerActions.accountsListed.type)
3131
.run(50)
@@ -34,7 +34,7 @@ describe('Ledger Sagas', () => {
3434
it('should list legacy accounts', async () => {
3535
const validAccount = {
3636
publicKey: await addressToPublicKey('oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk'),
37-
path: Ledger.mustGetPath(DerivationPathLegacy, 0),
37+
path: Ledger.mustGetPath(DerivationPathTypeLegacy, 0),
3838
}
3939

4040
return expectSaga(ledgerSaga)
@@ -44,7 +44,7 @@ describe('Ledger Sagas', () => {
4444
[matchers.call.fn(Ledger.enumerateAccounts), [validAccount]],
4545
[matchers.call.fn(getBalance), {}],
4646
])
47-
.dispatch(ledgerActions.enumerateAccounts())
47+
.dispatch(ledgerActions.enumerateAccounts(DerivationPathTypeAdr8))
4848
.put(ledgerActions.setStep(LedgerStep.Done))
4949
.put.actionType(ledgerActions.accountsListed.type)
5050
.run(50)
@@ -56,7 +56,7 @@ describe('Ledger Sagas', () => {
5656
[matchers.call.fn(TransportWebUSB.isSupported), false],
5757
[matchers.call.fn(TransportWebUSB.create), { close: () => {} }],
5858
])
59-
.dispatch(ledgerActions.enumerateAccounts())
59+
.dispatch(ledgerActions.enumerateAccounts(DerivationPathTypeAdr8))
6060
.put.like({ action: { payload: { code: WalletErrors.USBTransportNotSupported } } })
6161
.run(50)
6262
})
@@ -67,7 +67,7 @@ describe('Ledger Sagas', () => {
6767
[matchers.call.fn(TransportWebUSB.isSupported), true],
6868
[matchers.call.fn(TransportWebUSB.create), Promise.reject(new Error('No device selected'))],
6969
])
70-
.dispatch(ledgerActions.enumerateAccounts())
70+
.dispatch(ledgerActions.enumerateAccounts(DerivationPathTypeAdr8))
7171
.put.like({ action: { payload: { code: WalletErrors.LedgerNoDeviceSelected } } })
7272
.run(50)
7373
})
@@ -78,7 +78,7 @@ describe('Ledger Sagas', () => {
7878
[matchers.call.fn(TransportWebUSB.isSupported), true],
7979
[matchers.call.fn(TransportWebUSB.create), Promise.reject(new Error('Dummy error'))],
8080
])
81-
.dispatch(ledgerActions.enumerateAccounts())
81+
.dispatch(ledgerActions.enumerateAccounts(DerivationPathTypeAdr8))
8282
.put.like({ action: { payload: { code: WalletErrors.USBTransportError, message: 'Dummy error' } } })
8383
.run(50)
8484
})
@@ -90,7 +90,7 @@ describe('Ledger Sagas', () => {
9090
[matchers.call.fn(TransportWebUSB.create), { close: () => {} }],
9191
[matchers.call.fn(Ledger.enumerateAccounts), Promise.reject(new Error('Dummy error'))],
9292
])
93-
.dispatch(ledgerActions.enumerateAccounts())
93+
.dispatch(ledgerActions.enumerateAccounts(DerivationPathTypeAdr8))
9494
.put.like({ action: { payload: { code: WalletErrors.UnknownError, message: 'Dummy error' } } })
9595
.run(50)
9696
})

src/app/state/ledger/saga.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
// import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2+
import type Transport from '@ledgerhq/hw-transport'
23
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
34
import * as oasis from '@oasisprotocol/client'
45
import { publicKeyToAddress, uint2hex } from 'app/lib/helpers'
5-
import {DerivationPathAdr8, Ledger, LedgerSigner} from 'app/lib/ledger'
6+
import { Ledger, LedgerSigner } from 'app/lib/ledger'
67
import { OasisTransaction } from 'app/lib/transaction'
78
import { all, call, put, select, takeEvery } from 'typed-redux-saga'
89
import { ErrorPayload, WalletError, WalletErrors } from 'types/errors'
910

1011
import { ledgerActions } from '.'
1112
import { selectChainContext } from '../network/selectors'
1213
import { getBalance } from '../wallet/saga'
14+
import { selectDerivationPathType } from './selectors'
1315
import { LedgerAccount, LedgerStep } from './types'
14-
import type Transport from '@ledgerhq/hw-transport'
1516

1617
function* setStep(step: LedgerStep) {
1718
yield* put(ledgerActions.setStep(step))
@@ -41,8 +42,14 @@ function* enumerateAccounts() {
4142
transport = yield* getUSBTransport()
4243

4344
yield* setStep(LedgerStep.LoadingAccounts)
44-
// TODO: Pick the selected derivation path.
45-
const accounts = yield* call(Ledger.enumerateAccounts, transport, DerivationPathAdr8)
45+
const derivationPathType = yield* select(selectDerivationPathType)
46+
if (!derivationPathType) {
47+
throw new WalletError(
48+
WalletErrors.LedgerNoDerivationPathTypeProvided,
49+
'No derivation path type provided',
50+
)
51+
}
52+
const accounts = yield* call(Ledger.enumerateAccounts, transport, derivationPathType)
4653

4754
const balances = yield* all(accounts.map(a => call(getBalance, a.publicKey)))
4855
const addresses = yield* all(accounts.map(a => call(publicKeyToAddress, a.publicKey)))

src/app/state/ledger/selectors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const selectSlice = (state: RootState) => state.ledger || initialState
77

88
export const selectError = createSelector([selectSlice], state => state.error)
99
export const selectLedger = createSelector([selectSlice], state => state)
10+
export const selectDerivationPathType = createSelector([selectSlice], state => state.derivationPathType)
1011
export const selectLedgerAccounts = createSelector([selectSlice], state => state.accounts)
1112
export const selectSelectedLedgerAccounts = createSelector([selectLedgerAccounts], state =>
1213
state.filter(a => a.selected),

src/app/state/ledger/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export enum LedgerStep {
1919

2020
export interface LedgerState {
2121
accounts: LedgerAccount[]
22+
derivationPathType?: string
2223
step?: LedgerStep
2324
error?: ErrorPayload
2425
}

src/locales/en/translation.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@
182182
"loadingAccounts": "Loading account details",
183183
"loadingBalances": "Loading balance details",
184184
"done": "Done"
185+
},
186+
"derivationPathType": {
187+
"adr8": "ADR 8 derivation path",
188+
"legacy": "Legacy derivation path"
185189
}
186190
},
187191
"transaction": {

0 commit comments

Comments
 (0)