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 chain_getBalances #1

Merged
merged 20 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
6 changes: 5 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ module.exports = {
collectCoverage: true,

// An array of glob patterns indicating a set of files for which coverage information should be collected
collectCoverageFrom: ['./src/**/*.ts'],
collectCoverageFrom: [
'./src/**/*.ts',
'!./src/**/index.ts',
'!./src/**/*.test-d.ts',
],

// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
"test": "jest && jest-it-up",
"test:watch": "jest --watch"
},
"resolutions": {
"superstruct@^1.0.3": "1.0.3"
},
"dependencies": {
"@metamask/utils": "^8.4.0",
"superstruct": "1.0.3"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^3.0.0",
"@lavamoat/preinstall-always-fail": "^2.0.0",
Expand Down
59 changes: 59 additions & 0 deletions src/JsonRpcRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { is } from 'superstruct';

import { JsonRpcRequestStruct } from './JsonRpcRequest';

describe('JsonRpcRequestStruct', () => {
it('should be a valid JsonRpcRequest with a numerical ID', () => {
const request = {
jsonrpc: '2.0',
id: 1,
method: 'my_method',
params: [1, 2, 3],
};

expect(is(request, JsonRpcRequestStruct)).toBe(true);
});

it('should be a valid JsonRpcRequest with a string ID', () => {
const request = {
jsonrpc: '2.0',
id: 'request_id',
method: 'my_method',
params: [1, 2, 3],
};

expect(is(request, JsonRpcRequestStruct)).toBe(true);
});

it('should be a valid JsonRpcRequest with a null ID', () => {
const request = {
jsonrpc: '2.0',
id: null,
method: 'my_method',
montelaidev marked this conversation as resolved.
Show resolved Hide resolved
params: [1, 2, 3],
};

expect(is(request, JsonRpcRequestStruct)).toBe(true);
});

it('should be a valid JsonRpcRequest without params', () => {
const request = {
jsonrpc: '2.0',
id: 1,
method: 'my_method',
};

expect(is(request, JsonRpcRequestStruct)).toBe(true);
});

it('should not be a valid JsonRpcRequest if params is undefined', () => {
const request = {
jsonrpc: '2.0',
id: 1,
method: 'my_method',
params: undefined,
};

expect(is(request, JsonRpcRequestStruct)).toBe(false);
});
});
19 changes: 19 additions & 0 deletions src/JsonRpcRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { JsonStruct } from '@metamask/utils';
import type { Infer } from 'superstruct';
import { array, literal, number, record, string, union } from 'superstruct';

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

export const JsonRpcRequestStruct = object({
ccharly marked this conversation as resolved.
Show resolved Hide resolved
jsonrpc: literal('2.0'),
id: union([string(), number(), literal(null)]),
method: string(),
params: exactOptional(
union([array(JsonStruct), record(string(), JsonStruct)]),
),
});

/**
* JSON-RPC request type.
*/
export type JsonRpcRequest = Infer<typeof JsonRpcRequestStruct>;
12 changes: 12 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { CaipChainId } from '@metamask/utils';

import type { CaipAssetTypeOrId } from './caip-types';
import type { BalancesResult } from './types';

export type Chain = {
getBalances(
scope: CaipChainId,
accounts: string[],
assets: CaipAssetTypeOrId[],
): Promise<BalancesResult>;
};
104 changes: 104 additions & 0 deletions src/caip-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
isCaipAssetType,
isCaipAssetId,
isCaipAssetTypeOrId,
} from './caip-types';

// Imported from: https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md#test-cases
const good = {
assetTypes: [
'eip155:1/slip44:60',
'bip122:000000000019d6689c085ae165831e93/slip44:0',
'cosmos:cosmoshub-3/slip44:118',
'bip122:12a765e31ffd4059bada1e25190f6e98/slip44:2',
'cosmos:Binance-Chain-Tigris/slip44:714',
'cosmos:iov-mainnet/slip44:234',
'lip9:9ee11e9df416b18b/slip44:134',
'eip155:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f',
'eip155:1/erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d',
],
assetIds: [
'eip155:1/erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d/771769',
'hedera:mainnet/nft:0.0.55492/12',
],
};

const badAssets = [
true,
false,
null,
undefined,
1,
{},
[],
'',
'!@#$%^&*()',
'foo',
'eip155',
'eip155:',
'eip155:1',
'eip155:1:',
'eip155:1:0x0000000000000000000000000000000000000000:2',
'bip122',
'bip122:',
'bip122:000000000019d6689c085ae165831e93',
'bip122:000000000019d6689c085ae165831e93/',
'bip122:000000000019d6689c085ae165831e93/tooooooolong',
'bip122:000000000019d6689c085ae165831e93/tooooooolong:asset',
'eip155:1/erc721',
'eip155:1/erc721:',
'eip155:1/erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d/',
];
const bad = {
assetTypes: badAssets,
assetIds: [
...badAssets,
'eip155:1/erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d/tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolongasset',
],
};

