Skip to content

Commit

Permalink
feat(caip): add support for CAIP-19 (#183)
Browse files Browse the repository at this point in the history
## Description

Add support for CAIP-19 assets type/ID.
  • Loading branch information
ccharly authored Oct 9, 2024
1 parent f4e3820 commit b7b9cfe
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
87 changes: 87 additions & 0 deletions src/caip-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
isCaipChainId,
isCaipNamespace,
isCaipReference,
isCaipAssetType,
isCaipAssetId,
parseCaipAccountId,
parseCaipChainId,
toCaipChainId,
Expand Down Expand Up @@ -149,6 +151,91 @@ describe('isCaipAccountAddress', () => {
});
});

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);
});
});

describe('parseCaipChainId', () => {
it('parses valid chain ids', () => {
expect(parseCaipChainId('eip155:1')).toMatchInlineSnapshot(`
Expand Down
44 changes: 44 additions & 0 deletions src/caip-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export const CAIP_ACCOUNT_ID_REGEX =

export const CAIP_ACCOUNT_ADDRESS_REGEX = /^[-.%a-zA-Z0-9]{1,128}$/u;

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;

/**
* A CAIP-2 chain ID, i.e., a human-readable namespace and reference.
*/
Expand Down Expand Up @@ -52,6 +58,24 @@ export const CaipAccountAddressStruct = pattern(
);
export type CaipAccountAddress = Infer<typeof CaipAccountAddressStruct>;

/**
* A CAIP-19 asset type identifier, i.e., a human-readable type of asset identifier.
*/
export const CaipAssetTypeStruct = pattern(
string(),
CAIP_ASSET_TYPE_REGEX,
) as Struct<CaipAssetType, null>;
export type CaipAssetType = `${string}:${string}/${string}:${string}`;

/**
* A CAIP-19 asset ID identifier, i.e., a human-readable type of asset ID.
*/
export const CaipAssetIdStruct = pattern(
string(),
CAIP_ASSET_ID_REGEX,
) as Struct<CaipAssetId, null>;
export type CaipAssetId = `${string}:${string}/${string}:${string}/${string}`;

/** Known CAIP namespaces. */
export enum KnownCaipNamespace {
/** BIP-122 (Bitcoin) compatible chains. */
Expand Down Expand Up @@ -113,6 +137,26 @@ export function isCaipAccountAddress(
return is(value, CaipAccountAddressStruct);
}

/**
* 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);
}

/**
* Parse a CAIP-2 chain ID to an object containing the namespace and reference.
* This validates the CAIP-2 chain ID before parsing it.
Expand Down
6 changes: 6 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ describe('index', () => {
"AssertionError",
"CAIP_ACCOUNT_ADDRESS_REGEX",
"CAIP_ACCOUNT_ID_REGEX",
"CAIP_ASSET_ID_REGEX",
"CAIP_ASSET_TYPE_REGEX",
"CAIP_CHAIN_ID_REGEX",
"CAIP_NAMESPACE_REGEX",
"CAIP_REFERENCE_REGEX",
"CaipAccountAddressStruct",
"CaipAccountIdStruct",
"CaipAssetIdStruct",
"CaipAssetTypeStruct",
"CaipChainIdStruct",
"CaipNamespaceStruct",
"CaipReferenceStruct",
Expand Down Expand Up @@ -95,6 +99,8 @@ describe('index', () => {
"isBytes",
"isCaipAccountAddress",
"isCaipAccountId",
"isCaipAssetId",
"isCaipAssetType",
"isCaipChainId",
"isCaipNamespace",
"isCaipReference",
Expand Down
6 changes: 6 additions & 0 deletions src/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ describe('node', () => {
"AssertionError",
"CAIP_ACCOUNT_ADDRESS_REGEX",
"CAIP_ACCOUNT_ID_REGEX",
"CAIP_ASSET_ID_REGEX",
"CAIP_ASSET_TYPE_REGEX",
"CAIP_CHAIN_ID_REGEX",
"CAIP_NAMESPACE_REGEX",
"CAIP_REFERENCE_REGEX",
"CaipAccountAddressStruct",
"CaipAccountIdStruct",
"CaipAssetIdStruct",
"CaipAssetTypeStruct",
"CaipChainIdStruct",
"CaipNamespaceStruct",
"CaipReferenceStruct",
Expand Down Expand Up @@ -100,6 +104,8 @@ describe('node', () => {
"isBytes",
"isCaipAccountAddress",
"isCaipAccountId",
"isCaipAssetId",
"isCaipAssetType",
"isCaipChainId",
"isCaipNamespace",
"isCaipReference",
Expand Down

0 comments on commit b7b9cfe

Please sign in to comment.