diff --git a/packages/caip/src/caips/caip-19.test.ts b/packages/caip/src/caips/caip-19.test.ts new file mode 100644 index 0000000..65ded2c --- /dev/null +++ b/packages/caip/src/caips/caip-19.test.ts @@ -0,0 +1,164 @@ +import { describe, expect, it } from "vitest" + +import { + caip19AssetIdRegex, + caip19AssetNameRegex, + caip19AssetNamespaceRegex, + caip19AssetReferenceRegex, + caip19AssetTypeRegex, + caip19TokenIdRegex, +} from "./index" + +describe("caip19AssetNamespaceRegex", () => { + it("matches a valid asset namespace", () => { + expect(caip19AssetNamespaceRegex.test("erc20")).toBe(true) + }) + + it("matches a namespace with hyphens", () => { + expect(caip19AssetNamespaceRegex.test("erc-20")).toBe(true) + }) + + it("rejects a namespace shorter than 3 characters", () => { + expect(caip19AssetNamespaceRegex.test("ab")).toBe(false) + }) + + it("rejects a namespace longer than 8 characters", () => { + expect(caip19AssetNamespaceRegex.test("abcdefghi")).toBe(false) + }) + + it("rejects uppercase characters", () => { + expect(caip19AssetNamespaceRegex.test("ERC20")).toBe(false) + }) +}) + +describe("caip19AssetReferenceRegex", () => { + it("matches a contract address", () => { + expect( + caip19AssetReferenceRegex.test( + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + ), + ).toBe(true) + }) + + it("matches a reference with dots and percent signs", () => { + expect(caip19AssetReferenceRegex.test("token.ref%20")).toBe(true) + }) + + it("rejects an empty reference", () => { + expect(caip19AssetReferenceRegex.test("")).toBe(false) + }) + + it("rejects a reference longer than 128 characters", () => { + const longRef = "a".repeat(129) + expect(caip19AssetReferenceRegex.test(longRef)).toBe(false) + }) +}) + +describe("caip19AssetNameRegex", () => { + it("matches a valid ERC-20 asset name", () => { + expect( + caip19AssetNameRegex.test( + "erc20:0xdAC17F958D2ee523a2206206994597C13D831ec7", + ), + ).toBe(true) + }) + + it("matches a valid ERC-721 asset name", () => { + expect( + caip19AssetNameRegex.test( + "erc721:0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", + ), + ).toBe(true) + }) + + it("rejects an asset name without a colon separator", () => { + expect(caip19AssetNameRegex.test("erc20-0xabc")).toBe(false) + }) +}) + +describe("caip19AssetTypeRegex", () => { + it("matches a valid ERC-20 asset type", () => { + expect( + caip19AssetTypeRegex.test( + "eip155:1/erc20:0xdAC17F958D2ee523a2206206994597C13D831ec7", + ), + ).toBe(true) + }) + + it("matches a valid asset type on Base", () => { + expect( + caip19AssetTypeRegex.test( + "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + ), + ).toBe(true) + }) + + it("rejects an asset type missing the chain ID", () => { + expect( + caip19AssetTypeRegex.test( + "erc20:0xdAC17F958D2ee523a2206206994597C13D831ec7", + ), + ).toBe(false) + }) + + it("rejects an asset type with an invalid namespace", () => { + expect( + caip19AssetTypeRegex.test( + "XX:1/erc20:0xdAC17F958D2ee523a2206206994597C13D831ec7", + ), + ).toBe(false) + }) +}) + +describe("caip19TokenIdRegex", () => { + it("matches a numeric token ID", () => { + expect(caip19TokenIdRegex.test("1234")).toBe(true) + }) + + it("matches an alphanumeric token ID", () => { + expect(caip19TokenIdRegex.test("token-1.2")).toBe(true) + }) + + it("rejects an empty token ID", () => { + expect(caip19TokenIdRegex.test("")).toBe(false) + }) + + it("rejects a token ID longer than 78 characters", () => { + const longId = "a".repeat(79) + expect(caip19TokenIdRegex.test(longId)).toBe(false) + }) +}) + +describe("caip19AssetIdRegex", () => { + it("matches a valid ERC-721 asset ID with token ID", () => { + expect( + caip19AssetIdRegex.test( + "eip155:1/erc721:0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/1234", + ), + ).toBe(true) + }) + + it("matches a valid asset ID on Arbitrum", () => { + expect( + caip19AssetIdRegex.test( + "eip155:42161/erc721:0xabc123def456abc123def456abc123def456abc1/99", + ), + ).toBe(true) + }) + + it("rejects an asset ID without a token ID", () => { + expect( + caip19AssetIdRegex.test( + "eip155:1/erc721:0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", + ), + ).toBe(false) + }) + + it("rejects an asset ID with an invalid chain namespace", () => { + expect( + caip19AssetIdRegex.test( + "XX:1/erc721:0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/1234", + ), + ).toBe(false) + }) +}) diff --git a/packages/caip/src/caips/caip-2.test.ts b/packages/caip/src/caips/caip-2.test.ts new file mode 100644 index 0000000..991b657 --- /dev/null +++ b/packages/caip/src/caips/caip-2.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it } from "vitest" + +import { + caip2ChainIdRegex, + caip2ChainIds, + caip2NamespaceRegex, + caip2Parts, + caip2ReferenceRegex, +} from "./index" + +describe("caip2Parts", () => { + it("parses a valid EIP-155 chain ID", () => { + const result = caip2Parts("eip155:1") + expect(result).toEqual({ namespace: "eip155", reference: "1" }) + }) + + it("parses a valid Solana chain ID", () => { + const result = caip2Parts("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp") + expect(result).toEqual({ + namespace: "solana", + reference: "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + }) + }) + + it("throws for an empty string", () => { + expect(() => caip2Parts("" as `${string}:${string}`)).toThrow( + "Invalid CAIP-2 chain ID", + ) + }) + + it("throws for a string without a colon", () => { + expect(() => caip2Parts("eip155" as `${string}:${string}`)).toThrow( + "Invalid CAIP-2 chain ID", + ) + }) + + it("throws when the reference is missing after the colon", () => { + expect(() => caip2Parts("eip155:" as `${string}:${string}`)).toThrow( + "Invalid CAIP-2 chain ID", + ) + }) +}) + +describe("caip2ChainIdRegex", () => { + it("matches a valid EIP-155 chain ID", () => { + expect(caip2ChainIdRegex.test("eip155:1")).toBe(true) + }) + + it("matches a valid Solana chain ID", () => { + expect( + caip2ChainIdRegex.test("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"), + ).toBe(true) + }) + + it("matches a chain ID with hyphens and underscores in reference", () => { + expect(caip2ChainIdRegex.test("abc:ref_with-chars")).toBe(true) + }) + + it("rejects a namespace shorter than 3 characters", () => { + expect(caip2ChainIdRegex.test("ab:1")).toBe(false) + }) + + it("rejects a namespace longer than 8 characters", () => { + expect(caip2ChainIdRegex.test("abcdefghi:1")).toBe(false) + }) + + it("rejects uppercase characters in namespace", () => { + expect(caip2ChainIdRegex.test("EIP155:1")).toBe(false) + }) + + it("rejects an empty reference", () => { + expect(caip2ChainIdRegex.test("eip155:")).toBe(false) + }) + + it("rejects a reference longer than 32 characters", () => { + const longRef = "a".repeat(33) + expect(caip2ChainIdRegex.test(`eip155:${longRef}`)).toBe(false) + }) + + it("rejects a chain ID without a colon", () => { + expect(caip2ChainIdRegex.test("eip1551")).toBe(false) + }) +}) + +describe("caip2NamespaceRegex", () => { + it("matches a valid namespace", () => { + expect(caip2NamespaceRegex.test("eip155")).toBe(true) + }) + + it("rejects a namespace with uppercase letters", () => { + expect(caip2NamespaceRegex.test("EIP155")).toBe(false) + }) +}) + +describe("caip2ReferenceRegex", () => { + it("matches a numeric reference", () => { + expect(caip2ReferenceRegex.test("1")).toBe(true) + }) + + it("matches an alphanumeric reference with hyphens and underscores", () => { + expect(caip2ReferenceRegex.test("my_ref-123")).toBe(true) + }) + + it("rejects an empty reference", () => { + expect(caip2ReferenceRegex.test("")).toBe(false) + }) +}) + +describe("caip2ChainIds", () => { + it("returns the correct Ethereum mainnet chain ID", () => { + expect(caip2ChainIds.ethereumMainnet).toBe("eip155:1") + }) + + it("returns the correct Base mainnet chain ID", () => { + expect(caip2ChainIds.baseMainnet).toBe("eip155:8453") + }) + + it("returns the correct Solana mainnet chain ID", () => { + expect(caip2ChainIds.solanaMainnet).toBe( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + ) + }) + + it("returns the correct Arbitrum Sepolia chain ID", () => { + expect(caip2ChainIds.arbitrumSepolia).toBe("eip155:421614") + }) +})