const uniq = (data1: any[], data2: any[]): any[] => {
return Array.from(new Set(data1.concat(data2)));
};

describe('isCaipAssetType', () => {
it.each(good.assetTypes)(
'returns true for a valid asset type %s',
(asset) => {
expect(isCaipAssetType(asset)).toBe(true);
},
);

it.each(bad.assetTypes)(
'returns false for an invalid asset type %s',
(asset) => {
expect(isCaipAssetType(asset)).toBe(false);
},
);
});

describe('isCaipAssetId', () => {
it.each(good.assetIds)('returns true for a valid asset id %s', (asset) => {
expect(isCaipAssetId(asset)).toBe(true);
});

it.each(bad.assetIds)('returns false for an invalid asset id %s', (asset) => {
expect(isCaipAssetType(asset)).toBe(false);
});
});

describe('isCaipAssetTypeOrId', () => {
it.each(uniq(good.assetIds, good.assetTypes))(
'returns true for a valid asset %s',
(asset) => {
expect(isCaipAssetTypeOrId(asset)).toBe(true);
},
);

it.each(uniq(bad.assetIds, bad.assetIds))(
'returns false for an invalid asset %s',
(asset) => {
expect(isCaipAssetTypeOrId(asset)).toBe(false);
},
);
});
62 changes: 62 additions & 0 deletions src/caip-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Infer } from 'superstruct';
import { is, string, pattern } from 'superstruct';

export const CAIP_ASSET_TYPE_REGEX =
/^(?<chainId>(?<namespace>[-a-z0-9]{3,8}):(?<reference>[-_a-zA-Z0-9]{1,32}))\/(?<assetNamespace>[-a-z0-9]{3,8}):(?<assetReference>[-.%a-zA-Z0-9]{1,128})$/u;

export const CAIP_ASSET_ID_REGEX =
/^(?<chainId>(?<namespace>[-a-z0-9]{3,8}):(?<reference>[-_a-zA-Z0-9]{1,32}))\/(?<assetNamespace>[-a-z0-9]{3,8}):(?<assetReference>[-.%a-zA-Z0-9]{1,128})\/(?<tokenId>[-.%a-zA-Z0-9]{1,78})$/u;

export const CAIP_ASSET_TYPE_OR_ID_REGEX =
/^(?<chainId>(?<namespace>[-a-z0-9]{3,8}):(?<reference>[-_a-zA-Z0-9]{1,32}))\/(?<assetNamespace>[-a-z0-9]{3,8}):(?<assetReference>[-.%a-zA-Z0-9]{1,128})(\/(?<tokenId>[-.%a-zA-Z0-9]{1,78}))?$/u;

/**
* A CAIP-19 asset type identifier, i.e., a human-readable type of asset type identifier.
*/
export const CaipAssetTypeStruct = pattern(string(), CAIP_ASSET_TYPE_REGEX);
export type CaipAssetType = Infer<typeof CaipAssetTypeStruct>;

/**
* A CAIP-19 asset ID identifier, i.e., a human-readable type of asset ID identifier.
*/
export const CaipAssetIdStruct = pattern(string(), CAIP_ASSET_ID_REGEX);
export type CaipAssetId = Infer<typeof CaipAssetIdStruct>;

/**
* A CAIP-19 asset type or asset ID identifier, i.e., a human-readable type of asset identifier.
*/
export const CaipAssetTypeOrIdStruct = pattern(
string(),
CAIP_ASSET_TYPE_OR_ID_REGEX,
);
export type CaipAssetTypeOrId = Infer<typeof CaipAssetTypeOrIdStruct>;

/**
* Check if the given value is a {@link CaipAssetType}.
*
* @param value - The value to check.
* @returns Whether the value is a {@link CaipAssetType}.
*/
export function isCaipAssetType(value: unknown): value is CaipAssetType {
return is(value, CaipAssetTypeStruct);
}

/**
* Check if the given value is a {@link CaipAssetId}.
*
* @param value - The value to check.
* @returns Whether the value is a {@link CaipAssetId}.
*/
export function isCaipAssetId(value: unknown): value is CaipAssetId {
return is(value, CaipAssetIdStruct);
}

/**
* Check if the given value is a {@link CaipAssetTypeOrId}.
*
* @param value - The value to check.
* @returns Whether the value is a {@link CaipAssetTypeOrId}.
*/
export function isCaipAssetTypeOrId(value: unknown): value is CaipAssetId {
return is(value, CaipAssetTypeOrIdStruct);
}
9 changes: 0 additions & 9 deletions src/index.test.ts

This file was deleted.

14 changes: 5 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
/**
* Example function that returns a greeting for the given name.
*
* @param name - The name to greet.
* @returns The greeting.
*/
export default function greeter(name: string): string {
return `Hello, ${name}!`;
}
export * from './api';
export * from './types';
export * from './caip-types';
export * from './rpc-handler';
export * from './rpc-types';
Loading