Skip to content

Commit 7634337

Browse files
CP-13385 - Fix keystone account duplication (#3584)
1 parent 5f459ab commit 7634337

File tree

2 files changed

+68
-12
lines changed

2 files changed

+68
-12
lines changed

packages/core-mobile/app/hooks/useXPAddresses/useXPAddresses.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import { transformXPAddresses } from './transformXPAddresses'
1313

1414
const STALE_TIME = 60 * 1000 // 1 minute
1515

16+
const EMPTY_XP_ADDRESSES = {
17+
xpAddresses: [] as string[],
18+
xpAddressDictionary: {} as XPAddressDictionary
19+
}
20+
1621
const getQueryKey = ({
1722
walletId,
1823
walletType,
@@ -53,6 +58,14 @@ export const useXPAddresses = (
5358

5459
const shouldDisable = !wallet || !account
5560

61+
// Keystone SDK currently only exposes a single XP xpub (account index 0)
62+
// for all accounts. Until their SDK supports per-account xpubs, non-primary
63+
// accounts must return empty to avoid duplicate XP balances caused by the
64+
// addressPVM fallback in transformXPAddresses.
65+
// TODO: Remove this workaround once the Keystone SDK is fixed.
66+
const isKeystoneNonPrimary =
67+
walletType === WalletType.KEYSTONE && accountIndex > 0
68+
5669
const queryResult = useQuery({
5770
staleTime: STALE_TIME,
5871
queryKey: getQueryKey({
@@ -62,17 +75,18 @@ export const useXPAddresses = (
6275
accountId,
6376
isDeveloperMode
6477
}),
65-
queryFn: shouldDisable
66-
? skipToken
67-
: () => {
68-
return getAddressesFromXpubXP({
69-
isDeveloperMode,
70-
walletId,
71-
walletType: walletType as WalletType,
72-
accountIndex,
73-
onlyWithActivity: true
74-
})
75-
}
78+
queryFn:
79+
shouldDisable || isKeystoneNonPrimary
80+
? skipToken
81+
: () => {
82+
return getAddressesFromXpubXP({
83+
isDeveloperMode,
84+
walletId,
85+
walletType: walletType as WalletType,
86+
accountIndex,
87+
onlyWithActivity: true
88+
})
89+
}
7690
})
7791

7892
const transformed = useMemo(
@@ -81,7 +95,7 @@ export const useXPAddresses = (
8195
)
8296

8397
return {
84-
...transformed,
98+
...(isKeystoneNonPrimary ? EMPTY_XP_ADDRESSES : transformed),
8599
isLoading: queryResult.isLoading
86100
}
87101
}
@@ -100,6 +114,11 @@ export async function getCachedXPAddresses({
100114
xpAddresses: string[]
101115
xpAddressDictionary: XPAddressDictionary
102116
}> {
117+
// TODO: Remove this workaround once the Keystone SDK supports per-account XP xpubs.
118+
if (walletType === WalletType.KEYSTONE && account.index > 0) {
119+
return EMPTY_XP_ADDRESSES
120+
}
121+
103122
try {
104123
const result = await queryClient.fetchQuery({
105124
staleTime: STALE_TIME,

packages/core-mobile/app/utils/getAddressesFromXpubXP/getAddressesFromXpubXP.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,41 @@ describe('getAddressesFromXpubXP', () => {
137137
{ address: 'avax1aaa', index: 1 }
138138
])
139139
})
140+
141+
describe('Keystone wallet XP address ownership', () => {
142+
const keystoneArgs = {
143+
isDeveloperMode: false,
144+
walletId: 'keystone-wallet-1',
145+
walletType: WalletType.KEYSTONE,
146+
accountIndex: 0,
147+
onlyWithActivity: true
148+
}
149+
150+
it('returns all addresses for Keystone primary account (index 0)', async () => {
151+
const avm = makeResponse(
152+
'AVM',
153+
['X-avax1aaa', 'X-avax1bbb', 'X-avax1ccc'],
154+
[]
155+
)
156+
const pvm = makeResponse(
157+
'PVM',
158+
['P-avax1aaa', 'P-avax1bbb', 'P-avax1ccc'],
159+
[]
160+
)
161+
162+
mockWalletService.getAddressesFromXpubXP.mockImplementation(
163+
async ({ networkType }) =>
164+
networkType === NetworkVMType.AVM ? avm : pvm
165+
)
166+
167+
const result = await getAddressesFromXpubXP(keystoneArgs)
168+
169+
// Account 0 should see ALL addresses under the shared xpub
170+
expect(result.xpAddresses).toEqual([
171+
{ address: 'avax1aaa', index: 0 },
172+
{ address: 'avax1bbb', index: 1 },
173+
{ address: 'avax1ccc', index: 2 }
174+
])
175+
})
176+
})
140177
})

0 commit comments

Comments
 (0)