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

feat: add CAIP-19 types and split utils #321

Merged
merged 1 commit into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/KeyringClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from './internal/api';
import { KeyringRpcMethod } from './internal/rpc';
import type { JsonRpcRequest } from './JsonRpcRequest';
import { strictMask } from './utils';
import { strictMask } from './superstruct';

export type Sender = {
send(request: JsonRpcRequest): Promise<Json>;
Expand Down
3 changes: 2 additions & 1 deletion src/eth/erc4337/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Infer } from 'superstruct';

import { UrlStruct, exactOptional, object } from '../../superstruct';
import { exactOptional, object } from '../../superstruct';
import { UrlStruct } from '../../utils';
import { EthAddressStruct, EthBytesStruct, EthUint256Struct } from '../types';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/eth/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UrlStruct } from '../superstruct';
import { UrlStruct } from '../utils';

describe('types', () => {
it('is a valid BundlerUrl', () => {
Expand Down
32 changes: 17 additions & 15 deletions src/superstruct.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Infer, Context } from 'superstruct';
import { Struct, define, object as stObject } from 'superstruct';
import { Struct, assert, define, object as stObject } from 'superstruct';
import type {
ObjectSchema,
OmitBy,
Expand Down Expand Up @@ -127,19 +127,21 @@ export function definePattern(
}

/**
* Validates if a given value is a valid URL.
* Assert that a value is valid according to a struct.
*
* @param value - The value to be validated.
* @returns A boolean indicating if the value is a valid URL.
* It is similar to superstruct's mask function, but it does not ignore extra
* properties.
*
* @param value - Value to check.
* @param struct - Struct to validate the value against.
* @param message - Error message to throw if the value is not valid.
* @returns The value if it is valid.
*/
export const UrlStruct = define<string>('Url', (value: unknown) => {
let url;

try {
url = new URL(value as string);
} catch (_) {
return false;
}

return url.protocol === 'http:' || url.protocol === 'https:';
});
export function strictMask<Type, Schema>(
value: unknown,
struct: Struct<Type, Schema>,
message?: string,
): Type {
assert(value, struct, message);
return value;
}
86 changes: 86 additions & 0 deletions src/utils/caip.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { isCaipAssetId, isCaipAssetType } from './caip';

describe('isCaipAssetType', () => {
// Imported from: https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md#test-cases
it.each([
'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',
])('returns true for a valid asset type %s', (id) => {
expect(isCaipAssetType(id)).toBe(true);
});

it.each([
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/',
])('returns false for an invalid asset type %s', (id) => {
expect(isCaipAssetType(id)).toBe(false);
});
});

describe('isCaipAssetId', () => {
// Imported from: https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md#test-cases
it.each([
'eip155:1/erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d/771769',
'hedera:mainnet/nft:0.0.55492/12',
])('returns true for a valid asset id %s', (id) => {
expect(isCaipAssetId(id)).toBe(true);
});

it.each([
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/',
])('returns false for an invalid asset id %s', (id) => {
expect(isCaipAssetType(id)).toBe(false);
});
});
59 changes: 59 additions & 0 deletions src/utils/caip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { is, type Infer } from 'superstruct';

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

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;

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;

/**
* A CAIP-19 asset type identifier, i.e., a human-readable type of asset identifier.
*/
export const CaipAssetTypeStruct = definePattern(
'CaipAssetType',
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.
*/
export const CaipAssetIdStruct = definePattern(
'CaipAssetId',
CAIP_ASSET_ID_REGEX,
);
export type CaipAssetId = Infer<typeof CaipAssetIdStruct>;

/**
* Check if the given value is a {@link CaipAssetType}.
*
* @param value - The value to check.
* @returns Whether the value is a {@link CaipAssetType}.
* @example
* ```ts
* isCaipAssetType('eip155:1/slip44:60'); // true
* isCaipAssetType('cosmos:cosmoshub-3/slip44:118'); // true
* isCaipAssetType('hedera:mainnet/nft:0.0.55492/12'); // false
* ```
*/
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}.
* @example
* ```ts
* isCaipAssetType('eip155:1/slip44:60'); // false
* isCaipAssetType('cosmos:cosmoshub-3/slip44:118'); // false
* isCaipAssetType('hedera:mainnet/nft:0.0.55492/12'); // true
* ```
*/
export function isCaipAssetId(value: unknown): value is CaipAssetId {
return is(value, CaipAssetIdStruct);
}
4 changes: 4 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './caip';
export * from './typing';
export * from './url';
export * from './uuid';
4 changes: 2 additions & 2 deletions src/utils.test-d.ts → src/utils/typing.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Extends } from './utils';
import { expectTrue } from './utils';
import type { Extends } from './typing';
import { expectTrue } from './typing';

expectTrue<true>();

Expand Down
2 changes: 1 addition & 1 deletion src/utils.test.ts → src/utils/typing.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expectTrue } from './utils';
import { expectTrue } from './typing';

describe('expectTrue', () => {
it('does nothing since expectTrue is an empty function', () => {
Expand Down
33 changes: 0 additions & 33 deletions src/utils.ts → src/utils/typing.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
import { assert } from 'superstruct';
import type { Struct } from 'superstruct';

import { definePattern } from './superstruct';

/**
* UUIDv4 struct.
*/
export const UuidStruct = definePattern(
'UuidV4',
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu,
);

/**
* Omit keys from a union type.
*
Expand All @@ -23,26 +10,6 @@ export type OmitUnion<Type, Key extends keyof any> = Type extends any
? Omit<Type, Key>
: never;

/**
* Assert that a value is valid according to a struct.
*
* It is similar to superstruct's mask function, but it does not ignore extra
* properties.
*
* @param value - Value to check.
* @param struct - Struct to validate the value against.
* @param message - Error message to throw if the value is not valid.
* @returns The value if it is valid.
*/
export function strictMask<Type, Schema>(
value: unknown,
struct: Struct<Type, Schema>,
message?: string,
): Type {
assert(value, struct, message);
return value;
}

/**
* Type that resolves to `true` if `Child` extends `Base`, otherwise `false`.
*
Expand Down
19 changes: 19 additions & 0 deletions src/utils/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { define } from 'superstruct';

/**
* Validates if a given value is a valid URL.
*
* @param value - The value to be validated.
* @returns A boolean indicating if the value is a valid URL.
*/
export const UrlStruct = define<string>('Url', (value: unknown) => {
let url;

try {
url = new URL(value as string);
} catch (_) {
return false;
}

return url.protocol === 'http:' || url.protocol === 'https:';
});
9 changes: 9 additions & 0 deletions src/utils/uuid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { definePattern } from '../superstruct';

/**
* UUIDv4 struct.
*/
export const UuidStruct = definePattern(
'UuidV4',
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu,
);
Loading