Skip to content

Commit b74dbea

Browse files
CP-12136: Fix avalanche_getAccountPubKey for ledger (#3650)
1 parent 65c1d8c commit b74dbea

File tree

15 files changed

+911
-245
lines changed

15 files changed

+911
-245
lines changed

packages/core-mobile/app/new/features/ledger/hooks/useLedgerWallet.ts

Lines changed: 22 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
LedgerDerivationPathType,
1010
LedgerKeys,
1111
LedgerTransportState,
12-
PublicKeyInfo,
1312
WalletCreationOptions,
1413
WalletUpdateOptions,
1514
WalletUpdateSolanaOptions
@@ -23,10 +22,8 @@ import Logger from 'utils/Logger'
2322
import { uuid } from 'utils/uuid'
2423
import { CoreAccountType } from '@avalabs/types'
2524
import BiometricsSDK from 'utils/BiometricsSDK'
26-
import { Curve } from 'utils/publicKeys'
2725
import { LedgerWalletSecretSchema } from '../utils'
2826
import { useLedgerWalletMap } from '../store'
29-
import { DerivationPathKey, getLedgerDerivationPath } from '../consts'
3027

3128
export interface UseLedgerWalletReturn {
3229
// Connection state
@@ -135,19 +132,9 @@ export function useLedgerWallet(): UseLedgerWalletReturn {
135132

136133
const newWalletId = uuid()
137134

138-
// Use addresses for display and xpubs for wallet functionality
139-
const { addresses, xpubs } = avalancheKeys
140-
135+
const { addresses, xpubs, publicKeys } = avalancheKeys
141136
const formattedAddresses = getFormattedAddresses(addresses)
142137

143-
// Create the public keys array
144-
const publicKeysToStore = getPublicKeysForAccount(
145-
formattedAddresses,
146-
solanaKeys,
147-
0 // For wallet creation, we are only adding the first account (index 0)
148-
)
149-
// Store the Ledger wallet with the specified derivation path type
150-
// For BIP44, store xpub in per-account format for future account additions
151138
await dispatch(
152139
storeWallet({
153140
walletId: newWalletId,
@@ -166,7 +153,12 @@ export function useLedgerWallet(): UseLedgerWalletReturn {
166153
}
167154
}
168155
}),
169-
publicKeys: publicKeysToStore
156+
publicKeys: {
157+
0: [
158+
...publicKeys,
159+
...(solanaKeys?.length > 0 ? [solanaKeys[0]] : [])
160+
].filter(Boolean)
161+
}
170162
}),
171163
type:
172164
derivationPathType === LedgerDerivationPathType.BIP44
@@ -235,18 +227,9 @@ export function useLedgerWallet(): UseLedgerWalletReturn {
235227
throw new Error('Missing Avalanche keys for account creation')
236228
}
237229

238-
// Use addresses for display and xpubs for wallet functionality
239-
const { addresses, xpubs } = avalancheKeys
240-
230+
const { addresses, xpubs, publicKeys: newPublicKeys } = avalancheKeys
241231
const formattedAddresses = getFormattedAddresses(addresses)
242232

243-
// Create the public keys array
244-
const publicKeysToUpdate = getPublicKeysForAccount(
245-
formattedAddresses,
246-
solanaKeys,
247-
accountIndexToUse
248-
)
249-
250233
const walletSecretResult = await BiometricsSDK.loadWalletSecret(
251234
walletId
252235
)
@@ -292,7 +275,13 @@ export function useLedgerWallet(): UseLedgerWalletReturn {
292275
}
293276
}
294277
}),
295-
publicKeys: [...publicKeys, ...publicKeysToUpdate] // Append new account public keys to existing array
278+
publicKeys: {
279+
...publicKeys,
280+
[accountIndexToUse]: [
281+
...newPublicKeys,
282+
...(solanaKeys.length > 0 ? [solanaKeys[0]] : [])
283+
].filter(Boolean)
284+
}
296285
})
297286
})
298287
).unwrap()
@@ -372,6 +361,7 @@ export function useLedgerWallet(): UseLedgerWalletReturn {
372361
}
373362

374363
const { publicKeys, ...baseWalletSecret } = parsedWalletSecret
364+
const accountIndex = account.index
375365

