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

feat: add and use Accounts API for Account balance calls #4781

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7cee69b
feat: add multichain accounts API services
Prithpal-Sooriya Oct 10, 2024
6e4171a
docs: scaffold and comment sections of TokenDetectionController
Prithpal-Sooriya Oct 10, 2024
b52827e
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1179/add-…
Prithpal-Sooriya Oct 11, 2024
d201250
feat: add accounts API support networks endpoint
Prithpal-Sooriya Oct 11, 2024
816ca78
refactor: rename fetchSupportedNetworks function
Prithpal-Sooriya Oct 11, 2024
d4c37ef
feat: add token detection via API method and connect to detection con…
Prithpal-Sooriya Oct 11, 2024
9f5724d
refactor: add barrel file
Prithpal-Sooriya Oct 11, 2024
9adebed
test: add test coverage for tokenDetectionController
Prithpal-Sooriya Oct 11, 2024
4e08cba
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1179/add-…
Prithpal-Sooriya Oct 14, 2024
0911070
docs: update test util jsdoc comment
Prithpal-Sooriya Oct 14, 2024
8664211
docs: remove todo/note comments
Prithpal-Sooriya Oct 14, 2024
228515c
test: add ignore comments for untestable statements
Prithpal-Sooriya Oct 14, 2024
200beb7
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1179/add-…
Prithpal-Sooriya Oct 15, 2024
4a464ab
Merge branch 'main' into NOTIFY-1179/add-account-api-to-token-detecti…
Prithpal-Sooriya Oct 15, 2024
87c4155
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1179/add-…
Prithpal-Sooriya Oct 16, 2024
cc53352
refactor: use existing enums and constants
Prithpal-Sooriya Oct 16, 2024
7157e10
Merge branch 'NOTIFY-1179/add-account-api-to-token-detection-controll…
Prithpal-Sooriya Oct 16, 2024
9bd0f70
Merge branch 'main' into NOTIFY-1179/add-account-api-to-token-detecti…
Prithpal-Sooriya Oct 16, 2024
56e242c
Merge branch 'main' into NOTIFY-1179/add-account-api-to-token-detecti…
Prithpal-Sooriya Oct 17, 2024
909fde8
Merge branch 'main' into NOTIFY-1179/add-account-api-to-token-detecti…
Prithpal-Sooriya Oct 18, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { GetBalancesResponse } from '../types';

