Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: adds Solana support for the account overview #28411

Open
wants to merge 28 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
73c498f
chore: adds SOL support
zone-live Nov 11, 2024
dd6fcbf
chore: prettier run
zone-live Nov 12, 2024
8a51986
chore: clean up
zone-live Nov 12, 2024
a56008b
chore: adds comment and btc check for now
zone-live Nov 12, 2024
f503046
chore: updates packages version to support Solana
zone-live Nov 12, 2024
3684963
Merge branch 'develop' into SOL-2-view-solana-account-overview
zone-live Nov 12, 2024
3cbd887
Update LavaMoat policies
metamaskbot Nov 12, 2024
6248ba5
chore: update enum value
zone-live Nov 12, 2024
97c7198
chore: undo
zone-live Nov 12, 2024
b0dd1c6
chore: improvements
zone-live Nov 12, 2024
e40f75c
chore: prettier
zone-live Nov 12, 2024
1a81480
chore: undo change
zone-live Nov 12, 2024
d7da72a
chore: test update for balancesController
zone-live Nov 12, 2024
7743068
chore: prettier
zone-live Nov 12, 2024
7daa2ac
chore: import order, lint
zone-live Nov 12, 2024
e6e8485
chore: test update
zone-live Nov 12, 2024
9142284
chore: test update
zone-live Nov 13, 2024
9d4e4f2
Merge branch 'develop' into SOL-2-view-solana-account-overview
zone-live Nov 13, 2024
3a16d1c
chore: updates the metamask/assets-controllers package
zone-live Nov 13, 2024
22d1eba
Update LavaMoat policies
metamaskbot Nov 13, 2024
1cf494e
Merge branch 'develop' into SOL-2-view-solana-account-overview
zone-live Nov 14, 2024
6c8e19e
chore: re-apply the patch for assets-controllers
zone-live Nov 14, 2024
9babd20
Update LavaMoat policies
metamaskbot Nov 14, 2024
cc81cda
chore: update e2e snapshots data
zone-live Nov 14, 2024
4fedc02
Merge branch 'SOL-2-view-solana-account-overview' of github.com:MetaM…
zone-live Nov 14, 2024
9f1189b
chore: update e2e
zone-live Nov 14, 2024
ce125fa
chore: comment flaky endpoint
zone-live Nov 14, 2024
547c71d
Merge branch 'develop' into SOL-2-view-solana-account-overview
zone-live Nov 14, 2024
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
58 changes: 42 additions & 16 deletions app/scripts/lib/accounts/BalancesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type CaipAssetType,
type InternalAccount,
isEvmAccountType,
SolAccountType,
} from '@metamask/keyring-api';
import type { HandleSnapRequest } from '@metamask/snaps-controllers';
import type { SnapId } from '@metamask/snaps-sdk';
Expand All @@ -23,7 +24,10 @@ import type {
AccountsControllerAccountRemovedEvent,
AccountsControllerListMultichainAccountsAction,
} from '@metamask/accounts-controller';
import { isBtcMainnetAddress } from '../../../../shared/lib/multichain';
import {
isBtcMainnetAddress,
isSolanaAddress,
} from '../../../../shared/lib/multichain';
import { BalancesTracker } from './BalancesTracker';

const controllerName = 'BalancesController';
Expand Down Expand Up @@ -126,9 +130,15 @@ const BTC_TESTNET_ASSETS = ['bip122:000000000933ea01ad0ee984209779ba/slip44:0'];
const BTC_MAINNET_ASSETS = ['bip122:000000000019d6689c085ae165831e93/slip44:0'];
const BTC_AVG_BLOCK_TIME = 10 * 60 * 1000; // 10 minutes in milliseconds

const SOLANA_ASSETS = ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'];
const SOLANA_DEVNET_ASSETS = [
'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1/slip44:501',
];
const SOLANA_AVG_BLOCK_TIME = 400; // 400 milliseconds

// NOTE: We set an interval of half the average block time to mitigate when our interval
// is de-synchronized with the actual block time.
export const BALANCES_UPDATE_TIME = BTC_AVG_BLOCK_TIME / 2;
export const BTC_BALANCES_UPDATE_TIME = BTC_AVG_BLOCK_TIME / 2;

