> ## Documentation Index
> Fetch the complete documentation index at: https://docs.algovoi.co.uk/llms.txt
> Use this file to discover all available pages before exploring further.

# CAIP Chain-Agnostic Identifiers

> Byte canonical validation of CAIP-2 chain ids, CAIP-10 account ids and CAIP-19 asset ids in the AlgoVoi substrate. Three opt in tiers, two languages.

A receipt that names a chain, an account or an asset carries a **CAIP identifier**. CAIP-2 names the chain (`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](/canonicalisation-substrate) 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:

```
"eip155:1\n"
```

Whether that input is accepted **depends on the language the verifier is written in**:

| Behaviour of `$` (no multiline flag)                                           | Languages                                      |
| ------------------------------------------------------------------------------ | ---------------------------------------------- |
| Also matches just before a trailing newline, so `"eip155:1\n"` is **accepted** | Python, Java, PHP (PCRE), Ruby (line-anchored) |
| Matches only at end of input, so `"eip155:1\n"` is **rejected**                | Go (RE2), Rust, JavaScript                     |

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`](https://github.com/chopmob-cloud/algovoi-jcs-conformance-vectors/tree/main/vectors/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](https://github.com/chopmob-cloud/algovoi-jcs-conformance-vectors/blob/main/_attestations/2026-07-21-caip-edge-v1-seven-engine.md).

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:

<Note>
  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.
</Note>

## Three validation tiers

The tiers are additive and **opt-in**. Chain-agnostic by default, strict where you ask for it.

<CardGroup cols={3}>
  <Card title="1. Grammar" icon="code">
    `is_caip2` / `isCaip2`

    The 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.
  </Card>

  <Card title="2. Registered namespace" icon="list-check">
    `is_registered_caip2` / `isRegisteredCaip2`

    Additionally requires a namespace registered in `ChainAgnostic/namespaces`, such as `eip155`, `solana`, `cosmos`, `xrpl`.
  </Card>

  <Card title="3. Reference format" icon="shield-check">
    `is_valid_caip2` / `isValidCaip2`

    Strictest. Additionally requires the chain reference to be well formed for its namespace, so `eip155:abc` is rejected because `eip155` references are decimal.
  </Card>
</CardGroup>

Each tier has a `require_*` counterpart (`require_caip*`, `require_registered_caip*`, `require_valid_caip*`) that returns the identifier unchanged or raises `CaipError`.

<Note>
  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`.
</Note>

## Install

<CodeGroup>
  ```bash Python theme={null}
  pip install algovoi-substrate
  ```

  ```bash TypeScript theme={null}
  npm install @algovoi/substrate
  ```
</CodeGroup>

## Usage

<CodeGroup>
  ```python Python theme={null}
  from algovoi_substrate import (
      is_caip2, is_caip10, is_caip19,
      require_caip2, caip10_of, caip19_slip44,
      is_registered_caip2, is_valid_caip2, CaipError,
  )

  is_caip2("eip155:1")                  # True
  is_caip2("eip155:1\n")                # False  (anchor discipline)
  is_caip10("eip155:1:0xAb16a9...")     # True
  is_caip19("eip155:1/slip44:60")       # True

  # constructors are gated on the way in and on the way out
  caip10_of("eip155:1", "0xAb16a9...")  # "eip155:1:0xAb16a9..."
  caip19_slip44("eip155:1", 60)         # "eip155:1/slip44:60"

  # tier 2 and tier 3
  is_registered_caip2("zzz:1")          # False (namespace not registered)
  is_valid_caip2("eip155:abc")          # False (eip155 references are decimal)

  require_caip2("eip155:1\n")           # raises CaipError
  ```

  ```typescript TypeScript theme={null}
  import {
    isCaip2, isCaip10, isCaip19,
    requireCaip2, caip10Of, caip19Slip44,
    isRegisteredCaip2, isValidCaip2, CaipError,
  } from '@algovoi/substrate';

  isCaip2('eip155:1');                  // true
  isCaip2('eip155:1\n');                // false  (anchor discipline)
  isCaip10('eip155:1:0xAb16a9...');     // true
  isCaip19('eip155:1/slip44:60');       // true

  // constructors are gated on the way in and on the way out
  caip10Of('eip155:1', '0xAb16a9...');  // 'eip155:1:0xAb16a9...'
  caip19Slip44('eip155:1', 60);         // 'eip155:1/slip44:60'

  // tier 2 and tier 3
  isRegisteredCaip2('zzz:1');           // false (namespace not registered)
  isValidCaip2('eip155:abc');           // false (eip155 references are decimal)

  requireCaip2('eip155:1\n');           // throws CaipError
  ```
</CodeGroup>

## Grammar

Verbatim from CAIP-2, CAIP-10 and CAIP-19:

```
chain_id    = namespace ":" reference    namespace [-a-z0-9]{3,8}   reference [-_a-zA-Z0-9]{1,32}
account_id  = chain_id ":" address       address   [-.%a-zA-Z0-9]{1,128}
asset_type  = chain_id "/" ns ":" ref    asset ns  [-a-z0-9]{3,8}   asset ref [-.%a-zA-Z0-9]{1,128}
asset_id    = asset_type "/" token_id    token_id  [-.%a-zA-Z0-9]{1,78}
```

The Python implementation uses only the standard library, so the tier adds no dependency to the substrate.

## What is validated

The identifier validators ship with the substrate test suite. Every claim below is reproducible from the tagged source:

```bash theme={null}
git clone https://github.com/chopmob-cloud/algovoi-substrate
cd algovoi-substrate/python && git checkout v0.5.0
pip install -e ".[test]" && pytest
```

<CardGroup cols={2}>
  <Card title="Cross-runtime" icon="layer-group">
    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.
  </Card>

  <Card title="Differential" icon="scale-balanced">
    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.
  </Card>

  <Card title="Property-based" icon="dice">
    Structural generators build grammar-valid identifiers that must validate, and shrink any counterexample to a minimal failing input.
  </Card>

  <Card title="Non-catastrophic" icon="gauge-high">
    Pathological inputs are checked for catastrophic backtracking, so a hostile identifier cannot stall a verifier.
  </Card>
</CardGroup>

The Python and TypeScript implementations hand-maintain the same grammar and the same namespace registry in separate files. A parity test parses the TypeScript source and asserts its constants match Python's, so copy drift fails the suite rather than shipping as a silent cross-language disagreement.

## Reproducible builds

The published artefacts are reproducible from the tagged source. An independent checkout of `v0.5.0` rebuilds the wheel, source distribution and npm tarball byte for byte:

```bash theme={null}
git clone https://github.com/chopmob-cloud/algovoi-substrate
cd algovoi-substrate && git checkout v0.5.0
SOURCE_DATE_EPOCH=1735689600 python -m build   # from python/
npm install && npm run build && npm pack        # from typescript/
```

Line endings are pinned to LF in `.gitattributes`, so the build does not depend on the checkout platform.

## Licence and attribution

Apache-2.0. Every module carries an `SPDX-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](/substrate-licensing-and-attribution).

<CardGroup cols={2}>
  <Card title="JCS Canonicalisation Substrate" icon="fingerprint" href="/canonicalisation-substrate">
    The canonicalisation discipline these identifiers are hashed under.
  </Card>

  <Card title="Adopt the substrate" icon="handshake" href="/adopters">
    Anchor your own service or specification and be recorded in the registry.
  </Card>
</CardGroup>