376366
// Update the Ledger wallet extended public keys for new account
377367
await dispatch(
@@ -381,18 +371,13 @@ export function useLedgerWallet(): UseLedgerWalletReturn {
381371
type: walletType,
382372
walletSecret: JSON.stringify({
383373
...baseWalletSecret,
384-
publicKeys: [
374+
publicKeys: {
385375
...publicKeys,
386-
...(solanaKeys.length > 0 && solanaKeys[0]?.key
387-
? [
388-
{
389-
key: solanaKeys[0].key, // Solana addresses don't use 0x prefix
390-
derivationPath: solanaKeys[0].derivationPath, // Use the same path from getSolanaKeys
391-
curve: Curve.ED25519
392-
}
393-
]
394-
: [])
395-
] // Append new account public keys to existing array
376+
[accountIndex]: [
377+
...(publicKeys[accountIndex] ?? []),
378+
...(solanaKeys.length > 0 ? [solanaKeys[0]] : [])
379+
].filter(Boolean)
380+
}
396381
})
397382
})
398383
).unwrap()
@@ -454,53 +439,3 @@ const getFormattedAddresses = (address: {
454439
: address.coreEth
455440
}
456441
}
457-
458-
const getPublicKeysForAccount = (
459-
address: {
460-
evm: string
461-
avm: string
462-
pvm: string
463-
btc: string
464-
coreEth: string
465-
},
466-
solanaKeys: PublicKeyInfo[],
467-
accountIndex = 0
468-
): PublicKeyInfo[] => {
469-
return [
470-
// Use formatted addresses
471-
{
472-
key: address.evm, // Use formatted address
473-
derivationPath: getLedgerDerivationPath(
474-
DerivationPathKey.EVM,
475-
accountIndex
476-
),
477-
curve: Curve.SECP256K1
478-
},
479-
{
480-
key: address.avm,
481-
derivationPath: getLedgerDerivationPath(
482-
DerivationPathKey.AVALANCHE,
483-
accountIndex
484-
),
485-
curve: Curve.SECP256K1
486-
},
487-
{
488-
key: address.pvm,
489-
derivationPath: getLedgerDerivationPath(
490-
DerivationPathKey.AVALANCHE,
491-
accountIndex
492-
),
493-
curve: Curve.SECP256K1
494-
},
495-
// Only include Solana key if it exists
496-
...(solanaKeys.length > 0 && solanaKeys[0]?.key
497-
? [
498-
{
499-
key: solanaKeys[0].key, // Solana addresses don't use 0x prefix
500-
derivationPath: solanaKeys[0].derivationPath, // Use the same path from getSolanaKeys
501-
curve: Curve.ED25519
502-
}
503-
]
504-
: [])
505-
]
506-
}

