eip155:1), CAIP-10 names an account on that chain (eip155:1:0xAb16a9...), and CAIP-19 names an asset (eip155:1/slip44:60).
When such an identifier is folded into a canonicalised, content-addressed record, it stops being a display string and becomes part of the hash preimage. Two verifiers must agree on its exact bytes or their digests diverge and the record stops recomputing. So the question is not merely whether an identifier is valid. It must be byte-canonical.
That makes identifier validation a substrate concern rather than an application concern, and it is why it sits next to JCS canonicalisation in the same package.
The trailing-newline anchor trap
This is the failure that motivates the module, and it is a genuine cross-language divergence rather than a quirk of one runtime. Consider validating a CAIP-2 chain id with a regex anchored as^...$, then hashing the string:
A producer in one language accepts the trailing newline and hashes the identifier with the newline. A verifier in another language rejects it, or accepts the clean identifier and hashes without it. Both parties believe they validated correctly, yet the digests differ by one byte and the record fails to recompute.
This table is measured, not reasoned. The
caip_edge_v1 conformance set runs 102 adversarial identifiers through eight implementations across the seven languages above. All eight validate 102/102 under the correct anchors; under a naive ^...$ the over-acceptance counts are Python 3, Java 8, PHP 3, Ruby 5, and Go, Rust and JavaScript 0. Opting into line anchors reverts the last three to the same defect, so the fault is the anchor rather than the language. Full matrix with engine versions: seven-engine attestation.
The substrate therefore anchors with \A and \Z (Python) and without the multiline flag (TypeScript), which have no trailing-newline exception in any of the above:
Anchor discipline is load-bearing.
require_caip2("eip155:1\n") raises rather than returning a
value that would silently produce a non-reproducible digest. The strict require_* forms are the
pre-hash gate: they fail closed before a non-canonical identifier ever reaches the digest.Three validation tiers
The tiers are additive and opt-in. Chain-agnostic by default, strict where you ask for it.1. Grammar
is_caip2 / isCaip2The CAIP grammar only. A future or not-yet-registered chain still validates, so the substrate does not become a gatekeeper on which chains may exist.2. Registered namespace
is_registered_caip2 / isRegisteredCaip2Additionally requires a namespace registered in ChainAgnostic/namespaces, such as eip155, solana, cosmos, xrpl.3. Reference format
is_valid_caip2 / isValidCaip2Strictest. Additionally requires the chain reference to be well formed for its namespace, so eip155:abc is rejected because eip155 references are decimal.require_* counterpart (require_caip*, require_registered_caip*, require_valid_caip*) that returns the identifier unchanged or raises CaipError.
The namespace registry is vendored, not fetched at runtime, so validation stays offline and
deterministic. Note that a registry directory name is not always the namespace it declares: the
avalanche directory registers the namespace avax.Install
Usage
Grammar
Verbatim from CAIP-2, CAIP-10 and CAIP-19:What is validated
The identifier validators ship with the substrate test suite. Every claim below is reproducible from the tagged source:Cross-runtime
Full suite green on Python 3.10, 3.11, 3.12 and 3.13 and on Node 18, 20 and 22, run against the installed package rather than the source tree.
Differential
The regexes are checked against an independent regex-free oracle over a large fuzz corpus, so an anchor or character-class error surfaces as a disagreement rather than a silent pass.
Property-based
Structural generators build grammar-valid identifiers that must validate, and shrink any counterexample to a minimal failing input.
Non-catastrophic
Pathological inputs are checked for catastrophic backtracking, so a hostile identifier cannot stall a verifier.
Reproducible builds
The published artefacts are reproducible from the tagged source. An independent checkout ofv0.5.0 rebuilds the wheel, source distribution and npm tarball byte for byte:
.gitattributes, so the build does not depend on the checkout platform.
Licence and attribution
Apache-2.0. Every module carries anSPDX-License-Identifier: Apache-2.0 header, and the NOTICE file ships inside the wheel, the source distribution and the npm tarball.
When redistributing this work or derivative works, retain the NOTICE per Apache License, Version 2.0, Section 4(d). See Substrate licensing and attribution.
JCS Canonicalisation Substrate
The canonicalisation discipline these identifiers are hashed under.
Adopt the substrate
Anchor your own service or specification and be recorded in the registry.