Skip to content

Commit 5be330c

Browse files
committed
Use accountApiClient within AccountController to fetch token metadata and balance
- Introduce 'getTokenBalanceAndMetadata' to account controllers - Add placeholder icons for missing network/token SVGs - Replace hardcoded ETH token details in UI with dynamic values
1 parent a697591 commit 5be330c

14 files changed

Lines changed: 181 additions & 96 deletions

File tree

packages/gator-permissions-snap/images/networks/not-found.svg

Lines changed: 10 additions & 0 deletions
Loading

packages/gator-permissions-snap/images/tokens/not-found.svg

Lines changed: 10 additions & 0 deletions
Loading

packages/gator-permissions-snap/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snap-7715-permissions.git"
88
},
99
"source": {
10-
"shasum": "B/45l1lyLyqYX2GkYxvKAmCCWOCSu030Do+IEMdQV+c=",
10+
"shasum": "8RuzV/zrJlK0VNzSDBoQQ8t1ecofnqNn8WL7MwNKfAE=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/gator-permissions-snap/src/accountController/baseAccountController.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import { logger } from '@metamask/7715-permissions-shared/utils';
22
import { CHAIN_ID as ChainsWithDelegatorDeployed } from '@metamask/delegation-toolkit';
33
import type { SnapsProvider } from '@metamask/snaps-sdk';
44
import * as chains from 'viem/chains';
5+
import type { Address } from 'viem';
6+
7+
import type {
8+
AccountApiClient,
9+
TokenBalanceAndMetadata,
10+
} from '../clients/accountApiClient';
11+
import type {
12+
AccountOptionsBase,
13+
GetTokenBalanceAndMetadataOptions,
14+
} from './types';
515

616
export type SupportedChains =
717
(typeof chains)[keyof typeof ChainsWithDelegatorDeployed &
@@ -14,7 +24,9 @@ type SupportedChainId = SupportedChains[number]['id'];
1424
* Base class for account controllers that provides common functionality.
1525
*/
1626
export abstract class BaseAccountController {
17-
#snapsProvider: SnapsProvider;
27+
readonly #snapsProvider: SnapsProvider;
28+
29+
readonly #accountApiClient: AccountApiClient;
1830

1931
protected supportedChains: SupportedChains;
2032

@@ -32,17 +44,20 @@ export abstract class BaseAccountController {
3244
* @param config - The configuration object.
3345
* @param config.snapsProvider - The provider for interacting with snaps.
3446
* @param config.supportedChains - The supported blockchain chains.
47+
* @param config.accountApiClient - The client for interacting with the account API.
3548
*/
3649
constructor(config: {
3750
snapsProvider: SnapsProvider;
3851
supportedChains?: SupportedChains;
52+
accountApiClient: AccountApiClient;
3953
}) {
4054
// only validate if supportedChains is specified, as it will default to ALL_SUPPORTED_CHAINS
4155
if (config.supportedChains) {
4256
this.#validateSupportedChains(config.supportedChains);
4357
}
4458

4559
this.#snapsProvider = config.snapsProvider;
60+
this.#accountApiClient = config.accountApiClient;
4661
this.supportedChains =
4762
config.supportedChains ?? BaseAccountController.#allSupportedChains;
4863
}
@@ -147,4 +162,45 @@ export abstract class BaseAccountController {
147162
protected get snapsProvider(): SnapsProvider {
148163
return this.#snapsProvider;
149164
}
165+
166+
/**
167+
* Gets the account address. Must be implemented by derived classes.
168+
*
169+
* @param options - The base account options including chainId.
170+
* @returns A promise resolving to the account address.
171+
*/
172+
protected abstract getAccountAddress(
173+
options: AccountOptionsBase,
174+
): Promise<Address>;
175+
176+
/**
177+
* Retrieves the token balance and metadata for the account.
178+
*
179+
* @param options - The options for fetching the token balance and metadata.
180+
* @returns A promise resolving to the token balance and metadata.
181+
*/
182+
public async getTokenBalanceAndMetadata(
183+
options: GetTokenBalanceAndMetadataOptions,
184+
): Promise<TokenBalanceAndMetadata> {
185+
logger.debug('accountController:getTokenBalanceAndMetadata()');
186+
187+
const { chainId, assetAddress } = options;
188+
189+
this.assertIsSupportedChainId(chainId);
190+
191+
const account = await this.getAccountAddress({ chainId });
192+
193+
const balanceAndMetadata =
194+
await this.#accountApiClient.getTokenBalanceAndMetadata({
195+
chainId,
196+
account,
197+
assetAddress,
198+
});
199+
200+
logger.debug(
201+
'accountController:getTokenBalanceAndMetadata() - balance and metadata resolved',
202+
);
203+
204+
return balanceAndMetadata;
205+
}
150206
}

packages/gator-permissions-snap/src/accountController/eoaAccountController.ts

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import type { SnapsProvider } from '@metamask/snaps-sdk';
88
import { type Address, type Hex } from 'viem';
99

10+
import { AccountApiClient } from '../clients/accountApiClient';
1011
import type { SupportedChains } from './baseAccountController';
1112
import { BaseAccountController } from './baseAccountController';
1213
import type {
@@ -37,11 +38,13 @@ export class EoaAccountController
3738
* @param config.snapsProvider - The provider for interacting with snaps.
3839
* @param config.ethereumProvider - The provider for interacting with Ethereum.
3940
* @param config.supportedChains - Optional list of supported blockchain chains.
41+
* @param config.accountApiClient - The client for interacting with the account API.
4042
*/
4143
constructor(config: {
4244
snapsProvider: SnapsProvider;
4345
ethereumProvider: EthereumProvider;
4446
supportedChains?: SupportedChains;
47+
accountApiClient: AccountApiClient;
4548
}) {
4649
super(config);
4750
this.#ethereumProvider = config.ethereumProvider;
@@ -198,30 +201,6 @@ export class EoaAccountController
198201
};
199202
}
200203

201-
/**
202-
* Retrieves the balance of the EOA account.
203-
* @param options - The options object containing chain information.
204-
* @returns The account balance in hex format.
205-
*/
206-
public async getAccountBalance(options: AccountOptionsBase): Promise<Hex> {
207-
logger.debug('eoaAccountController:getAccountBalance()');
208-
209-
this.assertIsSupportedChainId(options.chainId);
210-
211-
const address = await this.#getAccountAddress();
212-
213-
const provider = this.createExperimentalProviderRequestProvider(
214-
options.chainId,
215-
);
216-
217-
const balance = await provider.request({
218-
method: 'eth_getBalance',
219-
params: [address, 'latest'],
220-
});
221-
222-
return balance as Hex;
223-
}
224-
225204
/**
226205
* Retrieves the delegation manager address.
227206
* @param options - The options object containing chain information.

packages/gator-permissions-snap/src/accountController/smartAccountController.ts

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from 'viem';
1717
import { privateKeyToAccount } from 'viem/accounts';
1818

19+
import { AccountApiClient } from '../clients/accountApiClient';
1920
import type { SupportedChains } from './baseAccountController';
2021
import { BaseAccountController } from './baseAccountController';
2122
import type {
@@ -53,11 +54,13 @@ export class SmartAccountController
5354
* @param config.snapsProvider - The provider for interacting with snaps.
5455
* @param config.supportedChains - The supported blockchain chains.
5556
* @param config.deploymentSalt - The hex salt for smart account deployment.
57+
* @param config.accountApiClient - The client for interacting with the account API.
5658
*/
5759
constructor(config: {
5860
snapsProvider: SnapsProvider;
5961
supportedChains?: SupportedChains;
6062
deploymentSalt: Hex;
63+
accountApiClient: AccountApiClient;
6164
}) {
6265
super(config);
6366
this.#deploymentSalt = config.deploymentSalt;
@@ -207,40 +210,24 @@ export class SmartAccountController
207210
}
208211

209212
/**
210-
* Retrieves the balance of the smart account.
213+
* Retrieves the environment for the current account.
211214
*
212215
* @param options - The base account options including chainId.
213-
* @returns A promise resolving to the account balance as a hex string.
216+
* @returns A promise resolving to a DeleGatorEnvironment.
214217
*/
215-
public async getAccountBalance(options: AccountOptionsBase): Promise<Hex> {
216-
logger.debug('accountController:getAccountBalance()');
217-
218-
const { chainId } = options;
218+
public async getEnvironment(
219+
options: AccountOptionsBase,
220+
): Promise<DeleGatorEnvironment> {
221+
logger.debug('accountController:getEnvironment()');
219222

220223
const smartAccount = await this.#getMetaMaskSmartAccount(options);
221224

222225
logger.debug(
223-
'accountController:getAccountBalance() - smartAccount resolved',
226+
'accountController:getEnvironment() - smartAccount resolved',
224227
smartAccount,
225228
);
226229

227-
this.assertIsSupportedChainId(chainId);
228-
229-
const provider = this.createExperimentalProviderRequestProvider(chainId);
230-
231-
const accountAddress = await smartAccount.getAddress();
232-
233-
const balance = await provider.request({
234-
method: 'eth_getBalance',
235-
params: [accountAddress, 'latest'],
236-
});
237-
238-
logger.debug(
239-
'accountController:getAccountBalance() - balance resolved',
240-
balance,
241-
);
242-
243-
return balance as Hex;
230+
return smartAccount.environment;
244231
}
245232

246233
/**
@@ -272,25 +259,4 @@ export class SmartAccountController
272259

273260
return { ...delegation, signature };
274261
}
275-
276-
/**
277-
* Retrieves the environment for the current account.
278-
*
279-
* @param options - The base account options including chainId.
280-
* @returns A promise resolving to a DeleGatorEnvironment.
281-
*/
282-
public async getEnvironment(
283-
options: AccountOptionsBase,
284-
): Promise<DeleGatorEnvironment> {
285-
logger.debug('accountController:getEnvironment()');
286-
287-
const smartAccount = await this.#getMetaMaskSmartAccount(options);
288-
289-
logger.debug(
290-
'accountController:getEnvironment() - smartAccount resolved',
291-
smartAccount,
292-
);
293-
294-
return smartAccount.environment;
295-
}
296262
}

packages/gator-permissions-snap/src/accountController/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
DeleGatorEnvironment,
44
} from '@metamask/delegation-toolkit';
55
import type { Address, Hex } from 'viem';
6+
import type { TokenBalanceAndMetadata } from '../clients/accountApiClient';
67

78
/**
89
* Base options required for account operations.
@@ -12,6 +13,10 @@ export type AccountOptionsBase = {
1213
chainId: number;
1314
};
1415

16+
export type GetTokenBalanceAndMetadataOptions = AccountOptionsBase & {
17+
assetAddress?: Address;
18+
};
19+
1520
/**
1621
* Options for signing a delegation.
1722
*/
@@ -49,7 +54,9 @@ export type AccountController = {
4954
/**
5055
* Retrieves the balance of the smart account.
5156
*/
52-
getAccountBalance(options: AccountOptionsBase): Promise<Hex>;
57+
getTokenBalanceAndMetadata(
58+
options: GetTokenBalanceAndMetadataOptions,
59+
): Promise<TokenBalanceAndMetadata>;
5360

5461
/**
5562
* Retrieves the delegation manager address for the current account.

packages/gator-permissions-snap/src/clients/accountApiClient.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { logger } from '@metamask/7715-permissions-shared/utils';
2+
import { IconUrls } from '../ui/iconConstant';
23
import { isAddressEqual, zeroAddress, type Address } from 'viem';
34

45
/**
@@ -67,18 +68,21 @@ export class AccountApiClient {
6768
account,
6869
}: {
6970
chainId: number;
70-
assetAddress?: Address;
7171
account: Address;
72+
assetAddress?: Address | undefined;
7273
}): Promise<TokenBalanceAndMetadata> {
74+
console.log('getTokenBalanceAndMetadata', chainId, assetAddress, account);
7375
if (!chainId) {
7476
const message = 'No chainId provided to fetch token balance';
77+
console.log(message);
7578
logger.error(message);
7679

7780
throw new Error(message);
7881
}
7982

8083
if (!account) {
8184
const message = 'No account address provided to fetch token balance';
85+
console.log(message);
8286
logger.error(message);
8387

8488
throw new Error(message);
@@ -87,8 +91,12 @@ export class AccountApiClient {
8791
// zeroAddress is the native token on the specified chain
8892
const tokenAddress = assetAddress ?? zeroAddress;
8993

94+
console.log(
95+
`${this.#baseUrl}/tokens/${tokenAddress}?accountAddresses=${account}&chainId=${1}`,
96+
);
97+
// todo: chainId is hardcoded to mainnet, because the account api does not support sepolia.
9098
const response = await this.#fetch(
91-
`${this.#baseUrl}/tokens/${tokenAddress}?accountAddresses=${account}&chainId=${chainId}`,
99+
`${this.#baseUrl}/tokens/${tokenAddress}?accountAddresses=${account}&chainId=${1}`,
92100
);
93101

94102
if (!response.ok) {
@@ -120,11 +128,21 @@ export class AccountApiClient {
120128
throw new Error(message);
121129
}
122130

131+
// this is an awkward workaround. We cannot actually use the iconUrl from
132+
// the response, because we must have an SVG literal. The service returns a
133+
// png, and we cannot even fetch it due to CORs if it were svg.
134+
const iconUrl = ICON_URLS[balanceData.iconUrl] ?? IconUrls.notFound.token;
135+
123136
return {
124137
balance: BigInt(accountData.rawBalance),
125138
decimals: balanceData.decimals,
126139
symbol: balanceData.symbol,
127-
iconUrl: balanceData.iconUrl,
140+
iconUrl,
128141
};
129142
}
130143
}
144+
145+
const ICON_URLS: Record<string, string> = {
146+
'https://dev-static.cx.metamask.io/api/v1/tokenIcons/1/0x0000000000000000000000000000000000000000.png':
147+
IconUrls.ethereum.token,
148+
};

packages/gator-permissions-snap/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { RpcMethod } from './rpc/rpcMethod';
3939
import { TokenPricesService } from './services/tokenPricesService';
4040
import { createStateManager } from './stateManagement';
4141
import { UserEventDispatcher } from './userEventDispatcher';
42+
import { AccountApiClient } from './clients/accountApiClient';
4243

4344
const isFeatureEnabled = process.env.STORE_PERMISSIONS_ENABLED === 'true';
4445
const snapEnv = process.env.SNAP_ENV;
@@ -48,16 +49,23 @@ const snapEnv = process.env.SNAP_ENV;
4849
// eslint-disable-next-line no-restricted-globals
4950
const useEoaAccountController = process.env.USE_EOA_ACCOUNT === 'true';
5051

52+
// todo: set up the configuration properly
53+
const accountApiClient = new AccountApiClient(
54+
'https://account.api.cx.metamask.io',
55+
);
56+
5157
const accountController: AccountController = useEoaAccountController
5258
? new EoaAccountController({
5359
snapsProvider: snap,
5460
supportedChains: [sepolia, lineaSepolia],
5561
ethereumProvider: ethereum,
62+
accountApiClient,
5663
})
5764
: new SmartAccountController({
5865
snapsProvider: snap,
5966
supportedChains: [sepolia, lineaSepolia],
6067
deploymentSalt: '0x',
68+
accountApiClient,
6169
});
6270

6371
const stateManager = createStateManager(snap);

0 commit comments

Comments
 (0)