export const MOCK_GET_BALANCES_RESPONSE: GetBalancesResponse = {
Prithpal-Sooriya marked this conversation as resolved.
Show resolved Hide resolved
count: 6,
balances: [
{
object: 'token',
address: '0x0000000000000000000000000000000000000000',
symbol: 'ETH',
name: 'Ether',
type: 'native',
timestamp: '2015-07-30T03:26:13.000Z',
decimals: 18,
chainId: 1,
balance: '0.026380882267770930',
},
{
object: 'token',
address: '0x4200000000000000000000000000000000000042',
name: 'Optimism',
symbol: 'OP',
decimals: 18,
balance: '5.250000000000000000',
chainId: 10,
},
{
object: 'token',
address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
name: 'USD Coin (PoS)',
symbol: 'USDC',
decimals: 6,
balance: '22.484688',
chainId: 137,
},
{
object: 'token',
address: '0x0000000000000000000000000000000000000000',
symbol: 'MATIC',
name: 'MATIC',
type: 'native',
timestamp: '2020-05-30T07:47:16.000Z',
decimals: 18,
chainId: 137,
balance: '2.873547261071381088',
},
{
object: 'token',
address: '0x912ce59144191c1204e64559fe8253a0e49e6548',
name: 'Arbitrum',
symbol: 'ARB',
decimals: 18,
balance: '14.640000000000000000',
chainId: 42161,
},
{
object: 'token',
address: '0xd83af4fbd77f3ab65c3b1dc4b38d7e67aecf599a',
name: 'Linea Voyage XP',
symbol: 'LXP',
decimals: 18,
balance: '100.000000000000000000',
chainId: 59144,
},
],
unprocessedNetworks: [],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import nock from 'nock';

import { MOCK_GET_BALANCES_RESPONSE } from './mocks/mock-get-balances';
import { fetchMultiChainBalances } from './multi-chain-accounts';

const MOCK_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';

describe('fetchMultiChainBalances()', () => {
const createMockAPI = () =>
nock('https://accounts.api.cx.metamask.io').get(
`/v2/accounts/${MOCK_ADDRESS}/balances`,
);

it('should successfully return balances response', async () => {
const mockAPI = createMockAPI().reply(200, MOCK_GET_BALANCES_RESPONSE);

const result = await fetchMultiChainBalances(MOCK_ADDRESS);
expect(result).toBeDefined();
expect(result).toStrictEqual(MOCK_GET_BALANCES_RESPONSE);
expect(mockAPI.isDone()).toBe(true);
});

it('should successfully return balances response with query params to refine search', async () => {
const mockAPI = createMockAPI()
.query({
networks: '1,10',
filterSupportedTokens: 'true',
includeTokenAddresses: 'abc',
includeStakedAssets: 'false',
})
.reply(200, MOCK_GET_BALANCES_RESPONSE);

const result = await fetchMultiChainBalances(MOCK_ADDRESS, {
networks: '1,10',
filterSupportedTokens: true,
includeTokenAddresses: 'abc',
includeStakedAssets: false,
});
expect(result).toBeDefined();
expect(result).toStrictEqual(MOCK_GET_BALANCES_RESPONSE);
expect(mockAPI.isDone()).toBe(true);
});

const testMatrix = [
{ httpCode: 429, httpCodeName: 'Too Many Requests' }, // E.g. Rate Limit
{ httpCode: 422, httpCodeName: 'Unprocessable Content' }, // E.g. fails to fetch any balances from specified chains
{ httpCode: 500, httpCodeName: 'Internal Server Error' }, // E.g. Server Rekt
];

it.each(testMatrix)(
'should throw when $httpCode "$httpCodeName"',
async ({ httpCode }) => {
const mockAPI = createMockAPI().reply(httpCode);

await expect(
async () => await fetchMultiChainBalances(MOCK_ADDRESS),
).rejects.toThrow(expect.any(Error));
expect(mockAPI.isDone()).toBe(true);
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { handleFetch } from '@metamask/controller-utils';
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice move with the separate client. Also good to attempt OpenAPI generation but we can live without it for now.


import type { GetBalancesQueryParams, GetBalancesResponse } from './types';

const MULTICHAIN_ACCOUNTS_DOMAIN = 'https://accounts.api.cx.metamask.io';

const getBalancesUrl = (address: string) =>
`${MULTICHAIN_ACCOUNTS_DOMAIN}/v2/accounts/${address}/balances`;

/**
* Fetches Balances for multiple networks.
* @param address - address to fetch balances from
* @param options - params to pass down for a more refined search
* @returns a Balances Response
*/
export async function fetchMultiChainBalances(
address: string,
options?: GetBalancesQueryParams,
Prithpal-Sooriya marked this conversation as resolved.
Show resolved Hide resolved
) {
const url = new URL(getBalancesUrl(address));
if (options?.networks !== undefined) {
url.searchParams.append('networks', options.networks);
}
if (options?.filterSupportedTokens !== undefined) {
url.searchParams.append(
'filterSupportedTokens',
String(options.filterSupportedTokens),
);
}
if (options?.includeTokenAddresses !== undefined) {
url.searchParams.append(
'includeTokenAddresses',
options.includeTokenAddresses,
);
}
if (options?.includeStakedAssets !== undefined) {
url.searchParams.append(
'includeStakedAssets',
String(options.includeStakedAssets),
);
}

// TODO - swap handleFetch with raw fetch
// We may want to handle 429 (Too Many Requests) Rate Limit separately
const response: GetBalancesResponse = await handleFetch(url);
return response;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export type GetBalancesQueryParams = {
Prithpal-Sooriya marked this conversation as resolved.
Show resolved Hide resolved
/** @description Comma-separated network/chain IDs */
networks?: string;
/** @description Whether or not to filter the assets to contain only the tokens existing in the Token API */
filterSupportedTokens?: boolean;
/** @description Specific token addresses to fetch balances for across specified network(s) */
includeTokenAddresses?: string;
/** @description Whether to include balances of the account's staked asset balances */
includeStakedAssets?: boolean;
};

export type GetBalancesResponse = {
count: number;
balances: {
object: string;
address: string;
symbol: string;
name: string;
type?: string;
timestamp?: string;
decimals: number;
chainId: number;
balance: string;
Prithpal-Sooriya marked this conversation as resolved.
Show resolved Hide resolved
}[];
/** @description networks that failed to process, if no network is processed, returns HTTP 422 */
unprocessedNetworks: number[];
};
Loading