Skip to content

Commit 1f0bf52

Browse files
authored
fix: sub account caching policy only cache known address (#167)
1 parent e2673f1 commit 1f0bf52

File tree

2 files changed

+185
-12
lines changed

2 files changed

+185
-12
lines changed

packages/account-sdk/src/sign/base-account/Signer.test.ts

Lines changed: 160 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,9 @@ describe('Signer', () => {
10261026
expect(accounts).toEqual([subAccountAddress, globalAccountAddress]);
10271027

10281028
// Test with eth_requestAccounts as well
1029-
const requestedAccounts = await signer.request({ method: 'eth_requestAccounts' });
1029+
const requestedAccounts = await signer.request({
1030+
method: 'eth_requestAccounts',
1031+
});
10301032
expect(requestedAccounts).toEqual([subAccountAddress, globalAccountAddress]);
10311033
});
10321034
});
@@ -1262,9 +1264,157 @@ describe('Signer', () => {
12621264
expect(accounts).toEqual([subAccountAddress, globalAccountAddress]);
12631265

12641266
// eth_requestAccounts will also order based on defaultAccount
1265-
const requestedAccounts = await signer.request({ method: 'eth_requestAccounts' });
1267+
const requestedAccounts = await signer.request({
1268+
method: 'eth_requestAccounts',
1269+
});
12661270
expect(requestedAccounts).toEqual([subAccountAddress, globalAccountAddress]);
12671271
});
1272+
1273+
it('should return cached sub account when requested address matches', async () => {
1274+
await signer.cleanup();
1275+
1276+
// Setup initial connection
1277+
(decryptContent as Mock).mockResolvedValueOnce({
1278+
result: { value: null },
1279+
});
1280+
await signer.handshake({ method: 'handshake' });
1281+
1282+
(decryptContent as Mock).mockResolvedValueOnce({
1283+
result: {
1284+
value: {
1285+
accounts: [{ address: globalAccountAddress, capabilities: {} }],
1286+
},
1287+
},
1288+
});
1289+
await signer.request({ method: 'wallet_connect', params: [] });
1290+
1291+
// Cache a sub account
1292+
store.subAccounts.set({
1293+
address: subAccountAddress,
1294+
factory: globalAccountAddress,
1295+
factoryData: '0x',
1296+
});
1297+
1298+
// Request same address (isAddressEqual handles case-insensitive comparison)
1299+
const result = await signer.request({
1300+
method: 'wallet_addSubAccount',
1301+
params: [
1302+
{
1303+
version: '1',
1304+
account: {
1305+
type: 'deployed',
1306+
address: subAccountAddress,
1307+
chainId: '0x14a34',
1308+
},
1309+
},
1310+
],
1311+
});
1312+
1313+
// Should return cached without calling backend
1314+
expect(result.address).toBe(subAccountAddress);
1315+
expect(decryptContent).toHaveBeenCalledTimes(2); // Only handshake + connect, not addSubAccount
1316+
});
1317+
1318+
it('should fetch from backend when requested address differs from cached', async () => {
1319+
await signer.cleanup();
1320+
1321+
const secondSubAccountAddress = '0x9999999999999999999999999999999999999999';
1322+
1323+
// Setup initial connection
1324+
(decryptContent as Mock).mockResolvedValueOnce({
1325+
result: { value: null },
1326+
});
1327+
await signer.handshake({ method: 'handshake' });
1328+
1329+
(decryptContent as Mock).mockResolvedValueOnce({
1330+
result: {
1331+
value: {
1332+
accounts: [{ address: globalAccountAddress, capabilities: {} }],
1333+
},
1334+
},
1335+
});
1336+
await signer.request({ method: 'wallet_connect', params: [] });
1337+
1338+
// Cache a sub account
1339+
store.subAccounts.set({
1340+
address: subAccountAddress,
1341+
factory: globalAccountAddress,
1342+
factoryData: '0x',
1343+
});
1344+
1345+
// Request different address
1346+
(decryptContent as Mock).mockResolvedValueOnce({
1347+
result: {
1348+
value: {
1349+
address: secondSubAccountAddress,
1350+
factory: globalAccountAddress,
1351+
factoryData: '0x123',
1352+
},
1353+
},
1354+
});
1355+
1356+
const result = await signer.request({
1357+
method: 'wallet_addSubAccount',
1358+
params: [
1359+
{
1360+
version: '1',
1361+
account: {
1362+
type: 'deployed',
1363+
address: secondSubAccountAddress,
1364+
chainId: '0x14a34',
1365+
},
1366+
},
1367+
],
1368+
});
1369+
1370+
// Should call backend and return new sub account
1371+
expect(result.address).toBe(secondSubAccountAddress);
1372+
expect(decryptContent).toHaveBeenCalledTimes(3); // handshake + connect + addSubAccount
1373+
});
1374+
1375+
it('should return cached sub account for create type (no address specified)', async () => {
1376+
await signer.cleanup();
1377+
1378+
// Setup initial connection
1379+
(decryptContent as Mock).mockResolvedValueOnce({
1380+
result: { value: null },
1381+
});
1382+
await signer.handshake({ method: 'handshake' });
1383+
1384+
(decryptContent as Mock).mockResolvedValueOnce({
1385+
result: {
1386+
value: {
1387+
accounts: [{ address: globalAccountAddress, capabilities: {} }],
1388+
},
1389+
},
1390+
});
1391+
await signer.request({ method: 'wallet_connect', params: [] });
1392+
1393+
// Cache a sub account
1394+
store.subAccounts.set({
1395+
address: subAccountAddress,
1396+
factory: globalAccountAddress,
1397+
factoryData: '0x',
1398+
});
1399+
1400+
// Request create type (no specific address)
1401+
const result = await signer.request({
1402+
method: 'wallet_addSubAccount',
1403+
params: [
1404+
{
1405+
version: '1',
1406+
account: {
1407+
type: 'create',
1408+
keys: [{ publicKey: '0x123', type: 'p256' }],
1409+
},
1410+
},
1411+
],
1412+
});
1413+
1414+
// Should return cached without calling backend
1415+
expect(result.address).toBe(subAccountAddress);
1416+
expect(decryptContent).toHaveBeenCalledTimes(2); // Only handshake + connect, not addSubAccount
1417+
});
12681418
});
12691419

