From d532b071735ec622e6c150b9be1c649767bc144a Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Thu, 14 Mar 2024 16:53:47 +0100 Subject: [PATCH] feat(caip): add .toCaipChainId + KnownCaipNamespace (#175) This adds some new helpers regarding CAIP-2 chain IDs. This is in regard to the on-going work of adding those chain-agnostics IDs into our Snap keyring implementations. Initially those helpers were living on https://github.com/MetaMask/eth-snap-keyring repository, but it feels more natural to have them here. Moreover, we might use them elsewhere. ## Related - https://github.com/MetaMask/eth-snap-keyring/pull/231 --------- Co-authored-by: Elliot Winkler --- src/caip-types.test.ts | 57 ++++++++++++++++++++++++++++++++++++++++++ src/caip-types.ts | 40 +++++++++++++++++++++++++++++ src/index.test.ts | 2 ++ src/node.test.ts | 2 ++ 4 files changed, 101 insertions(+) diff --git a/src/caip-types.test.ts b/src/caip-types.test.ts index 3b5acc9d5..d610f0e88 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,56 @@ 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 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 + '', + '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 = 'abc'; + 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",