Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

Commit

Permalink
refactor: split api.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
danroc committed May 28, 2024
1 parent 6f925d6 commit c72985f
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 9 deletions.
19 changes: 19 additions & 0 deletions src/api/balance.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expectAssignable, expectNotAssignable } from 'tsd';

import type { Balance } from './balance';

expectAssignable<Balance>({ amount: '1.0', unit: 'ETH' });
expectAssignable<Balance>({ amount: '0.1', unit: 'BTC' });
expectAssignable<Balance>({ amount: '.1', unit: 'gwei' });
expectAssignable<Balance>({ amount: '.1', unit: 'wei' });
expectAssignable<Balance>({ amount: '1.', unit: 'sat' });

expectNotAssignable<Balance>({ amount: 1, unit: 'ETH' });
expectNotAssignable<Balance>({ amount: true, unit: 'ETH' });
expectNotAssignable<Balance>({ amount: undefined, unit: 'ETH' });
expectNotAssignable<Balance>({ amount: null, unit: 'ETH' });

expectNotAssignable<Balance>({ amount: '1.0', unit: 1 });
expectNotAssignable<Balance>({ amount: '1.0', unit: true });
expectNotAssignable<Balance>({ amount: '1.0', unit: undefined });
expectNotAssignable<Balance>({ amount: '1.0', unit: null });
11 changes: 11 additions & 0 deletions src/api/balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Infer } from 'superstruct';
import { string } from 'superstruct';

import { object } from '../superstruct';

export const BalanceStruct = object({
amount: string(),
unit: string(),
});

export type Balance = Infer<typeof BalanceStruct>;
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './account';
export * from './balance';
export * from './export';
export * from './keyring';
export * from './request';
Expand Down
33 changes: 33 additions & 0 deletions src/api/keyring.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Json } from '@metamask/utils';

