Skip to content

Commit 804dfda

Browse files
fix: Tron addresses not being generated for all BIP44 accounts
1 parent 7ac6585 commit 804dfda

File tree

2 files changed

+53
-78
lines changed

2 files changed

+53
-78
lines changed

packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,51 +33,40 @@ class MockTronKeyring {
3333
this.accounts = accounts;
3434
}
3535

36-
#getIndexFromDerivationPath(derivationPath: string): number {
37-
// eslint-disable-next-line prefer-regex-literals
38-
const derivationPathIndexRegex = new RegExp(
39-
"^m/44'/195'/0'/(?<index>[0-9]+)'$",
40-
'u',
41-
);
42-
43-
const matched = derivationPath.match(derivationPathIndexRegex);
44-
if (matched?.groups?.index === undefined) {
45-
throw new Error('Unable to extract index');
46-
}
47-
48-
const { index } = matched.groups;
49-
return Number(index);
50-
}
51-
5236
createAccount: SnapKeyring['createAccount'] = jest
5337
.fn()
54-
.mockImplementation((_, { derivationPath }) => {
55-
if (derivationPath !== undefined) {
56-
const index = this.#getIndexFromDerivationPath(derivationPath);
57-
const found = this.accounts.find(
58-
(account) =>
59-
isBip44Account(account) &&
60-
account.options.entropy.groupIndex === index,
61-
);
62-
63-
if (found) {
64-
return found; // Idempotent.
65-
}
38+
.mockImplementation((_, { index, ...options }) => {
39+
// Use the provided index or fallback to accounts length
40+
const groupIndex = index !== undefined ? index : this.accounts.length;
41+
42+
// Check if an account already exists for this group index (idempotent behavior)
43+
const found = this.accounts.find(
44+
(account) =>
45+
isBip44Account(account) &&
46+
account.options.entropy.groupIndex === groupIndex,
47+
);
48+
49+
if (found) {
50+
return found; // Idempotent.
6651
}
6752

53+
// Create new account with the correct group index
6854
const account = MockAccountBuilder.from(MOCK_TRX_ACCOUNT_1)
6955
.withUuid()
7056
.withAddressSuffix(`${this.accounts.length}`)
71-
.withGroupIndex(this.accounts.length)
57+
.withGroupIndex(groupIndex)
7258
.get();
7359
this.accounts.push(account);
7460

7561
return account;
7662
});
63+
64+
// Add discoverAccounts method to match the provider's usage
65+
discoverAccounts = jest.fn().mockResolvedValue([]);
7766
}
7867

7968
/**
80-
* Sets up a SolAccountProvider for testing.
69+
* Sets up a TrxAccountProvider for testing.
8170
*
8271
* @param options - Configuration options for setup.
8372
* @param options.messenger - An optional messenger instance to use. Defaults to a new Messenger.
@@ -98,6 +87,7 @@ function setup({
9887
handleRequest: jest.Mock;
9988
keyring: {
10089
createAccount: jest.Mock;
90+
discoverAccounts: jest.Mock;
10191
};
10292
};
10393
} {
@@ -108,11 +98,17 @@ function setup({
10898
() => accounts,
10999
);
110100

111-
const mockHandleRequest = jest
112-
.fn()
113-
.mockImplementation((address: string) =>
114-
keyring.accounts.find((account) => account.address === address),
101+
const mockHandleRequest = jest.fn().mockImplementation((request) => {
102+
// Handle KeyringClient discoverAccounts calls
103+
if (request.request?.method === 'keyring_discoverAccounts') {
104+
// Return the keyring's discoverAccounts result directly
105+
return keyring.discoverAccounts();
106+
}
107+
// Handle other requests (fallback for legacy compatibility)
108+
return keyring.accounts.find(
109+
(account) => account.address === request.address,
115110
);
111+
});
116112
messenger.registerActionHandler(
117113
'SnapController:handleRequest',
118114
mockHandleRequest,
@@ -143,6 +139,7 @@ function setup({
143139
handleRequest: mockHandleRequest,
144140
keyring: {
145141
createAccount: keyring.createAccount as jest.Mock,
142+
discoverAccounts: keyring.discoverAccounts as jest.Mock,
146143
},
147144
},
148145
};
@@ -235,7 +232,7 @@ describe('TrxAccountProvider', () => {
235232
});
236233

237234
// Skip this test for now, since we manually inject those options upon
238-
// account creation, so it cannot fails (until the Solana Snap starts
235+
// account creation, so it cannot fails (until the Tron Snap starts
239236
// using the new typed options).
240237
// eslint-disable-next-line jest/no-disabled-tests
241238
it.skip('throws if the created account is not BIP-44 compatible', async () => {
@@ -263,7 +260,9 @@ describe('TrxAccountProvider', () => {
263260
});
264261

265262
// Simulate one discovered account at the requested index.
266-
mocks.handleRequest.mockReturnValue([MOCK_TRX_DISCOVERED_ACCOUNT_1]);
263+
mocks.keyring.discoverAccounts.mockResolvedValue([
264+
MOCK_TRX_DISCOVERED_ACCOUNT_1,
265+
]);
267266

268267
const discovered = await provider.discoverAccounts({
269268
entropySource: MOCK_HD_KEYRING_1.metadata.id,
@@ -283,7 +282,9 @@ describe('TrxAccountProvider', () => {
283282
});
284283

285284
// Simulate one discovered account — should resolve to the existing one
286-
mocks.handleRequest.mockReturnValue([MOCK_TRX_DISCOVERED_ACCOUNT_1]);
285+
mocks.keyring.discoverAccounts.mockResolvedValue([
286+
MOCK_TRX_DISCOVERED_ACCOUNT_1,
287+
]);
287288

288289
const discovered = await provider.discoverAccounts({
289290
entropySource: MOCK_HD_KEYRING_1.metadata.id,
@@ -298,7 +299,7 @@ describe('TrxAccountProvider', () => {
298299
accounts: [],
299300
});
300301

301-
mocks.handleRequest.mockReturnValue([]);
302+
mocks.keyring.discoverAccounts.mockResolvedValue([]);
302303

303304
const discovered = await provider.discoverAccounts({
304305
entropySource: MOCK_HD_KEYRING_1.metadata.id,

packages/multichain-account-service/src/providers/TrxAccountProvider.ts

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -83,47 +83,26 @@ export class TrxAccountProvider extends SnapAccountProvider {
8383
);
8484
}
8585

86-
async #createAccount({
86+
async createAccounts({
8787
entropySource,
88-
groupIndex,
89-
derivationPath,
88+
groupIndex: index,
9089
}: {
9190
entropySource: EntropySourceId;
9291
groupIndex: number;
93-
derivationPath: string;
94-
}): Promise<Bip44Account<KeyringAccount>> {
92+
}): Promise<Bip44Account<KeyringAccount>[]> {
9593
const createAccount = await this.getRestrictedSnapAccountCreator();
94+
9695
const account = await withTimeout(
97-
createAccount({ entropySource, derivationPath }),
96+
createAccount({
97+
entropySource,
98+
index,
99+
addressType: TrxAccountType.Eoa,
100+
scope: TrxScope.Mainnet,
101+
}),
98102
this.#config.createAccounts.timeoutMs,
99103
);
100104

101-
// Ensure entropy is present before type assertion validation
102-
account.options.entropy = {
103-
type: KeyringAccountEntropyTypeOption.Mnemonic,
104-
id: entropySource,
105-
groupIndex,
106-
derivationPath,
107-
};
108-
109105
assertIsBip44Account(account);
110-
return account;
111-
}
112-
113-
async createAccounts({
114-
entropySource,
115-
groupIndex,
116-
}: {
117-
entropySource: EntropySourceId;
118-
groupIndex: number;
119-
}): Promise<Bip44Account<KeyringAccount>[]> {
120-
const derivationPath = `m/44'/195'/0'/${groupIndex}'`;
121-
const account = await this.#createAccount({
122-
entropySource,
123-
groupIndex,
124-
derivationPath,
125-
});
126-
127106
return [account];
128107
}
129108

@@ -154,15 +133,10 @@ export class TrxAccountProvider extends SnapAccountProvider {
154133
return [];
155134
}
156135

157-
const createdAccounts = await Promise.all(
158-
discoveredAccounts.map((d) =>
159-
this.#createAccount({
160-
entropySource,
161-
groupIndex,
162-
derivationPath: d.derivationPath,
163-
}),
164-
),
165-
);
136+
const createdAccounts = await this.createAccounts({
137+
entropySource,
138+
groupIndex,
139+
});
166140

167141
return createdAccounts;
168142
}

0 commit comments

Comments
 (0)