/**
* The BalancesController is responsible for fetching and caching account
Expand Down Expand Up @@ -165,7 +175,11 @@ export class BalancesController extends BaseController<
// Register all non-EVM accounts into the tracker
for (const account of this.#listAccounts()) {
if (this.#isNonEvmAccount(account)) {
this.#tracker.track(account.id, BALANCES_UPDATE_TIME);
const updateTime =
account.type === BtcAccountType.P2wpkh
? BTC_BALANCES_UPDATE_TIME
: SOLANA_AVG_BLOCK_TIME;
this.#tracker.track(account.id, updateTime);
}
}

Expand Down Expand Up @@ -207,15 +221,16 @@ export class BalancesController extends BaseController<
/**
* Lists the accounts that we should get balances for.
*
* Currently, we only get balances for P2WPKH accounts, but this will change
* in the future when we start support other non-EVM account types.
*
* @returns A list of accounts that we should get balances for.
*/
#listAccounts(): InternalAccount[] {
const accounts = this.#listMultichainAccounts();

return accounts.filter((account) => account.type === BtcAccountType.P2wpkh);
return accounts.filter(
(account) =>
account.type === SolAccountType.DataAccount ||
account.type === BtcAccountType.P2wpkh,
);
}

/**
Expand Down Expand Up @@ -249,13 +264,21 @@ export class BalancesController extends BaseController<
const partialState: BalancesControllerState = { balances: {} };

Copy link
Contributor Author

@zone-live zone-live Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method, updateBalance, will be updated once we have the network picker. The scope will adapt based on the network selected.

if (account.metadata.snap) {
partialState.balances[account.id] = await this.#getBalances(
account.id,
account.metadata.snap.id,
isBtcMainnetAddress(account.address)
? BTC_MAINNET_ASSETS
: BTC_TESTNET_ASSETS,
);
// In here we need to check which assets to query
let assetTypes: CaipAssetType[];
if (isSolanaAddress(account.address)) {
assetTypes = SOLANA_ASSETS;
} else if (isBtcMainnetAddress(account.address)) {
assetTypes = BTC_MAINNET_ASSETS;
} else {
// If not mainnet, we need to check if it's testnet or devnet
assetTypes =
account.type === BtcAccountType.P2wpkh
? BTC_TESTNET_ASSETS
: SOLANA_DEVNET_ASSETS;
}

await this.#getBalances(account.id, account.metadata.snap.id, assetTypes);
}

this.update((state: Draft<BalancesControllerState>) => ({
Expand Down Expand Up @@ -312,8 +335,11 @@ export class BalancesController extends BaseController<
return;
}

this.#tracker.track(account.id, BTC_AVG_BLOCK_TIME);
// NOTE: Unfortunately, we cannot update the balance right away here, because
const updateTime =
account.type === BtcAccountType.P2wpkh
? BTC_AVG_BLOCK_TIME
: SOLANA_AVG_BLOCK_TIME;
this.#tracker.track(account.id, updateTime); // NOTE: Unfortunately, we cannot update the balance right away here, because
// messenger's events are running synchronously and fetching the balance is
// asynchronous.
// Updating the balance here would resume at some point but the event emitter
Expand Down
1 change: 1 addition & 0 deletions app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { KeyringRpcMethod } from '@metamask/keyring-api';
* The origins of the Portfolio dapp.
*/
const PORTFOLIO_ORIGINS: string[] = [
'http://localhost:3000',
'https://portfolio.metamask.io',
///: BEGIN:ONLY_INCLUDE_IF(build-flask)
'https://dev.portfolio.metamask.io',
Expand Down
8 changes: 7 additions & 1 deletion shared/constants/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ export const CURRENCY_SYMBOLS = {
AVALANCHE: 'AVAX',
BNB: 'BNB',
BUSD: 'BUSD',
BTC: 'BTC', // Do we wanna mix EVM and non-EVM here?
CELO: 'CELO',
DAI: 'DAI',
GNOSIS: 'XDAI',
Expand All @@ -320,8 +319,15 @@ export const CURRENCY_SYMBOLS = {
ONE: 'ONE',
} as const;

// Non-EVM currency symbols
export const NON_EVM_CURRENCY_SYMBOLS = {
BTC: 'BTC',
SOL: 'SOL',
} as const;

const CHAINLIST_CURRENCY_SYMBOLS_MAP = {
...CURRENCY_SYMBOLS,
...NON_EVM_CURRENCY_SYMBOLS,
BASE: 'ETH',
LINEA_MAINNET: 'ETH',
OPBNB: 'BNB',
Expand Down
2 changes: 1 addition & 1 deletion ui/components/app/wallet-overview/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as EthOverview } from './eth-overview';
export { default as BtcOverview } from './btc-overview';
export { default as NonEvmOverview } from './non-evm-overview';
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import BtcOverview from './btc-overview';
import NonEvmOverview from './non-evm-overview';

export default {
title: 'Components/App/WalletOverview/BtcOverview',
component: BtcOverview,
component: NonEvmOverview,
parameters: {
docs: {
description: {
Expand All @@ -14,6 +14,6 @@ export default {
},
};

const Template = (args) => <BtcOverview {...args} />;
const Template = (args) => <NonEvmOverview {...args} />;

export const Default = Template.bind({});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { MultichainNetworks } from '../../../../shared/constants/multichain/netw
import { RampsMetaMaskEntry } from '../../../hooks/ramps/useRamps/useRamps';
import { defaultBuyableChains } from '../../../ducks/ramps/constants';
import { setBackgroundConnection } from '../../../store/background-connection';
import BtcOverview from './btc-overview';
import NonEvmOverview from './non-evm-overview';

const PORTOFOLIO_URL = 'https://portfolio.test';

Expand Down Expand Up @@ -113,7 +113,7 @@ describe('BtcOverview', () => {
});

it('shows the primary balance as BTC when showNativeTokenAsMainBalance if true', async () => {
const { queryByTestId } = renderWithProvider(<BtcOverview />, getStore());
const { queryByTestId } = renderWithProvider(<NonEvmOverview />, getStore());

const primaryBalance = queryByTestId(BTC_OVERVIEW_PRIMARY_CURRENCY);
expect(primaryBalance).toBeInTheDocument();
Expand All @@ -122,7 +122,7 @@ describe('BtcOverview', () => {

it('shows the primary balance as fiat when showNativeTokenAsMainBalance if false', async () => {
const { queryByTestId } = renderWithProvider(
<BtcOverview />,
<NonEvmOverview />,
getStore({
metamask: {
...mockMetamaskStore,
Expand All @@ -141,7 +141,7 @@ describe('BtcOverview', () => {

it('shows a spinner if balance is not available', async () => {
const { container } = renderWithProvider(
<BtcOverview />,
<NonEvmOverview />,
getStore({
metamask: {
...mockMetamaskStore,
Expand All @@ -158,7 +158,7 @@ describe('BtcOverview', () => {
});

it('buttons Swap/Bridge are disabled', () => {
const { queryByTestId } = renderWithProvider(<BtcOverview />, getStore());
const { queryByTestId } = renderWithProvider(<NonEvmOverview />, getStore());

for (const buttonTestId of [BTC_OVERVIEW_SWAP, BTC_OVERVIEW_BRIDGE]) {
const button = queryByTestId(buttonTestId);
Expand All @@ -168,13 +168,13 @@ describe('BtcOverview', () => {
});

it('shows the "Buy & Sell" button', () => {
const { queryByTestId } = renderWithProvider(<BtcOverview />, getStore());
const { queryByTestId } = renderWithProvider(<NonEvmOverview />, getStore());
const buyButton = queryByTestId(BTC_OVERVIEW_BUY);
expect(buyButton).toBeInTheDocument();
});

it('"Buy & Sell" button is disabled if BTC is not buyable', () => {
const { queryByTestId } = renderWithProvider(<BtcOverview />, getStore());
const { queryByTestId } = renderWithProvider(<NonEvmOverview />, getStore());
const buyButton = queryByTestId(BTC_OVERVIEW_BUY);

expect(buyButton).toBeInTheDocument();
Expand All @@ -189,7 +189,7 @@ describe('BtcOverview', () => {
});

const { queryByTestId } = renderWithProvider(
<BtcOverview />,
<NonEvmOverview />,
storeWithBtcBuyable,
);

Expand All @@ -207,7 +207,7 @@ describe('BtcOverview', () => {
});

const { queryByTestId } = renderWithProvider(
<BtcOverview />,
<NonEvmOverview />,
storeWithBtcBuyable,
);

Expand All @@ -229,7 +229,7 @@ describe('BtcOverview', () => {
});

it('always show the Receive button', () => {
const { queryByTestId } = renderWithProvider(<BtcOverview />, getStore());
const { queryByTestId } = renderWithProvider(<NonEvmOverview />, getStore());
const receiveButton = queryByTestId(BTC_OVERVIEW_RECEIVE);
expect(receiveButton).toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { useMultichainSelector } from '../../../hooks/useMultichainSelector';
///: END:ONLY_INCLUDE_IF
import { CoinOverview } from './coin-overview';

type BtcOverviewProps = {
type NonEvmOverviewProps = {
className?: string;
};

const BtcOverview = ({ className }: BtcOverviewProps) => {
const NonEvmOverview = ({ className }: NonEvmOverviewProps) => {
const { chainId } = useSelector(getMultichainProviderConfig);
const balance = useSelector(getMultichainSelectedAccountCachedBalance);
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
Expand Down Expand Up @@ -47,4 +47,4 @@ const BtcOverview = ({ className }: BtcOverviewProps) => {
);
};

export default BtcOverview;
export default NonEvmOverview;
31 changes: 31 additions & 0 deletions ui/components/app/wallet-overview/sol-overview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { useSelector } from 'react-redux';
import {
getMultichainProviderConfig,
getMultichainSelectedAccountCachedBalance,
} from '../../../selectors/multichain';
import { CoinOverview } from './coin-overview';

type SolOverviewProps = {
className?: string;
};

const SolOverview = ({ className }: SolOverviewProps) => {
const { chainId } = useSelector(getMultichainProviderConfig);
const balance = useSelector(getMultichainSelectedAccountCachedBalance);

return (
<CoinOverview
balance={balance}
balanceIsCached={false}
className={className}
chainId={chainId}
isSigningEnabled={true}
isSwapsChain={false}
isBridgeChain={false}
isBuyableChain={false}
/>
);
};

export default SolOverview;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
import {
BtcAccountType,
EthAccountType,
SolAccountType,
///: BEGIN:ONLY_INCLUDE_IF(build-flask)
InternalAccount,
KeyringAccountType,
Expand Down Expand Up @@ -218,6 +219,7 @@ export const AccountListMenu = ({
EthAccountType.Eoa,
EthAccountType.Erc4337,
BtcAccountType.P2wpkh,
SolAccountType.DataAccount,
],
}: AccountListMenuProps) => {
const t = useI18nContext();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { AccountOverviewNonEvm } from './account-overview-non-evm';
import { AccountOverviewCommonProps } from './common';
import { BtcAccountType, SolAccountType } from '@metamask/keyring-api';

export default {
title: 'Components/Multichain/AccountOverviewNonEvm',
component: AccountOverviewNonEvm,
args: {
accountType: BtcAccountType.P2wpkh,
},
};

export const DefaultStory = (
args: JSX.IntrinsicAttributes &
AccountOverviewCommonProps & {
accountType: BtcAccountType.P2wpkh | SolAccountType.DataAccount;
},
) => <AccountOverviewNonEvm {...args} />;
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import configureStore from '../../../store/store';
import { renderWithProvider } from '../../../../test/jest/rendering';
import { setBackgroundConnection } from '../../../store/background-connection';
import {
AccountOverviewBtc,
AccountOverviewBtcProps,
} from './account-overview-btc';
AccountOverviewNonEvm,
AccountOverviewNonEvmProps,
} from './account-overview-non-evm';

const defaultProps: AccountOverviewBtcProps = {
const defaultProps: AccountOverviewNonEvmProps = {
defaultHomeActiveTabName: '',
onTabClick: jest.fn(),
setBasicFunctionalityModalOpen: jest.fn(),
onSupportLinkClick: jest.fn(),
};

const render = (props: AccountOverviewBtcProps = defaultProps) => {
const render = (props: AccountOverviewNonEvmProps = defaultProps) => {
const store = configureStore({
metamask: mockState.metamask,
});

return renderWithProvider(<AccountOverviewBtc {...props} />, store);
return renderWithProvider(<AccountOverviewNonEvm {...props} />, store);
};

describe('AccountOverviewBtc', () => {
Expand Down
Loading
Loading