Repository navigation
feat(sol): LibID, a Solidity library for using libID from contracts - #71
Open
SupremaLex wants to merge 22 commits into
Open
SupremaLex wants to merge 22 commits into
SupremaLex wants to merge 22 commits into
Conversation
LibID has the canonical IdentityRegistry and HandleEscrow addresses built in and wraps the calls a contract needs: resolve a handle or an id (with an optional maximum proof age), read a published handle, check or require that the caller holds a handle, and pay a handle in ETH or an ERC-20 through the escrow. Every function is internal and the file imports nothing. On a chain without libID, calls revert LibIDUnavailable. The tests place the real libID-contracts v0.15.0 proxies at the built-in addresses, check those addresses against the factory's CREATE3 derivation of their canonical names, and read the live Eden deployment in a fork test. CI gets a Solidity job. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
isAvailable now needs only the IdentityRegistry, since reads and gates work without the escrow; isEscrowAvailable covers payments. The token notes say what the escrow supports: a fee taken from the amount received works, while a sender surcharge, a rebasing token or one that can block the escrow does not. refund lets a contract named as refundTo take its deposits back. The library and README say that payments call out, so callers must guard against reentrancy. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…yments The verifiers date a proof a fixed allowance before its evidence (5 minutes on GitHub and X, two hours before a Google token's expiry), so observedAt is never ahead of the block and a fresh proof already has an age. The docs say so, and the tests bind with that timing, using the allowances from CeremonyProfile, instead of a time the verifiers never produce. requireHolder no longer lets the zero address in for a handle nobody holds. pay and payToken return the escrow node, and refund takes it, so a refund still reaches the deposit after the platform's rules change. payToken approves the amount first and resets to zero only if the token refuses, so tokens that reject a zero approval work, and an approve that answers anything but true is ApproveFailed. isEscrowAvailable also requires the escrow to resolve through REGISTRY. The handle check costs two registry calls instead of three, letting only UnusableHandle read as unheld. CI compiles LibID.sol alone with solc 0.8.20 on the legacy pipeline. 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>
The verifiers accept a session up to an hour old and a Google token until it expires, so a just-bound proof reads 5 to 65 minutes old on GitHub and X and 1 to 2 hours on Google; the docs give that range, and the tests bind at both ends of each window. The age-checking reads again revert UnknownPlatform for a platform with rules but no verifier, by calling resolveHandle whenever the answer is unheld. bindingOf returns the holder and observedAt so callers can tell unheld, stale and no-handle apart. The escrow node is computed locally from its fixed formula. isEscrowAvailable compares the answer as a word, so a dirty answer is false instead of a revert. approve accepts a first word of 1, as SafeERC20 does, and copies only that word. The docs say that Gmail dots and plus tags are kept and that publishedHandleOf does not revert. CI lints with forge lint -D notes, and the Eden fork step fails instead of skipping when its RPC is missing. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
SupremaLex
marked this pull request as ready for review
October 5, 2026 09:50
Main dropped the infrastructure smoke job; the workflow summary names the Solidity checks without it. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
🚀 Deploying Preview to Cloudflare 🚀Preview URL: https://feat-solidity-lib.previews.lib.id, https://feat-solidity-lib-libid.grounded-systems.workers.dev (commit 6be51e6)This URL reflects your latest Preview deploymentPreview Deployments by commit
|
libid-contracts 0.17.0 gives each environment its own factory, so the canonical addresses differ between production and testnets. LibID now carries Ethereum mainnet's, and LibIDTestnet, generated from it by script/testnet.sh, carries Sepolia's and Eden's with the same functions: an integrator switches by importing it as LibID. CI checks the generated file, builds both with solc 0.8.20, checks both address tables against their factories, and forks mainnet and Sepolia instead of Eden. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
pay and payToken returned a node from LibID's own copy of the node formula, while the escrow books the deposit at the registry's handleNodeOfHash. Both now come from the registry. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…n failure testnet.sh renamed only ILibIDRegistry and ILibIDEscrow, so a new file-level declaration would clash when LibID and LibIDTestnet are imported together. It now renames every file-level ILibID* name and stops on any other file-level name or line it does not handle. It also truncated src/LibIDTestnet.sol before perl ran. It now writes to a temporary file and moves it into place once formatted. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
LibID's addresses are constants, so a local chain where libID is deployed elsewhere cannot run a contract that uses it. LibIDTestBase puts the real IdentityRegistry and HandleEscrow at LibID's or LibIDTestnet's addresses and binds handles through a stand-in platform verifier. LibID's own tests now set up through it. It imports libID-contracts, so the README gives the remappings and the IR setting it needs. Checked from a scratch Foundry project that imports it through the documented remappings. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The README said a just-bound GitHub or X proof reads 5 to 65 minutes old, and a test treated a 4-minute maxAge as always too short. The verifier sets observedAt to createdAt less 300 s and accepts createdAt up to 300 s ahead of the block, so the age can be 0. The same holds for Google: exp may be up to 2 hours ahead, which also gives 0. The README now gives 0 as the floor and the 5-minute and 1-hour ages as what in-step clocks give. The tests bind at both ends of each verifier's window, from CeremonyProfile's constants. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Covers payToken with refundTo set to the paying contract, then LibID.refund: a plain token, a token that takes a fee on the way into the escrow, and one that takes a fee on the way out. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…d name
The fork tests asserted that "nobody-has-this-handle-xyz" is unbound on
live chains, which anyone can change by binding that GitHub name. They
now read text the registry refuses for GitHub ("not a handle"), which
nobody can ever hold.
They also only read. Each fork now pays a GitHub handle that is new to
the run, in ETH and in a token, checks the escrow books it at the
registry's node, and refunds it. Mainnet runs through LibID, Sepolia
through LibIDTestnet. Everything happens on the local fork.
Assisted-by: Claude Opus 5.5
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The README lists Eden testnet for LibIDTestnet, but no test ran there. Eden now runs the same fork tests as Sepolia, and CI gives it a public RPC under the same LIBID_REQUIRE_FORK rule. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The sol job ran on every pull request and depends on public RPCs. The changes job now has a sol output, true for pushes to main and for pull requests that touch sol/, .gitmodules or ci.yml, and the job skips otherwise. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The libID contracts compile through IR without the optimizer, and a profile-wide optimizer setting would reach the integrator's own contracts. Checked from a scratch Foundry project. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
pay and payToken took the node from LibID's registry, while the escrow books the deposit at the node its own registry gives. They now revert LibIDUnavailable unless isEscrowAvailable, which checks the two are the same. refund needs no registry and still works. The NatSpec of pay and payToken now lists the escrow's reverts. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Forge writes it to pin each dependency's revision, matching the submodule commits. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…wn verifier From the second review of LibIDTestBase: - bindHandle(holder, platformId, id, handle) dates the proof as one made now with in-step clocks would be: 5 minutes old on GitHub and X, an hour on Google, from CeremonyProfile's constants. It moves the clock a second first, so binds in one block are each newer, and past the proof's age when a test starts near zero. - deployLibIDAt uses a libID deployment already at the addresses, as on a fork or after an earlier call, and reverts with a message for other code there. Stand-in verifiers go under version 65535, beside any live ones; rules are set only for a platform that has none. - Every function stops the test's prank or startPrank and restores it afterwards, and refuses to run during a broadcast. - LibIDStandInVerifier, kept here, replaces libID-contracts' test fixture StubPlatformVerifier. - Private helpers carry a libid prefix, the proxy slot comes from ERC1967Utils, and LibIDTest uses the base's registry and escrow. New tests cover deployLibID, deployLibIDTestnet, both bindHandle forms, a second deploy, foreign code and pranks. The fork tests now bind a handle through the base on each live deployment and pay its holder, and read an unheld handle that the registry accepts, so _binding's success path runs against live contracts. The README's remappings now apply only under lib/libid/, and it says that tests build the integrator's contracts a second time, through IR. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
integrator/ is a Foundry project that uses LibID and LibIDTestBase with exactly the README's remappings and foundry.toml lines, on solc 0.8.24, the oldest the base supports. It maps its own @OpenZeppelin elsewhere, to show the context-specific remappings keep libID's apart. script/integrator.sh runs it with this repository at lib/libid, and the sol job runs that script. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The fork tests read and pay through the live deployments, which can change without a commit here. A nightly schedule runs the sol job alone: changes reports no workspaces and no image, and ts and dco skip. Nightly runs have their own concurrency group, so one never cancels a push's run of the same commit. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The Sepolia and Eden fork suites differ only in their RPC variable, so an abstract TestnetForkTest carries the consumer and deploy entry point. The fork suites check an unheld handle through one helper, and both not-set-up tests run the same full list of reads and gates, which now also covers bindingOf, requireHolder and publishedHandleOf on a platform with no rules. Consumer implements IConsumer, so the mainnet suite needs no cast, and the ETH-refusing holder is libID-contracts' RejectEther. 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.
LibID, a Solidity library for using libID from contracts. It lives insol/so the contracts repo does not have to know its own deployed addresses.What it is
sol/src/LibID.sol. It imports nothing, and every function isinternal, so there is nothing to deploy or link. It works with solc 0.8.20 and later.LibIDhas the production addresses (Ethereum mainnet:IdentityRegistry0xbefD…9a83,HandleEscrow0x17a2…bbf7).LibIDTestnethas the same functions with the testnet addresses (Sepolia and Eden:0x25F2…D640,0x5735…d8e5). It is generated fromLibID.solbysol/script/testnet.sh, so an integrator switches withimport {LibIDTestnet as LibID} from "libid/LibIDTestnet.sol";. Within an environment the addresses never change; contract updates are upgrades at the same address.resolve, with or without a maximum proof age;bindingOf, which returns the holder andobservedAt;resolveIdandpublishedHandleOf.isHolderandrequireHolder, which revertNotHolder(holder, caller)orProofTooOld(observedAt).HandleEscrow:pay(ETH) andpayToken(ERC-20) both return the escrow node, which comes from the registry'shandleNodeOfHash; both revertLibIDUnavailableunless the escrow resolves through LibID's registry;refund(handleNode, …)takes that node back.isAvailable()covers the registry, which is enough for reads and gates;isEscrowAvailable()covers payments, and also checks that the escrow resolves through that registry.LibIDUnavailableon a chain without the contract it needs.Behaviour to know
observedAta fixed allowance before the evidence, and accept evidence that is already a while old. The NatSpec and README give these ranges.@is dropped on GitHub and X. Google handles keep dots and+tags, and a leading@is invalid there.payTokenapproves the amount first and resets to zero only if the token refuses. It reads only the first word of the answer.Tests (
sol/test/LibID.t.sol)vm.etch, and handles are bound through the realbind. Bindings are dated as the real verifiers date them, using the allowances fromCeremonyProfile, at both ends of each acceptance window.handleNodeOfHash, an escrow bound to another registry, token refunds (plain and both fee-on-transfer kinds), both ends of each verifier's age window, and the test base below.LibIDTestBase(sol/src/test/LibIDTestBase.sol): a Foundry base for integrators testing their own contracts.prank.bindHandledates proofs at the platform's real age (5 minutes for GitHub and X, 1 hour for Google) and advances the clock, so binds in one block do not collide.sol/integrator/is a project built from exactly those lines on solc 0.8.24, and CI builds and tests it.LibID), Sepolia and Eden (LibIDTestnet). Each reads, then pays a fresh handle in ETH and in a token and refunds it, and binds throughLibIDTestBaseon the live deployment, on the local fork only. They are skipped locally withoutETH_RPC_URL,SEPOLIA_RPC_URLandEDEN_RPC_URL. In CI,LIBID_REQUIRE_FORKmakes a missing RPC fail the step.sol/lib/, pinned in the committedfoundry.lock.LibIDTestBaseimports libID-contracts;LibID.solimports nothing.CI
A new
soljob runs on pushes to main, nightly (to catch drift in the live deployments), and on pull requests that touchsol/,.gitmodulesor the CI workflow:forge fmt --check;forge lint -D notes;script/testnet.sh, failing ifLibIDTestnet.solchanges;LibID.solandLibIDTestnet.sol;forge test;script/integrator.sh).Known limitations
forge install libid-org/libidpulls the whole repo and the test-only submodules for one file.handleBindingOf, which makes it one, once deployed.LibIDTestBaseplaces the contracts at the built-in ones.