Repository navigation
feat(ens): @libid/ens, the ENS name of a handle - #109
Open
SupremaLex wants to merge 19 commits into
Open
SupremaLex wants to merge 19 commits into
SupremaLex wants to merge 19 commits into
Conversation
ensName(platform, handle, { chain }) normalizes the handle with the
registry's rules from @libid/contracts and returns its name under
handles.link, per the ENS integration spec's REQ-ENS-LABEL-01 to -04:
GitHub unchanged, X with `_` as `-`, Gmail's local part split at dots,
other Google domains with an `_at` marker. A handle with no name gives
null. Tests cover each rule and check every name is ENSIP-15 normal
with @adraffy/ens-normalize.
Assisted-by: Claude Opus 5.5
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
One check now decides whether a piece of a name is a label: lowercase ASCII letters, digits and `-`, 1 to 63 bytes, and not `--` at the third and fourth characters. Handle labels, chain labels and the X substitution all go through it. - A Workspace local part or domain is split and checked piece by piece before `_at` is inserted, so `alice._at@company.com` and `alice@_at.company.com` no longer produce names the gateway refuses or that collide. - A Workspace piece with `--` at the third and fourth characters, including an IDN `xn--` domain, has no name, as an X handle with `__` there has none: ENSIP-15 rejects it. - A chain label is held to the same check, so a 64-byte label, a `--` label or a non-string (`chain: null`) throws instead of giving `alice.x..handles.link`. - `handleLabels` is no longer exported: it trusted its input to be normalized, and `ensName` always normalizes first. - The platform list comes from @libid/contracts' PLATFORM_*_KEY constants and the Platform type from it; the duplicate chain regex is gone. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`ensName` normalized with the rules @libid/contracts was released with and always put the name under handles.link. A chain's owner can change a platform's rules, and a deployment can answer under a subname such as testnet.handles.link. `options.rules` (for example from `rulesOf`) and `options.parent` now cover both; the defaults are unchanged. The parent name's labels pass the same check as every other label. The 63-byte label ceiling is now tested through a path production can take: rules whose Google length limit is raised past the released 62. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Ceremony pins @libid/contracts 0.15.1 exactly; ens asked for ^0.17.0, so an app with both installed two copies and a HandleError thrown by one failed `instanceof` against the other. Ens now pins the same 0.15.1 (its handle normalizer and rule table are byte-identical to 0.17.0's) and re-exports HandleError and the Rules type, so callers catch the class ens throws. Ens imports `@libid/contracts/identity` instead of the package root, which also loaded every ABI and call builder. viem stays a required peer: `identity` re-exports its node and resolve modules, both of which import viem at load, and @libid/contracts exports no viem-free subpath for the normalizer. Loading the built package without viem fails with ERR_MODULE_NOT_FOUND for viem from @libid/contracts/dist/identity/node.js. The README and RELEASING.md say so. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
PARENT_NAME was exported as a second statement of the Parent Name, which the spec defines and `options.parent` now defaults to. It is module-private, with its one default and a comment naming where the spec defines it. Platform keys and handle rules already come from @libid/contracts. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
vectors/names.json lists, for REQ-ENS-LABEL-01 to -05 and REQ-ENS-NAME-01 to -02: - names: platform, normalized handle, chain label and parent, and the name they give; - unnamed: normalized handles with no name, each with the reason; - nonNames: names that read back as no handle. The TS tests run every name forward through `ensName`, back through an inverse written from REQ-ENS-LABEL-05 alone, and through @adraffy/ens-normalize; they check unnamed handles give null and nonNames read back as nothing. The gateway's inverse in usernames-indexer (crates/usernames-core/src/ens.rs) should read the same file; it does not yet. The table stays out of the package tarball. index.test.ts keeps only what the table cannot hold: raw input, options and errors. The tests read the table under Node, so the package's tsconfig includes the Node types; the build's sets none. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The README and the source header linked specs/ens-integration.md by a relative path that is not on main and is dead on npm. Both now link the spec on the docs/ens-integration branch at a pinned commit. The README lists every refusal, including the Workspace `--` and 63-byte cases, and points at the vector table. RELEASING.md said "all three packages" and listed three first publications; it now counts four and publishes ens.tgz too. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The consumer installed ens's tarball but never imported it, so its packed declarations were neither typechecked nor bundled. The browser entry now re-exports ens's public API, so both TypeScript versions check it and vite bundles it with the viem it requires as a peer. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
SupremaLex
added a commit
that referenced
this pull request
Oct 5, 2026
The current deployment holds the Handle Resolver's owner, the deployer and the Parent Name's owner as one key, so it does not meet REQ-ENS-KEY-01; Security Considerations say so. Provenance records the forward transform in @libid/ens (libID PR #109, unmerged) and what it lacks, the unimplemented SHOULD of REQ-ENS-LABEL-05, where REQ-ENS-RES-05 is enforced, and the new REQ-ENS-GW-11. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
That revision has the Workspace refusal and the Parent Name parameter the package implements. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The gateway reads every chain's names with the handle rules @libid/contracts was released with, so a name built with other rules could be one the gateway reads as a different handle or none. The `rules` option is gone, along with the README's rulesOf example and the `Rules` re-export; `ensName` always normalizes with `rulesFor`. The 63-byte ceiling stays tested through the paths that reach it, chain and parent labels. - `ensName(p, h, null)` reads null options as none instead of throwing a TypeError. - The platform switch uses the PLATFORM_*_KEY constants. - The unreachable "no rules" branch is gone: every Platform has released rules. - The ENSIP-15 block that never called `ensName` is gone; the vector test runs every name through ENSIP-15. - The README lists Gmail's empty pieces (`.alice@`, `alice.@`, `a..b@`) as having no name, says the gateway does not read the vector table yet, and states that a chain label naming a subname with its own deployment (`testnet`) is answered by that deployment, which `ensName` cannot see. BREAKING CHANGE: NameOptions no longer has `rules`, and `Rules` is no longer exported. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- Every handle in the vector table must already be what the released rules normalize it to, so a vector cannot test input the transform never receives. - Gmail local parts with an empty piece (`.alice@`, `alice.@`, `a..b@`) join the handles with no name. - The ENSIP-15 check uses `normalize` from viem/ens, which is @adraffy/ens-normalize, so that devDependency is gone. - The test header says plainly that the inverse is this package's own and the gateway does not read the file yet. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Ceremony and ens each pinned @libid/contracts 0.15.1 by hand, so a bump to one could leave two copies and two HandleError classes. Both now take `catalog:`, and pnpm-workspace.yaml holds the one exact version. pnpm pack writes 0.15.1 into both packed manifests, which the package check already requires. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The consumer has viem because ens requires it as a peer, so it can no longer show that an app without ens needs no viem. A second consumer installs the ledger, popup and ceremony tarballs alone, asserts viem was not installed, and imports every runtime entry under Node. A module of theirs that starts importing viem, such as @libid/contracts/identity, fails it with ERR_MODULE_NOT_FOUND. The ens consumer drops the `Rules` re-export ens no longer has. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The README linked vectors/names.json on main, where it does not exist until this branch merges, and where it can change under the link. It now links the commit that last changed the table. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
isPlatform replaces the two spellings of the platform-key test, the parent name reuses pieces, the label regex is a module constant, and a Gmail local part is its pieces with no `-` rather than a second regex. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The npm consumer is installed first without ens, where ledger, popup and ceremony must load with no viem, then again with ens for the type and bundle checks. This drops the second fixture directory and its manifest. The contracts catalog entry moves above the playwright comment that describes the entry after it. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Drop the forward-looking notes about the gateway's inverse and reflow a broken paragraph in RELEASING.md. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A new package,
@libid/ens: the ENS name of a libID handle, for applications that want to show users the name they can paste into a bio or a wallet.What it does
@libid/contracts. Those are the rules the gateway applies on every chain, so a name it builds is one the gateway reads back._becomes-._at, then the domain.--at the third and fourth characters, includingxn--domains;+,-or_;_atas a piece;nullmeans the handle has no name. Text that is not a handle on the platform throwsHandleError, which is re-exported.chainnarrows the name to one chain.parent(defaulthandles.link) targets a subname deployment such astestnet.handles.link.@libid/contracts.@libid/contractsis pinned once, in the pnpm catalog that ceremony also uses.@libid/contracts/identityloads viem. libID-contracts popup: mobile-WebKit flake in close-right-after-navigate (CI only) #86 adds a viem-free@libid/contracts/handle, which lets this package drop viem once released.Checks
nulloptions;vectors/names.json: names checked forward throughensNameand back through a test-only inverse written from REQ-ENS-LABEL-05;normalize.Name::queryin usernames-indexer). The gateway does not readvectors/names.jsonyet.test:packages, which imports@libid/ensin the packed-package consumer;@libid/ens, to check that ledger, popup and ceremony load without viem. An injected viem import in ceremony makes this check fail.Known gaps
ab--cd.x, where wallets refuse first under ENSIP-15, and_at.gmail.com. docs(specs): specify the ENS integration #93 records this as a SHOULD.4d77835; it needs re-pinning if docs(specs): specify the ENS integration #93 changes.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ar21wPcHzNprswTzLJd3MA