packages/core-mobile/app/new/features/ledger/screens/AppConnectionAddAccountScreen.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export const AppConnectionAddAccountScreen = (): JSX.Element => {
117117

118118
return (
119119
<AppConnectionScreen
120+
selectedDerivationPath={derivationPathType}
120121
completeStepTitle={`Your Account\nis being set up`}
121122
handleComplete={handleComplete}
122123
deviceId={device?.id}

packages/core-mobile/app/new/features/ledger/screens/AppConnectionOnboardingScreen.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { useSelector } from 'react-redux'
44
import { selectIsDeveloperMode } from 'store/settings/advanced'
55
import { Alert } from 'react-native'
66
import LedgerService from 'services/ledger/LedgerService'
7-
import { LedgerKeysByNetwork } from 'services/ledger/types'
7+
import {
8+
LedgerDerivationPathType,
9+
LedgerKeysByNetwork
10+
} from 'services/ledger/types'
811
import { useRouter } from 'expo-router'
912
import { useLedgerWallet } from '../hooks/useLedgerWallet'
1013
import { useLedgerSetupContext } from '../contexts/LedgerSetupContext'
@@ -109,6 +112,9 @@ export const AppConnectionOnboardingScreen = (): JSX.Element => {
109112

110113
return (
111114
<AppConnectionScreen
115+
selectedDerivationPath={
116+
selectedDerivationPath ?? LedgerDerivationPathType.BIP44
117+
}
112118
completeStepTitle={`Your Ledger wallet\nis being set up`}
113119
handleComplete={handleComplete}
114120
deviceId={connectedDeviceId}

packages/core-mobile/app/new/features/ledger/screens/AppConnectionScreen.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ import React, {
1818
import { Alert, Platform, View } from 'react-native'
1919
import { useSelector } from 'react-redux'
2020
import LedgerService from 'services/ledger/LedgerService'
21-
import { LedgerKeysByNetwork } from 'services/ledger/types'
21+
import {
22+
LedgerDerivationPathType,
23+
LedgerKeysByNetwork
24+
} from 'services/ledger/types'
2225
import { selectIsSolanaSupportBlocked } from 'store/posthog'
2326
import { selectIsDeveloperMode } from 'store/settings/advanced'
2427
import Logger from 'utils/Logger'
2528

2629
export default function AppConnectionScreen({
30+
selectedDerivationPath = LedgerDerivationPathType.BIP44,
2731
completeStepTitle,
2832
isUpdatingWallet,
2933
handleComplete,
@@ -32,6 +36,7 @@ export default function AppConnectionScreen({
3236
disconnectDevice,
3337
accountIndex
3438
}: {
39+
selectedDerivationPath?: LedgerDerivationPathType
3540
completeStepTitle: string
3641
isUpdatingWallet: boolean
3742
deviceId?: string | null
@@ -149,11 +154,13 @@ export default function AppConnectionScreen({
149154
// Get keys from service
150155
const avalancheKeys = await LedgerService.getAvalancheKeys(
151156
accountIndex,
152-
isDeveloperMode
157+
isDeveloperMode,
158+
selectedDerivationPath
153159
)
154160
const oppositeAvalancheKeys = await LedgerService.getAvalancheKeys(
155161
accountIndex,
156-
!isDeveloperMode
162+
!isDeveloperMode,
163+
selectedDerivationPath
157164
)
158165

159166
// Update local state
@@ -189,7 +196,13 @@ export default function AppConnectionScreen({
189196
[{ text: 'OK' }]
190197
)
191198
}
192-
}, [accountIndex, deviceId, isDeveloperMode, isSolanaSupportBlocked])
199+
}, [
200+
accountIndex,
201+
deviceId,
202+
isDeveloperMode,
203+
isSolanaSupportBlocked,
204+
selectedDerivationPath
205+
])
193206

194207
const handleConnectSolana = useCallback(async () => {
195208
try {

packages/core-mobile/app/new/features/ledger/utils/index.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { LedgerAppType, LedgerDerivationPathType } from 'services/ledger/types'
44
import { OnDelegationProgress } from 'contexts/DelegationContext'
55
import { z } from 'zod'
66
import { RpcMethod } from '@avalabs/vm-module-types'
7+
import { Curve } from 'utils/publicKeys'
78
import { ledgerParamsStore, StakingProgressParams } from '../store'
89

910
export const showLedgerReviewTransaction = ({
@@ -53,22 +54,42 @@ export const getLedgerAppName = (network?: Network): LedgerAppType => {
5354
: LedgerAppType.UNKNOWN
5455
}
5556

57+
const BtcWalletPolicySchema = z.object({
58+
hmacHex: z.string(),
59+
masterFingerprint: z.string(),
60+
xpub: z.string(),
61+
name: z.string()
62+
})
63+
64+
const PublicKeyInfoSchema = z.object({
65+
key: z.string(),
66+
derivationPath: z.string(),
67+
curve: z.enum([Curve.SECP256K1, Curve.ED25519]),
68+
btcWalletPolicy: BtcWalletPolicySchema.optional()
69+
})
70+
5671
export const LedgerWalletSecretSchema = z.looseObject({
5772
deviceId: z.string(),
5873
deviceName: z.string(),
59-
derivationPathSpec: z.nativeEnum(LedgerDerivationPathType),
60-
extendedPublicKeys: z.record(
61-
z.string(),
62-
z.object({
63-
evm: z.string().optional(),
64-
avalanche: z.string().optional()
65-
})
66-
),
67-
publicKeys: z.array(
68-
z.object({
69-
key: z.string(),
70-
derivationPath: z.string(),
71-
curve: z.string()
72-
})
73-
)
74+
derivationPathSpec: z.enum([
75+
LedgerDerivationPathType.BIP44,
76+
LedgerDerivationPathType.LedgerLive
77+
]),
78+
extendedPublicKeys: z
79+
.record(
80+
z.string(),
81+
z.object({
82+
evm: z.string().optional(),
83+
avalanche: z.string().optional()
84+
})
85+
)
86+
.optional(),
87+
publicKeys: z
88+
.record(z.string(), z.array(PublicKeyInfoSchema))
89+
.transform(
90+
record =>
91+
Object.fromEntries(
92+
Object.entries(record).map(([k, v]) => [Number(k), v])
93+
) as Record<number, z.infer<typeof PublicKeyInfoSchema>[]>
94+
)
7495
})

0 commit comments

Comments
 (0)