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

Add Caip Types #116

Merged
merged 14 commits into from
Aug 1, 2023
23 changes: 23 additions & 0 deletions src/caip-chaid-id.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expectAssignable, expectNotAssignable } from 'tsd';

import { CaipChainId } from '.';

// Valid caip chain id strings:

expectAssignable<CaipChainId>('namespace:reference');

expectAssignable<CaipChainId>('namespace:');

expectAssignable<CaipChainId>(':reference');

const embeddedString = 'test';
expectAssignable<CaipChainId>(`${embeddedString}:${embeddedString}`);

// Not valid caip chain id strings:

expectAssignable<CaipChainId>('namespace:😀');
expectAssignable<CaipChainId>('😀:reference');
jiexi marked this conversation as resolved.
Show resolved Hide resolved

expectNotAssignable<CaipChainId>(0);

expectNotAssignable<CaipChainId>('🙃');
jiexi marked this conversation as resolved.
Show resolved Hide resolved
135 changes: 135 additions & 0 deletions src/caip-chain-id.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
isCaipChainIdString,
assertIsCaipChainIdString,
getCaipChainIdString,
parseCaipChainIdString,
} from './caip-chain-id';

const validCaipChainIdStrings = [
'123:a',
'12345678:a',
'abc:1',
'abc:1234567890abcdefghijklmnopqrst32',
'12345678:1234567890abcdefghijklmnopqrst32',
'az-45678:abcxyz_1234567890-ABCXYZ',
'eip155:1',
'bip122:000000000019d6689c085ae165831e93',
'bip122:12a765e31ffd4059bada1e25190f6e98',
'bip122:fdbe99b90c90bae7505796461471d89a',
'cosmos:cosmoshub-2',
'cosmos:cosmoshub-3',
'cosmos:Binance-Chain-Tigris',
'cosmos:iov-mainnet',
'starknet:SN_GOERLI',
'lip9:9ee11e9df416b18b',
'chainstd:8c3444cf8970a9e41a706fab93e7a6c4',
] as const;

const invalidCaipChainIdStrings = [
true,
false,
null,
undefined,
0,
1,
{},
[],
'12:a',
'123456789:a',
'abc:',
'abc:1234567890abcdefghijklmnopqrstu33',
'abc',
'123::a',
'123:a:a',
':123:a',
'Abc:1',
'abc!@#$:1',
'abc:!@#$%^&*()123',
] as const;

describe('isCaipChainIdString', () => {
it.each(validCaipChainIdStrings)(
'returns true for a valid caip chain id string',
(caipChainIdString) => {
expect(isCaipChainIdString(caipChainIdString)).toBe(true);
},
);

it.each(invalidCaipChainIdStrings)(
'returns false for an invalid caip chain id string',
(caipChainIdString) => {
expect(isCaipChainIdString(caipChainIdString)).toBe(false);
},
);
});

describe('assertIsCaipChainIdString', () => {
it.each(validCaipChainIdStrings)(
'does not throw for a valid caip chain id string',
(caipChainIdString) => {
expect(() => assertIsCaipChainIdString(caipChainIdString)).not.toThrow();
},
);

it.each(invalidCaipChainIdStrings)(
'throws for an invalid caip chain id string',
(caipChainIdString) => {
expect(() => assertIsCaipChainIdString(caipChainIdString)).toThrow(
'Value must be a caip chain id string.',
);
},
);
});

describe('getCaipChainIdString', () => {
it('returns the unvalidated caip chain id string', () => {
expect(getCaipChainIdString('eip155', '1')).toBe('eip155:1');
expect(getCaipChainIdString('namespace', 'reference')).toBe(
'namespace:reference',
);
expect(getCaipChainIdString('', '')).toBe(':');
expect(getCaipChainIdString('UNVALIDATED', '!@#$%^&*()')).toBe(
'UNVALIDATED:!@#$%^&*()',
);
});
});

describe('parseCaipChainIdString', () => {
it('returns the namespace and reference for valid caip chain id', () => {
expect(parseCaipChainIdString('eip155:1')).toStrictEqual({
namespace: 'eip155',
reference: '1',
});
expect(parseCaipChainIdString('name:reference')).toStrictEqual({
namespace: 'name',
reference: 'reference',
});
expect(parseCaipChainIdString('abc:123')).toStrictEqual({
namespace: 'abc',
reference: '123',
});
});

it('returns empty strings for invalid caip chain id', () => {
expect(parseCaipChainIdString('12:a')).toStrictEqual({
namespace: '',
reference: '',
});
expect(parseCaipChainIdString('abc:')).toStrictEqual({
namespace: '',
reference: '',
});
expect(parseCaipChainIdString(':')).toStrictEqual({
namespace: '',
reference: '',
});
expect(parseCaipChainIdString('')).toStrictEqual({
namespace: '',
reference: '',
});
expect(parseCaipChainIdString('abc:123:xyz')).toStrictEqual({
namespace: '',
reference: '',
});
});
});
67 changes: 67 additions & 0 deletions src/caip-chain-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { is, pattern, string, Struct } from 'superstruct';

import { assert } from './assert';

export type CaipChainId = `${string}:${string}`;
jiexi marked this conversation as resolved.
Show resolved Hide resolved

const CAIP2_REGEX = /^([-a-z0-9]{3,8}):([-_a-zA-Z0-9]{1,32})$/u;

export const CaipChainIdStruct = pattern(string(), CAIP2_REGEX) as Struct<
CaipChainId,
null
>;

export type ParsedCaipChainId = {
namespace: string;
reference: string;
};

/**
* Check if a string is a valid caip chain id string.
*
* @param value - The value to check.
* @returns Whether the value is a valid caip chain id string.
*/
export function isCaipChainIdString(value: unknown): value is CaipChainId {
mcmire marked this conversation as resolved.
Show resolved Hide resolved
return is(value, CaipChainIdStruct);
}

/**
* Assert that a value is a valid caip chain id string.
*
* @param value - The value to check.
* @throws If the value is not a valid caip chain id string.
*/
export function assertIsCaipChainIdString(
value: unknown,
): asserts value is CaipChainId {
assert(isCaipChainIdString(value), 'Value must be a caip chain id string.');
}

/**
* Returns caip chain id string from namespace and reference.
*
* @param namespace - The caip chain id namespace string.
* @param reference - The caip chaid id reference string.
* @returns The unvalidated caip chaid id string.
*/
export function getCaipChainIdString(
namespace: string,
reference: string,
): string {
return `${namespace}:${reference}`;
jiexi marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Returns the namespace and reference strings from caip chain id string.
*
* @param caipChainId - The caip chain id string.
* @returns The {@link ParsedCaipChainId} object.
*/
export function parseCaipChainIdString(caipChainId: string): ParsedCaipChainId {
jiexi marked this conversation as resolved.
Show resolved Hide resolved
const [, namespace, reference] = caipChainId.match(CAIP2_REGEX) ?? [];
return {
namespace: namespace ?? '',
reference: reference ?? '',
};
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './assert';
export * from './base64';
export * from './bytes';
export * from './caip-chain-id';
export * from './checksum';
export * from './coercers';
export * from './collections';
Expand Down