import type { KeyringAccount } from './account';
import type { Balance } from './balance';
import type { KeyringAccountData } from './export';
import type { KeyringRequest } from './request';
import type { KeyringResponse } from './response';
Expand Down Expand Up @@ -44,6 +45,38 @@ export type Keyring = {
*/
createAccount(options?: Record<string, Json>): Promise<KeyringAccount>;

/**
* Retrieve the balances of a given account.
*
* This method fetches the balances of specified assets for a given account ID.
* It returns a promise that resolves to an object where the keys are asset IDs
* and the values are balance objects containing the amount and unit.
*
* @example
* ```ts
* await keyring.getAccountBalances(
* '43550276-c7d6-4fac-87c7-00390ad0ce90',
* ['bip122:000000000019d6689c085ae165831e93/slip44:0']
* );
* // Returns something similar to:
* // {
* // 'bip122:000000000019d6689c085ae165831e93/slip44:0': {
* // amount: '0.0001',
* // unit: 'BTC',
* // }
* // }
* ```
* @param id - The ID of the account to retrieve the balances for.
* @param assets - An array of asset IDs (in CAIP-19 format) for which to
* retrieve balances.
* @returns A promise that resolves to an object mapping asset IDs to their
* respective balances.
*/
getAccountBalances?(
id: string,
assets: string[],
): Promise<Record<string, Balance>>;

/**
* Filter supported chains for a given account.
*
Expand Down
38 changes: 29 additions & 9 deletions src/internal/api.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { JsonStruct } from '@metamask/utils';
import type { Infer } from 'superstruct';
import {
array,
literal,
number,
object,
record,
string,
union,
} from 'superstruct';
import { array, literal, number, record, string, union } from 'superstruct';

import {
BalanceStruct,
KeyringAccountDataStruct,
KeyringAccountStruct,
KeyringRequestStruct,
KeyringResponseStruct,
} from '../api';
import { object } from '../superstruct';
import { UuidStruct } from '../utils';
import { KeyringRpcMethod } from './rpc';

const CommonHeader = {
jsonrpc: literal('2.0'),
Expand Down Expand Up @@ -71,6 +66,31 @@ export const CreateAccountResponseStruct = KeyringAccountStruct;

export type CreateAccountResponse = Infer<typeof CreateAccountResponseStruct>;

// ----------------------------------------------------------------------------
// Get account balances

export const GetAccountBalancesRequestStruct = object({
...CommonHeader,
method: literal(`${KeyringRpcMethod.GetAccountBalances}`),
params: object({
id: UuidStruct,
assets: array(string()),
}),
});

export type GetAccountBalancesRequest = Infer<
typeof GetAccountBalancesRequestStruct
>;

export const GetAccountBalancesResponseStruct = record(
string(),
record(string(), BalanceStruct),
);

export type GetAccountBalancesResponse = Infer<
typeof GetAccountBalancesResponseStruct
>;

// ----------------------------------------------------------------------------
// Filter account chains

Expand Down
1 change: 1 addition & 0 deletions src/internal/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum KeyringRpcMethod {
ListAccounts = 'keyring_listAccounts',
GetAccount = 'keyring_getAccount',
CreateAccount = 'keyring_createAccount',
GetAccountBalances = 'keyring_getAccountBalances',
FilterAccountChains = 'keyring_filterAccountChains',
UpdateAccount = 'keyring_updateAccount',
DeleteAccount = 'keyring_deleteAccount',
Expand Down
68 changes: 68 additions & 0 deletions src/rpc-handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Keyring } from './api';
import type { GetAccountBalancesRequest } from './internal';
import { KeyringRpcMethod, isKeyringRpcMethod } from './internal/rpc';
import type { JsonRpcRequest } from './JsonRpcRequest';
import { handleKeyringRequest } from './rpc-handler';
Expand All @@ -8,6 +9,7 @@ describe('handleKeyringRequest', () => {
listAccounts: jest.fn(),
getAccount: jest.fn(),
createAccount: jest.fn(),
getAccountBalances: jest.fn(),
filterAccountChains: jest.fn(),
updateAccount: jest.fn(),
deleteAccount: jest.fn(),
Expand Down Expand Up @@ -453,6 +455,72 @@ describe('handleKeyringRequest', () => {
'An unknown error occurred while handling the keyring request',
);
});

describe('getAccountBalances', () => {
it('successfully calls `keyring_getAccountBalances`', async () => {
const request: GetAccountBalancesRequest = {
jsonrpc: '2.0',
id: '2ac49e1a-4f5b-4dad-889c-73f3ca34fd3b',
method: 'keyring_getAccountBalances',
params: {
id: '987910cc-2d23-48c2-a362-c37f0715793e',
assets: ['bip122:000000000019d6689c085ae165831e93/slip44:0'],
},
};

await handleKeyringRequest(keyring, request);
expect(keyring.getAccountBalances).toHaveBeenCalledWith(
request.params.id,
request.params.assets,
);
});

it('fails because the account ID is not provided', async () => {
const request: JsonRpcRequest = {
jsonrpc: '2.0',
id: '2ac49e1a-4f5b-4dad-889c-73f3ca34fd3b',
method: 'keyring_getAccountBalances',
params: {
assets: ['bip122:000000000019d6689c085ae165831e93/slip44:0'],
},
};

await expect(handleKeyringRequest(keyring, request)).rejects.toThrow(
'At path: params.id -- Expected a value of type `UuidV4`, but received: `undefined`',
);
});

it('fails because the assets are not provided', async () => {
const request: JsonRpcRequest = {
jsonrpc: '2.0',
id: '2ac49e1a-4f5b-4dad-889c-73f3ca34fd3b',
method: 'keyring_getAccountBalances',
params: {
id: '987910cc-2d23-48c2-a362-c37f0715793e',
},
};

await expect(handleKeyringRequest(keyring, request)).rejects.toThrow(
'At path: params.assets -- Expected an array value, but received: undefined',
);
});

it('fails because the assets are not strings', async () => {
const request: JsonRpcRequest = {
jsonrpc: '2.0',
id: '2ac49e1a-4f5b-4dad-889c-73f3ca34fd3b',
method: 'keyring_getAccountBalances',
params: {
id: '987910cc-2d23-48c2-a362-c37f0715793e',
assets: [1, 2, 3],
},
};

await expect(handleKeyringRequest(keyring, request)).rejects.toThrow(
'At path: params.assets.0 -- Expected a string, but received: 1',
);
});
});
});

describe('isKeyringRpcMethod', () => {
Expand Down
12 changes: 12 additions & 0 deletions src/rpc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
FilterAccountChainsStruct,
ListAccountsRequestStruct,
ListRequestsRequestStruct,
GetAccountBalancesRequestStruct,
} from './internal/api';
import { KeyringRpcMethod } from './internal/rpc';
import type { JsonRpcRequest } from './JsonRpcRequest';
Expand Down Expand Up @@ -61,6 +62,17 @@ async function dispatchRequest(
return keyring.createAccount(request.params.options);
}

case KeyringRpcMethod.GetAccountBalances: {
if (keyring.getAccountBalances === undefined) {
throw new MethodNotSupportedError(request.method);
}
assert(request, GetAccountBalancesRequestStruct);
return keyring.getAccountBalances(
request.params.id,
request.params.assets,
);
}

case KeyringRpcMethod.FilterAccountChains: {
assert(request, FilterAccountChainsStruct);
return keyring.filterAccountChains(
Expand Down

0 comments on commit c72985f

Please sign in to comment.