12701420
describe('auto sub account', () => {
@@ -1856,7 +2006,10 @@ describe('Signer', () => {
18562006
});
18572007

18582008
// Set the chain to match the mocked client
1859-
signer['chain'] = { id: 84532, rpcUrl: 'https://eth-rpc.example.com/84532' };
2009+
signer['chain'] = {
2010+
id: 84532,
2011+
rpcUrl: 'https://eth-rpc.example.com/84532',
2012+
};
18602013
signer['accounts'] = [globalAccountAddress];
18612014

18622015
// Setup basic handshake
@@ -2144,7 +2297,10 @@ describe('Signer', () => {
21442297
});
21452298

21462299
// Set the chain to match the mocked client
2147-
signer['chain'] = { id: 84532, rpcUrl: 'https://eth-rpc.example.com/84532' };
2300+
signer['chain'] = {
2301+
id: 84532,
2302+
rpcUrl: 'https://eth-rpc.example.com/84532',
2303+
};
21482304
signer['accounts'] = [globalAccountAddress];
21492305

21502306
// Mock decryptContent for wallet_connect to set up accounts

packages/account-sdk/src/sign/base-account/Signer.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -603,15 +603,32 @@ export class Signer {
603603
factoryData?: Hex;
604604
}> {
605605
const state = store.getState();
606-
const subAccount = state.subAccount;
606+
const cachedSubAccount = state.subAccount;
607607
const subAccountsConfig = store.subAccountsConfig.get();
608-
if (subAccount?.address) {
609-
this.accounts =
610-
subAccountsConfig?.defaultAccount === 'sub'
611-
? prependWithoutDuplicates(this.accounts, subAccount.address)
612-
: appendWithoutDuplicates(this.accounts, subAccount.address);
613-
this.callback?.('accountsChanged', this.accounts);
614-
return subAccount;
608+
609+
// Extract requested address from params (for deployed/undeployed types)
610+
const requestedAddress =
611+
Array.isArray(request.params) &&
612+
request.params.length > 0 &&
613+
request.params[0]?.account?.address
614+
? request.params[0].account.address
615+
: undefined;
616+
617+
// Only return cached if:
618+
// 1. Cache exists AND
619+
// 2. No specific address requested (create type) OR requested address matches cached
620+
if (cachedSubAccount?.address) {
621+
const shouldUseCache =
622+
!requestedAddress || isAddressEqual(requestedAddress, cachedSubAccount.address);
623+
624+
if (shouldUseCache) {
625+
this.accounts =
626+
subAccountsConfig?.defaultAccount === 'sub'
627+
? prependWithoutDuplicates(this.accounts, cachedSubAccount.address)
628+
: appendWithoutDuplicates(this.accounts, cachedSubAccount.address);
629+
this.callback?.('accountsChanged', this.accounts);
630+
return cachedSubAccount;
631+
}
615632
}
616633

617634
// Wait for the popup to be loaded before sending the request

0 commit comments

Comments
 (0)