Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 5 additions & 21 deletions app/controllers/perps/PerpsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ import {
LastTransactionResult,
TransactionStatus,
} from './types/transactionTypes';
import { getSelectedEvmAccount } from './utils/accountUtils';
import { getSelectedEvmAccountFromMessenger } from './utils/accountUtils';
import { ensureError } from './utils/errorUtils';
import {
hydrateFromDiskSync,
Expand Down Expand Up @@ -1155,11 +1155,7 @@ export class PerpsController extends BaseController<
// Get current user address for validation
let currentAddress: string | null = null;
try {
const evmAccount = getSelectedEvmAccount(
this.messenger.call(
'AccountTreeController:getAccountsFromSelectedAccountGroup',
),
);
const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
currentAddress = evmAccount?.address ?? null;
} catch {
// Can't determine current account — trust the cache
Expand Down Expand Up @@ -2215,11 +2211,7 @@ export class PerpsController extends BaseController<
currentDepositId = depositId;

// Get current account address via messenger (outside of update() for proper typing)
const evmAccount = getSelectedEvmAccount(
this.messenger.call(
'AccountTreeController:getAccountsFromSelectedAccountGroup',
),
);
const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
const accountAddress = evmAccount?.address ?? 'unknown';

this.update((state) => {
Expand Down Expand Up @@ -3090,11 +3082,7 @@ export class PerpsController extends BaseController<

// Watch for account changes via AccountTreeController
const accountChangeHandler = (): void => {
const evmAccount = getSelectedEvmAccount(
this.messenger.call(
'AccountTreeController:getAccountsFromSelectedAccountGroup',
),
);
const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
const currentAddress = evmAccount?.address ?? null;

// If any cached entry belongs to a different account, clear all entries.
Expand Down Expand Up @@ -3332,11 +3320,7 @@ export class PerpsController extends BaseController<
}

// Get current user address
const evmAccount = getSelectedEvmAccount(
this.messenger.call(
'AccountTreeController:getAccountsFromSelectedAccountGroup',
),
);
const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
if (!evmAccount?.address) {
return;
}
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/perps/types/messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
AccountTreeControllerGetAccountsFromSelectedAccountGroupAction,
AccountTreeControllerSelectedAccountGroupChangeEvent,
} from '@metamask/account-tree-controller';
import type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';
import type { GeolocationControllerGetGeolocationAction } from '@metamask/geolocation-controller';
import type {
KeyringControllerGetStateAction,
Expand Down Expand Up @@ -32,6 +33,7 @@ export type PerpsControllerAllowedActions =
| KeyringControllerSignTypedMessageAction
| TransactionControllerAddTransactionAction
| RemoteFeatureFlagControllerGetStateAction
| AccountsControllerGetSelectedAccountAction
| AccountTreeControllerGetAccountsFromSelectedAccountGroupAction
| AuthenticationController.AuthenticationControllerGetBearerTokenAction;

Expand Down
98 changes: 98 additions & 0 deletions app/controllers/perps/utils/accountUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,111 @@
import type { InternalAccount } from '@metamask/keyring-internal-api';

import { PERPS_CONSTANTS } from '../constants/perpsConfig';
import type { PerpsControllerMessenger } from '../PerpsController';
import type { AccountState } from '../types';

import {
addSpotBalanceToAccountState,
aggregateAccountStates,
calculateWeightedReturnOnEquity,
getSelectedEvmAccountFromMessenger,
getSpotBalance,
} from './accountUtils';

const SELECTED_ADDRESS = '0x1111111111111111111111111111111111111111';
const GROUP_ADDRESS = '0x2222222222222222222222222222222222222222';
const NON_EVM_ADDRESS = 'bc1qselectedaccount';

function buildAccount(
address: string,
id: string,
type: InternalAccount['type'] = 'eip155:eoa',
): InternalAccount {
return {
address,
id,
type,
options: {},
methods: [],
metadata: {
name: id,
importTime: Date.now(),
keyring: {
type: 'HD Key Tree',
},
},
scopes: ['eip155:0'],
} as InternalAccount;
}

function buildMessenger(
call: (actionType: string) => InternalAccount | InternalAccount[],
): Pick<PerpsControllerMessenger, 'call'> {
return { call } as unknown as Pick<PerpsControllerMessenger, 'call'>;
}

describe('getSelectedEvmAccountFromMessenger', () => {
it('prefers the selected account over the first evm account in the selected group', () => {
const selectedAccount = buildAccount(SELECTED_ADDRESS, 'selected');
const groupedAccount = buildAccount(GROUP_ADDRESS, 'grouped');
const messenger = buildMessenger((actionType: string) => {
switch (actionType) {
case 'AccountsController:getSelectedAccount':
return selectedAccount;
case 'AccountTreeController:getAccountsFromSelectedAccountGroup':
return [groupedAccount];
default:
throw new Error(`Unexpected action: ${actionType}`);
}
});

expect(getSelectedEvmAccountFromMessenger(messenger)).toStrictEqual({
address: SELECTED_ADDRESS,
});
});

it('falls back to the selected account group when selected account lookup is unavailable', () => {
const groupedAccount = buildAccount(GROUP_ADDRESS, 'grouped');
const messenger = buildMessenger((actionType: string) => {
switch (actionType) {
case 'AccountsController:getSelectedAccount':
throw new Error('Selected account unavailable');
case 'AccountTreeController:getAccountsFromSelectedAccountGroup':
return [groupedAccount];
default:
throw new Error(`Unexpected action: ${actionType}`);
}
});

expect(getSelectedEvmAccountFromMessenger(messenger)).toStrictEqual({
address: GROUP_ADDRESS,
});
});

it('falls back to the selected account group when the selected account is not evm', () => {
const selectedAccount = buildAccount(
NON_EVM_ADDRESS,
'selected',
'bip122:p2wpkh',
);
const groupedAccount = buildAccount(GROUP_ADDRESS, 'grouped');
const messenger = buildMessenger((actionType: string) => {
switch (actionType) {
case 'AccountsController:getSelectedAccount':
return selectedAccount;
case 'AccountTreeController:getAccountsFromSelectedAccountGroup':
return [groupedAccount];
default:
throw new Error(`Unexpected action: ${actionType}`);
}
});

expect(getSelectedEvmAccountFromMessenger(messenger)).toStrictEqual({
address: GROUP_ADDRESS,
});
});
});

describe('aggregateAccountStates', () => {
const fallback: AccountState = {
spendableBalance: PERPS_CONSTANTS.FallbackDataDisplay,
Expand Down
27 changes: 27 additions & 0 deletions app/controllers/perps/utils/accountUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { InternalAccount } from '@metamask/keyring-internal-api';

import { PERPS_CONSTANTS } from '../constants/perpsConfig';
import type { PerpsControllerMessenger } from '../PerpsController';
import type { AccountState, PerpsInternalAccount } from '../types';
import type { SpotClearinghouseStateResponse } from '../types/hyperliquid-types';

Expand Down Expand Up @@ -37,6 +38,32 @@ export function getSelectedEvmAccount(
return getEvmAccountFromAccountGroup(accounts);
}

export function getSelectedEvmAccountFromMessenger(
messenger: Pick<PerpsControllerMessenger, 'call'>,
): { address: string } | undefined {
try {
const selectedAccount = messenger.call(
'AccountsController:getSelectedAccount',
);
const evmAccount = findEvmAccount([selectedAccount]);
if (evmAccount) {
return { address: evmAccount.address };
}
} catch {
// Fall back to the selected account group if the direct lookup is unavailable.
}

try {
return getSelectedEvmAccount(
messenger.call(
'AccountTreeController:getAccountsFromSelectedAccountGroup',
),
);
} catch {
return undefined;
}
}

export type ReturnOnEquityInput = {
unrealizedPnl: string | number;
returnOnEquity: string | number;
Expand Down
Loading