From 066bde6b0611e6c4bef342a3206f15f2435ea9c2 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 13 Mar 2024 16:01:57 +0100 Subject: [PATCH 1/4] feat(caip): add .toCaipChainId + KnownCaipNamespace --- src/caip-types.test.ts | 47 ++++++++++++++++++++++++++++++++++++++++++ src/caip-types.ts | 40 +++++++++++++++++++++++++++++++++++ src/index.test.ts | 2 ++ src/node.test.ts | 2 ++ 4 files changed, 91 insertions(+) diff --git a/src/caip-types.test.ts b/src/caip-types.test.ts index 3b5acc9d5..77b57d841 100644 --- a/src/caip-types.test.ts +++ b/src/caip-types.test.ts @@ -13,6 +13,10 @@ import { isCaipReference, parseCaipAccountId, parseCaipChainId, + toCaipChainId, + KnownCaipNamespace, + CAIP_NAMESPACE_REGEX, + CAIP_REFERENCE_REGEX, } from './caip-types'; describe('isCaipChainId', () => { @@ -274,3 +278,46 @@ describe('parseCaipAccountId', () => { ); }); }); + +describe('toCaipChainId', () => { + // This function relies on @metamask/utils CAIP helpers. Those are being + // tested with a variety of inputs. + // Here we mainly focus on our own wrapper around those: + + it('returns a valid CAIP-2 chain ID', () => { + const namespace = KnownCaipNamespace.Eip155; + const reference = '1'; + expect(toCaipChainId(namespace, reference)).toBe( + `${namespace}:${reference}`, + ); + }); + + it.each([ + // Too short, must have 3 chars at least + '', + 'xs', + // Not matching + '!@#$%^&*()', + // Too long + 'namespacetoolong', + ])('throws for invalid namespaces: %s', (namespace) => { + const reference = '1'; + expect(() => toCaipChainId(namespace, reference)).toThrow( + `Invalid "namespace", must match: ${CAIP_NAMESPACE_REGEX.toString()}`, + ); + }); + + it.each([ + // Too short, must have 1 char at least + '', + // Not matching + '!@#$%^&*()', + // Too long + '012345678901234567890123456789012', // 33 chars + ])('throws for invalid reference: %s', (reference) => { + const namespace = KnownCaipNamespace.Eip155; + expect(() => toCaipChainId(namespace, reference)).toThrow( + `Invalid "reference", must match: ${CAIP_REFERENCE_REGEX.toString()}`, + ); + }); +}); diff --git a/src/caip-types.ts b/src/caip-types.ts index bbd1b4d41..416babf46 100644 --- a/src/caip-types.ts +++ b/src/caip-types.ts @@ -46,6 +46,12 @@ export const CaipAccountAddressStruct = pattern( ); export type CaipAccountAddress = Infer; +/** Known CAIP namespaces. */ +export enum KnownCaipNamespace { + /** EIP-155 compatible chains. */ + Eip155 = 'eip155', +} + /** * Check if the given value is a {@link CaipChainId}. * @@ -146,3 +152,37 @@ export function parseCaipAccountId(caipAccountId: CaipAccountId): { }, }; } + +/** + * Chain ID as defined per the CAIP-2 + * {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md}. + * + * It defines a way to uniquely identify any blockchain in a human-readable + * way. + * + * @param namespace - The standard (ecosystem) of similar blockchains. + * @param reference - Identify of a blockchain within a given namespace. + * @throws {@link Error} + * This exception is thrown if the inputs does not comply with the CAIP-2 + * syntax specification + * {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md#syntax}. + * @returns A CAIP chain ID. + */ +export function toCaipChainId( + namespace: CaipNamespace, + reference: CaipReference, +): CaipChainId { + if (!isCaipNamespace(namespace)) { + throw new Error( + `Invalid "namespace", must match: ${CAIP_NAMESPACE_REGEX.toString()}`, + ); + } + + if (!isCaipReference(reference)) { + throw new Error( + `Invalid "reference", must match: ${CAIP_REFERENCE_REGEX.toString()}`, + ); + } + + return `${namespace}:${reference}`; +} diff --git a/src/index.test.ts b/src/index.test.ts index e3bbf1ccb..e9a0ad8fd 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -34,6 +34,7 @@ describe('index', () => { "JsonRpcVersionStruct", "JsonSize", "JsonStruct", + "KnownCaipNamespace", "PendingJsonRpcResponseStruct", "StrictHexStruct", "UnsafeJsonStruct", @@ -129,6 +130,7 @@ describe('index', () => { "signedBigIntToBytes", "stringToBytes", "timeSince", + "toCaipChainId", "valueToBytes", "wrapError", ] diff --git a/src/node.test.ts b/src/node.test.ts index b92889550..7fd17a33f 100644 --- a/src/node.test.ts +++ b/src/node.test.ts @@ -34,6 +34,7 @@ describe('node', () => { "JsonRpcVersionStruct", "JsonSize", "JsonStruct", + "KnownCaipNamespace", "PendingJsonRpcResponseStruct", "StrictHexStruct", "UnsafeJsonStruct", @@ -136,6 +137,7 @@ describe('node', () => { "signedBigIntToBytes", "stringToBytes", "timeSince", + "toCaipChainId", "valueToBytes", "wrapError", "writeFile", From 3d1c24f522b6d7ffc3a050ec667ce0d23cd5bcaa Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 13 Mar 2024 23:02:46 +0100 Subject: [PATCH 2/4] test(caip): generic test + uses all KnowCaipNamespace values Co-authored-by: Elliot Winkler --- src/caip-types.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/caip-types.test.ts b/src/caip-types.test.ts index 77b57d841..ea8df8e2e 100644 --- a/src/caip-types.test.ts +++ b/src/caip-types.test.ts @@ -284,14 +284,24 @@ describe('toCaipChainId', () => { // tested with a variety of inputs. // Here we mainly focus on our own wrapper around those: - it('returns a valid CAIP-2 chain ID', () => { - const namespace = KnownCaipNamespace.Eip155; + it('returns a valid CAIP-2 chain ID when given a valid namespace and reference', () => { + const namespace = 'abc'; const reference = '1'; expect(toCaipChainId(namespace, reference)).toBe( `${namespace}:${reference}`, ); }); + it.each(Object.values(KnownCaipNamespace))( + 'treats %s as a valid namespace', + (namespace) => { + const reference = '1'; + expect(toCaipChainId(namespace, reference)).toBe( + `${namespace}:${reference}`, + ); + } + ); + it.each([ // Too short, must have 3 chars at least '', From a75ad67e5edf44bd8783cd0325e09563631ace7b Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 13 Mar 2024 23:04:05 +0100 Subject: [PATCH 3/4] test(caip): do not use KnownCaipNamespace in test cases Co-authored-by: Elliot Winkler --- src/caip-types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/caip-types.test.ts b/src/caip-types.test.ts index ea8df8e2e..6de978e4e 100644 --- a/src/caip-types.test.ts +++ b/src/caip-types.test.ts @@ -325,7 +325,7 @@ describe('toCaipChainId', () => { // Too long '012345678901234567890123456789012', // 33 chars ])('throws for invalid reference: %s', (reference) => { - const namespace = KnownCaipNamespace.Eip155; + const namespace = 'abc'; expect(() => toCaipChainId(namespace, reference)).toThrow( `Invalid "reference", must match: ${CAIP_REFERENCE_REGEX.toString()}`, ); From e34cd07a2259bc10ee894d3d08ee59043687fa89 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 13 Mar 2024 23:05:08 +0100 Subject: [PATCH 4/4] chore: lint --- src/caip-types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/caip-types.test.ts b/src/caip-types.test.ts index 6de978e4e..d610f0e88 100644 --- a/src/caip-types.test.ts +++ b/src/caip-types.test.ts @@ -299,7 +299,7 @@ describe('toCaipChainId', () => { expect(toCaipChainId(namespace, reference)).toBe( `${namespace}:${reference}`, ); - } + }, ); it.each([