Skip to content

Commit

Permalink
Add Caip Types (#116)
Browse files Browse the repository at this point in the history
* add caip chain id

* add parsing

* Update parseCaipChainIdString behavior

* lint

* drop string suffix

* Switch to @metamask/snap-utils caip types

* lint

* Separate regex const. Add CaipAccountAddressStruct

* Add CaipAccountAddress specs

* tighten types

* remove unused types. cleanup specs. add fixtures

* last bit
  • Loading branch information
jiexi authored Aug 1, 2023
1 parent e5bc219 commit 2653a1a
Show file tree
Hide file tree
Showing 6 changed files with 513 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/__fixtures__/caip-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export const CAIP_CHAIN_ID_FIXTURES = [
'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;

export const CAIP_NAMESPACE_FIXTURES = Array.from(
new Set(CAIP_CHAIN_ID_FIXTURES.map((value) => value.split(':')[0])),
);

export const CAIP_REFERENCE_FIXTURES = Array.from(
new Set(CAIP_CHAIN_ID_FIXTURES.map((value) => value.split(':')[1])),
);

export const CAIP_ACCOUNT_ID_FIXTURES = [
'eip155:1:0xab16a96D359eC26a11e2C2b3d8f8B8942d5Bfcdb',
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
'cosmos:cosmoshub-3:cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0',
'polkadot:b0a8d493285c2df73290dfb7e61f870f:5hmuyxw9xdgbpptgypokw4thfyoe3ryenebr381z9iaegmfy',
'starknet:SN_GOERLI:0x02dd1b492765c064eac4039e3841aa5f382773b598097a40073bd8b48170ab57',
'chainstd:8c3444cf8970a9e41a706fab93e7a6c4:6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7',
'hedera:mainnet:0.0.1234567890-zbhlt',
] as const;

export const CAIP_ACCOUNT_ADDRESS_FIXTURES = Array.from(
new Set(CAIP_ACCOUNT_ID_FIXTURES.map((value) => value.split(':')[2])),
);
1 change: 1 addition & 0 deletions src/__fixtures__/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './bytes';
export * from './caip-types';
export * from './coercions';
export * from './json';
export * from './numbers';
52 changes: 52 additions & 0 deletions src/caip-types.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expectAssignable, expectNotAssignable } from 'tsd';

import type {
CaipAccountAddress,
CaipAccountId,
CaipChainId,
CaipNamespace,
CaipReference,
} from '.';

const embeddedString = 'test';

// Valid caip strings:

expectAssignable<CaipChainId>('namespace:reference');
expectAssignable<CaipChainId>('namespace:');
expectAssignable<CaipChainId>(':reference');
expectAssignable<CaipChainId>(`${embeddedString}:${embeddedString}`);

expectAssignable<CaipNamespace>('string');
expectAssignable<CaipNamespace>(`${embeddedString}`);

expectAssignable<CaipReference>('string');
expectAssignable<CaipReference>(`${embeddedString}`);

expectAssignable<CaipAccountId>('namespace:reference:accountAddress');
expectAssignable<CaipAccountId>('namespace:reference:');
expectAssignable<CaipAccountId>(':reference:accountAddress');
expectAssignable<CaipAccountId>(
`${embeddedString}:${embeddedString}:${embeddedString}`,
);

expectAssignable<CaipAccountAddress>('string');
expectAssignable<CaipAccountAddress>(`${embeddedString}`);

// Not valid caip strings:

expectAssignable<CaipChainId>('namespace:😀');
expectAssignable<CaipChainId>('😀:reference');
expectNotAssignable<CaipChainId>(0);
expectNotAssignable<CaipChainId>('🙃');

expectNotAssignable<CaipNamespace>(0);

expectNotAssignable<CaipReference>(0);

expectAssignable<CaipAccountId>('namespace:reference:😀');
expectAssignable<CaipAccountId>('😀:reference:accountAddress');
expectNotAssignable<CaipAccountId>(0);
expectNotAssignable<CaipAccountId>('🙃');

expectNotAssignable<CaipAccountAddress>(0);
276 changes: 276 additions & 0 deletions src/caip-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import {
CAIP_ACCOUNT_ADDRESS_FIXTURES,
CAIP_ACCOUNT_ID_FIXTURES,
CAIP_CHAIN_ID_FIXTURES,
CAIP_NAMESPACE_FIXTURES,
CAIP_REFERENCE_FIXTURES,
} from './__fixtures__';
import {
isCaipAccountAddress,
isCaipAccountId,
isCaipChainId,
isCaipNamespace,
isCaipReference,
parseCaipAccountId,
parseCaipChainId,
} from './caip-types';

describe('isCaipChainId', () => {
it.each(CAIP_CHAIN_ID_FIXTURES)(
'returns true for a valid chain id %s',
(id) => {
expect(isCaipChainId(id)).toBe(true);
},
);

it.each([
true,
false,
null,
undefined,
1,
{},
[],
'!@#$%^&*()',
'a',
':1',
'123:',
'abC:1',
'eip155',
'eip155:',
'eip155:1:2',
'bip122',
'bip122:',
'bip122:000000000019d6689c085ae165831e93:2',
])('returns false for an invalid chain id %s', (id) => {
expect(isCaipChainId(id)).toBe(false);
});
});

describe('isCaipNamespace', () => {
it.each([...CAIP_NAMESPACE_FIXTURES])(
'returns true for a valid namespace %s',
(id) => {
expect(isCaipNamespace(id)).toBe(true);
},
);

it.each([true, false, null, undefined, 1, {}, [], 'abC', '12', '123456789'])(
'returns false for an invalid namespace %s',
(id) => {
expect(isCaipNamespace(id)).toBe(false);
},
);
});

describe('isCaipReference', () => {
it.each([...CAIP_REFERENCE_FIXTURES])(
'returns true for a valid reference %s',
(id) => {
expect(isCaipReference(id)).toBe(true);
},
);

it.each([
true,
false,
null,
undefined,
1,
{},
[],
'',
'!@#$%^&*()',
Array(33).fill('0').join(''),
])('returns false for an invalid reference %s', (id) => {
expect(isCaipReference(id)).toBe(false);
});
});

describe('isCaipAccountId', () => {
it.each([...CAIP_ACCOUNT_ID_FIXTURES])(
'returns true for a valid account id %s',
(id) => {
expect(isCaipAccountId(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:0x0000000000000000000000000000000000000000:2',
])('returns false for an invalid account id %s', (id) => {
expect(isCaipAccountId(id)).toBe(false);
});
});

describe('isCaipAccountAddress', () => {
it.each([...CAIP_ACCOUNT_ADDRESS_FIXTURES])(
'returns true for a valid account address %s',
(id) => {
expect(isCaipAccountAddress(id)).toBe(true);
},
);

it.each([
true,
false,
null,
undefined,
1,
{},
[],
'',
'!@#$%^&*()',
Array(129).fill('0').join(''),
])('returns false for an invalid account address %s', (id) => {
expect(isCaipAccountAddress(id)).toBe(false);
});
});

describe('parseCaipChainId', () => {
it('parses valid chain ids', () => {
expect(parseCaipChainId('eip155:1')).toMatchInlineSnapshot(`
{
"namespace": "eip155",
"reference": "1",
}
`);

expect(parseCaipChainId('bip122:000000000019d6689c085ae165831e93'))
.toMatchInlineSnapshot(`
{
"namespace": "bip122",
"reference": "000000000019d6689c085ae165831e93",
}
`);

expect(parseCaipChainId('cosmos:cosmoshub-3')).toMatchInlineSnapshot(`
{
"namespace": "cosmos",
"reference": "cosmoshub-3",
}
`);

expect(parseCaipChainId('polkadot:b0a8d493285c2df73290dfb7e61f870f'))
.toMatchInlineSnapshot(`
{
"namespace": "polkadot",
"reference": "b0a8d493285c2df73290dfb7e61f870f",
}
`);
});

it.each([
true,
false,
null,
undefined,
1,
'foo',
'foobarbazquz:1',
'foo:',
'foo:foobarbazquzfoobarbazquzfoobarbazquzfoobarbazquzfoobarbazquzfoobarbazquz',
])('throws for invalid input %s', (input) => {
expect(() => parseCaipChainId(input as any)).toThrow(
'Invalid CAIP chain ID.',
);
});
});

describe('parseCaipAccountId', () => {
it('parses valid account ids', () => {
expect(
parseCaipAccountId('eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb'),
).toMatchInlineSnapshot(`
{
"address": "0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb",
"chain": {
"namespace": "eip155",
"reference": "1",
},
"chainId": "eip155:1",
}
`);

expect(
parseCaipAccountId(
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
),
).toMatchInlineSnapshot(`
{
"address": "128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6",
"chain": {
"namespace": "bip122",
"reference": "000000000019d6689c085ae165831e93",
},
"chainId": "bip122:000000000019d6689c085ae165831e93",
}
`);

expect(
parseCaipAccountId(
'cosmos:cosmoshub-3:cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0',
),
).toMatchInlineSnapshot(`
{
"address": "cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0",
"chain": {
"namespace": "cosmos",
"reference": "cosmoshub-3",
},
"chainId": "cosmos:cosmoshub-3",
}
`);

expect(
parseCaipAccountId(
'polkadot:b0a8d493285c2df73290dfb7e61f870f:5hmuyxw9xdgbpptgypokw4thfyoe3ryenebr381z9iaegmfy',
),
).toMatchInlineSnapshot(`
{
"address": "5hmuyxw9xdgbpptgypokw4thfyoe3ryenebr381z9iaegmfy",
"chain": {
"namespace": "polkadot",
"reference": "b0a8d493285c2df73290dfb7e61f870f",
},
"chainId": "polkadot:b0a8d493285c2df73290dfb7e61f870f",
}
`);
});

it.each([
true,
false,
null,
undefined,
1,
'foo',
'foobarbazquz:1',
'foo:',
'foo:foobarbazquzfoobarbazquzfoobarbazquzfoobarbazquzfoobarbazquzfoobarbazquz',
'eip155:1',
'eip155:1:',
])('throws for invalid input %s', (input) => {
expect(() => parseCaipAccountId(input as any)).toThrow(
'Invalid CAIP account ID.',
);
});
});
Loading

0 comments on commit 2653a1a

Please sign in to comment.