Smart contracts for libID, laid out per chain. solidity/ is a self-contained
Foundry project holding the EVM contracts: the ceremony verification path
(Notary Service, Proof Verifier, Platform Verifiers, and the Google JWT root
list the Google verifier reads), the identity naming system, and the
deterministic deployment factory. rust/ and ts/ hold the ABI wrapper packages; room is reserved
for solana/ and other networks.
solidity/ # Foundry project root
contracts/
ceremony/ # NotaryService, CeremonyProofVerifier, Platform Verifiers,
# GoogleJwtRoots
circuits/ # the UltraHonk verifiers the Platform Verifiers pin:
# circuits.json pins a libid-circuits release, the
# Solidity is vendored from it and not committed
identity/ # IdentityNames, handle normalization
factory/ # LibidFactory: deterministic CREATE3 deployment
WTIA9.sol # wrapped TIA
script/Deploy.s.sol
lib/ # git submodules (openzeppelin, forge-std)
rust/contracts/ # libid-contracts crate: alloy bindings + embedded artifacts
ts/packages/contracts/ # @libid/contracts: viem ABIs, call builders, identity helpers
scripts/
vendor-artifacts.sh
vendor-circuit-verifiers.sh
regen-identity-handles.py
git submodule update --init --recursive
scripts/vendor-circuit-verifiers.sh # -> solidity/contracts/circuits/*HonkVerifier.sol
cd solidity
forge build
forge testNothing generated is committed. The Honk verifiers are downloaded from the
pinned libid-circuits release
(see Circuit verifiers), so a fresh clone vendors them
before its first forge build, which needs curl, jq, tar and forge. forge build is in turn the input to the two generated trees. Generate them once
after cloning, and again after any change to a contract they cover:
scripts/vendor-artifacts.sh # -> rust/contracts/artifacts (the crate embeds
# this with include_dir!, so cargo commands
# fail at macro expansion without it)
pnpm -C ts codegen # -> ts/packages/contracts/src/abis (tsc reads
# these, so `pnpm -C ts build` needs them)CI runs all three before every build, test, dry-run and publish: the forge-build action vendors the verifiers before it builds, and every job that compiles the crate or the package regenerates its tree — the published crate and npm package carry the generated output even though git does not.
solidity/contracts/identity/handles.json is the source of truth for platform
handle rules and the shared normalization vector table. After editing it:
python3 scripts/regen-identity-handles.py # rewrite generated outputs
python3 scripts/regen-identity-handles.py --check # verify nothing driftedThis generates solidity/contracts/identity/HandleVectors.sol,
rust/identity/src/handle_vectors.rs and
ts/packages/contracts/src/identity/handleVectors.ts; CI's handle-tables job
fails when any of them drifts from handles.json.
The ceremony circuits' UltraHonk verifiers are not written here. bb derives
each from its circuit's verification key, and
libid-circuits runs bb and
ships the Solidity in its release tarballs. scripts/vendor-circuit-verifiers.sh
downloads it into solidity/contracts/circuits/, formatted, where forge build compiles it and the crate embeds it, so no consumer runs bb. The
files are gitignored: they are another repository's release asset, and the
pin says which bytes they must be.
solidity/contracts/circuits/circuits.json is the pin — the release version
and each tarball's sha256, committed here and checked against every download.
To move it, download the new release's tarballs, take their digests with
shasum -a 256, write the version and the digests into circuits.json, then:
scripts/vendor-circuit-verifiers.sh # rewrite the verifiers from the pinCI's forge-build action runs the same script before every build, test, dry-run and publish, refusing any tarball whose digest is not the pin's; a release cannot ship a verifier that is not what the pinned circuits release shipped.
The Rust crate (libid-contracts)
and the npm package
(@libid/contracts) release
together under a single version number. A release is cut by publishing a
GitHub Release tagged v<version>; nothing publishes from pushes or PRs.
./scripts/bump-version.sh 0.2.0 # sets Cargo.toml, Cargo.lock, package.json
git checkout -b release/v0.2.0
git commit -sam "chore: release v0.2.0"
# open a PR, get it merged, then:
gh release create v0.2.0 --title "v0.2.0" --generate-notesPublishing the release triggers CI's release jobs:
verify-tag— the tag must equal the version in both manifests (the tag is a pointer, never a source; theversionsjob also enforces crate/npm equality on every PR).publish-crates— after the Solidity, Rust and publish dry-run jobs pass,cargo publishwithCARGO_REGISTRY_TOKEN. If the version is already on crates.io (a re-run after a partial release), it skips with a notice.publish-npm— afterpublish-crates, builds and publishes@libid/contractsvia npm OIDC trusted publishing (no token secret), with provenance. A prerelease publishes under its first prerelease identifier as the dist-tag (1.2.0-rc.1→rc); a plain version underlatest.
Dual-licensed under MIT and Apache-2.0; see LICENSE-MIT, LICENSE-APACHE
and CONTRIBUTING.md.