Version: 0.2.2-beta
Status: Beta - Deployed on Sepolia
ECS V2 is a decentralized registry of Smart Credentials—verifiable onchain or offchain data about any identity. Smart Credentials are implemented as Extended Resolvers. ECS supports both public and privacy-preserving credentials using Zero Knowledge Proofs (ZKPs), enabling users to prove attributes without revealing underlying data.
Entities can register a unique namespace (e.g., my-service.ecs.eth) and point it to their Smart Credential (Extended Resolver). ECS is built to be fully compatible with the ENS Hooks standard, enabling ENS names to securely resolve Smart Credentials.
npm install @nxt3d/ecsjs@0.2.4-betaVersion: 0.2.4-beta - View on NPM
Important: ECS V1 is deprecated and incompatible with V2.
Note: This package includes viem as a dependency, so you don't need to install it separately.
- Simplicity: The complex multi-level registry has been replaced with a flat, single-label registry. Labels (e.g.,
optimism) map directly to Smart Credentials. - Standard Extended Resolvers: Smart Credentials are implemented as standard ENSIP-10 (Extended Resolver) contracts. This means any existing ENS tooling can interact with them.
- Flexible Data: Smart Credential providers can define their own schema and keys. There's no forced structure for credential data.
- Hooks Integration: ECS serves as the registry for Hooks. Hooks in ENS text records can reference registered Smart Credentials.
ecs.ethResolution: Smart Credentials are resolved through service subdomains (e.g.,my-service.ecs.eth). Queries use specific keys (e.g.,eth.ecs.my-service.credential:vitalik.eth) to fetch data for a target identity.
The core contract. It maintains a mapping of:
LabelHash -> (Owner, Expiration)
- ENS Integration: When a label is registered, ECS automatically creates the corresponding subname (e.g.,
my-service.ecs.eth) in the official ENS Registry. - Smart Credential Management: The Smart Credential address is stored directly on the ENS Registry record for that subname.
- Ownership: ECS retains ownership of the ENS subnode to ensure protocol rules, while the logical owner manages the record via the ECS Registry.
- Commit/Reveal: Secure updates using a commitment pattern to prevent front-running.
A factory contract for deploying gas-efficient CredentialResolver clones:
- Minimal Proxy Pattern: Uses EIP-1167 for ultra-low-cost deployments (~160k gas vs ~2.1M gas for full deployment)
- Clone Management: Tracks all deployed clones and provides query functions
- Deterministic Deployment: Supports deterministic address generation for predictable deployments
- Initialization: Automatically initializes clones with owner address
- Validation: Prevents deployment with zero address owners
Smart Credentials are Extended Resolver contracts that are built and registered. The default implementation includes:
- Standard ENS Extended Resolvers: Implements standard ENS methods (
text,addr,contenthash,data) and works with any ENS client - ERC-8049 Integration: Uses ERC-8049 for contract-level metadata storage via
setContractMetadata()andgetContractMetadata() - ENS
data()Support: Thedata()ENS resolver function delegates to ERC-8049 storage for standardized metadata access - Ownable Pattern: Uses standard
Ownable(not upgradeable) for minimal clones, with initialization guards - Gas-Efficient Clones: Deployed as minimal proxies (EIP-1167) with independent storage per clone
- Interface Support: Implements
IERC165,IExtendedResolver, andIERC8049
Smart Credentials can be:
- Onchain Smart Credentials: Storing attestation data directly on Ethereum
- Offchain/L2 Smart Credentials: Using CCIP-Read to fetch data from Optimism, Base, or a centralized server, verified by signatures or proofs
- Privacy-Preserving Smart Credentials: Supporting Zero Knowledge Proofs (ZKPs) to prove attributes without revealing underlying data
- Specialized Smart Credentials: Like CCResolver for controlled accounts verification via ERC-8092, demonstrating the flexibility of the ECS system
Hooks enable ENS names to redirect queries to registered Smart Credentials.
- User sets a text record on their ENS name (e.g.,
maria.eth) containing a Hook:hook("text(bytes32,string)", <SMART_CREDENTIAL_ADDRESS>) - Client reads this record and extracts the
<SMART_CREDENTIAL_ADDRESS>. - Client calls
getResolverInfo(<SMART_CREDENTIAL_ADDRESS>)on the ECS Registry to:- Find its registered label (e.g.,
my-service) - Check the
resolverUpdatedtimestamp to verify Smart Credential stability - Read the
reviewfield for admin-assigned ratings or certifications - Make a trust decision based on Smart Credential age and review status
- Find its registered label (e.g.,
- Client constructs the service name
my-service.ecs.eth(optional, for provenance). - Client queries the Smart Credential directly:
text(node, "credential-key").- Note: Single-label Smart Credentials ignore the
nodeparameter, so any value (including0x0) works.
- Note: Single-label Smart Credentials ignore the
- Smart Credential returns the verified credential data.
This creates a trusted link to the record, where maria.eth doesn't store the record herself; instead, the data is resolved from a trusted registered Smart Credential.
import {
createECSClient,
sepolia,
getResolverInfo,
resolveCredential
} from '@nxt3d/ecsjs'
const client = createECSClient({
chain: sepolia,
rpcUrl: 'https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY'
})
// User has hook pointing to Smart Credential
const smartCredentialAddress = '0x48A3D8Cec7807eDB1ba78878c356B3D051278891'
const credentialKey = 'eth.ecs.name-stars.starts:vitalik.eth'
// Get label and resolve credential in one call
const credential = await resolveCredential(client, smartCredentialAddress, credentialKey)
// Returns: "100"
// Or get Smart Credential info
const { label, resolverUpdated, review } = await getResolverInfo(client, smartCredentialAddress)
// Returns: { label: "name-stars", resolverUpdated: 1234567890n, review: "" }
// You can also use viem's ENS functions directly
const ensName = `${label}.ecs.eth`
const textValue = await client.getEnsText({
name: ensName,
key: credentialKey
})
// Returns: "100"ECS strictly enforces a one-to-one relationship between labels and Smart Credentials. While label owners can change Smart Credentials (necessary for upgrades), this introduces a security concern. The registry tracks resolverUpdated timestamps, allowing clients to enforce security policies based on Smart Credential age.
Security-conscious clients can require Smart Credentials to be established (e.g., 90+ days old) before trusting them. Recent changes may indicate compromise, untested deployments, or migrations requiring review.
const { label, resolverUpdated, review } = await getResolverInfo(client, resolverAddress)
const credentialAge = Math.floor(Date.now() / 1000) - Number(resolverUpdated)
if (credentialAge < 90 * 24 * 60 * 60) { // 90 days for high security
console.warn(`⚠️ Smart Credential "${label}" changed ${Math.floor(credentialAge / 86400)} days ago`)
// Reject or require security review
}
// Check admin review status
if (review && review !== "verified") {
console.warn(`⚠️ Smart Credential "${label}" review status: ${review}`)
}ECS registry administrators can assign review strings to Smart Credentials to indicate trust levels, certification status, or security assessments. For example:
Status: Verified, Audit Score: 85/100, Date: 2025-04-21
This enables the ECS protocol to curate and communicate the quality or trustworthiness of Smart Credentials, helping clients make informed trust decisions beyond just age.
Planned Upgrades: Smart Credentials can announce upcoming upgrades via the resolver-info text record, including the bytecode hash of the new implementation. Clients can whitelist this hash to maintain continuous resolution even when the Smart Credential upgrades, verifying the upgrade follows the expected path.
Version: 0.2.2-beta
Date: December 23, 2025
Network: Sepolia (Chain ID: 11155111)
Status: ✅ Live and operational (Latest Deployment - Ownable + ERC-8049 Clone Pattern)
| Contract | Address | Verified |
|---|---|---|
| ECS Registry | 0x1Cc0E6c3B645D7751DE7Ff7ce7d17cD228e4a4F2 |
✅ View |
| ECS Registrar | 0x86a67901820da1e3523Db67d02083C0a08170b37 |
✅ View |
| Credential Resolver (Implementation) | 0x7F7e3EA29fd74cfA4672eD2F1995d7DD6988d06f |
✅ View |
| Credential Resolver Factory | 0xb5b31DEb61f6b9Dd61b222ad50084e11EF53B8E3 |
✅ View |
| Credential Resolver (Clone - name-stars) | 0x48A3D8Cec7807eDB1ba78878c356B3D051278891 |
View |
New in v0.2.2: Ownable pattern for minimal clones + ERC-8049 integration for contract metadata + Factory pattern for gas-efficient deployments
- Root Name:
ecs.eth - Root Node:
0xe436ba58406c69a63a9611a11eb52314c5c17ba9eaaa7dab8506fe8849517286 - Deployer:
0xF8e03bd4436371E0e2F7C02E529b2172fe72b4EF - Registrar Pricing: ~0.001 ETH/year (32000 wei/second)
- Min Commitment Age: 60 seconds
- Status: ✅ Registered
- Owner:
0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 - Smart Credential:
0x48A3D8Cec7807eDB1ba78878c356B3D051278891(minimal clone) - Expires: January 7, 2027
Credential Records:
- Key:
eth.ecs.name-stars.starts:vitalik.eth - Text Value:
"100"(viasetText()) - Data Value:
100(uint256, via ERC-8049setContractMetadata())
Features:
- Uses ERC-8049 for contract-level metadata storage
data()ENS resolver function delegates to ERC-8049 storage- Supports both text records and ERC-8049 metadata
- Implements
IERC8049interface for standardized metadata access - Minimal proxy clone (EIP-1167) for gas-efficient deployment
Onchain Verification:
- ✅
text()function returns"100" - ✅
data()function returns100(uint256) via ERC-8049 - ✅
getContractMetadata()returns ERC-8049 metadata - ✅
supportsInterface(IERC8049)returnstrue - ✅ Registry correctly maps resolver to label
"name-stars"
- Status: ✅ Registered
- Owner:
0xF8e03bd4436371E0e2F7C02E529b2172fe72b4EF - Smart Credential:
0xAE5A879A021982B65A691dFdcE83528e8e13dFd3(CCResolver v0.1.0) - Expires: 2035 (10 years)
Features:
- Full ENS Extended Resolver implementation (Smart Credential)
- Controlled accounts verification via ERC-8092 Associated Accounts
- Real-time signature verification through AssociationsStore
- Returns YAML-formatted data with parent/child accounts in ERC-7930 format
- Implements
resolver-infostandard for metadata discovery
Text Record Keys:
- Controlled Accounts:
eth.ecs.controlled-accounts:<id>- Returns YAML with verified parent and child accounts - Resolver Info:
resolver-info- Returns metadata about the resolver (version, features, standards)
Query Example:
import { createPublicClient, http } from 'viem'
import { sepolia } from 'viem/chains'
const client = createPublicClient({
chain: sepolia,
transport: http('https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY')
})
// Query controlled accounts using ENS text record
const yaml = await client.getEnsText({
name: 'controlled-accounts.ecs.eth',
key: 'eth.ecs.controlled-accounts:0'
})
// Returns YAML like:
// id: 0
// registeredAt: 1765302228
// parent: "0x0001000003aa36a7144d45cd7472f2c46e81734c561a2d0b4b66c8fefe"
// children:
// - "0x0001000003aa36a714f935f966a073746a9ee0f6a685a41da23a64e1d1"
// - "0x0001000003aa36a714cc8d7b159eafa8a2c4ca5c88c3f6b760761dbf28"Test the integration:
npm run test-cc # Run full CCResolver integration testsFor complete documentation, see CCResolver-README.md
Using Cast:
# Get label from Smart Credential address (for Hooks)
cast call 0x1Cc0E6c3B645D7751DE7Ff7ce7d17cD228e4a4F2 \
"getResolverInfo(address)(string,uint128,string)" \
0x48A3D8Cec7807eDB1ba78878c356B3D051278891 \
--rpc-url https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
# Returns: "name-stars", <timestamp>, ""Using @nxt3d/ecsjs:
import {
createECSClient,
sepolia,
getResolverInfo,
resolveCredential
} from '@nxt3d/ecsjs'
const client = createECSClient({
chain: sepolia,
rpcUrl: 'https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY'
})
// Get Smart Credential info from address
const { label, resolverUpdated, review } = await getResolverInfo(
client,
'0x48A3D8Cec7807eDB1ba78878c356B3D051278891'
)
// Returns: { label: "name-stars", resolverUpdated: <timestamp>n, review: "" }
// Or resolve credential directly
const credential = await resolveCredential(
client,
'0x48A3D8Cec7807eDB1ba78878c356B3D051278891',
'eth.ecs.name-stars.starts:vitalik.eth'
)
// Returns: "100"Run the demos:
npm run hook # Full Hooks resolution flow
npm run resolve # Direct text record resolution
npm run test-cc # CCResolver controlled accounts integration
npm test # Test ecsjs library with latest deploymentOnchain Testing:
All contracts have been verified onchain and tested:
# Test resolver functions directly
cast call 0x48A3D8Cec7807eDB1ba78878c356B3D051278891 \
"text(bytes32,string)(string)" \
0x0 "eth.ecs.name-stars.starts:vitalik.eth" \
--rpc-url $SEPOLIA_RPC_URL
# Returns: "100"
cast call 0x48A3D8Cec7807eDB1ba78878c356B3D051278891 \
"data(bytes32,string)(bytes)" \
0x0 "eth.ecs.name-stars.starts:vitalik.eth" \
--rpc-url $SEPOLIA_RPC_URL
# Returns: 0x0000000000000000000000000000000000000000000000000000000000000064 (100)
cast call 0x48A3D8Cec7807eDB1ba78878c356B3D051278891 \
"getContractMetadata(string)(bytes)" \
"eth.ecs.name-stars.starts:vitalik.eth" \
--rpc-url $SEPOLIA_RPC_URL
# Returns: 0x0000000000000000000000000000000000000000000000000000000000000064
cast call 0x1Cc0E6c3B645D7751DE7Ff7ce7d17cD228e4a4F2 \
"getResolverInfo(address)(string,uint128,string)" \
0x48A3D8Cec7807eDB1ba78878c356B3D051278891 \
--rpc-url $SEPOLIA_RPC_URL
# Returns: "name-stars", <timestamp>, ""For full deployment details, see deployments/sepolia-2025-12-23-1429.md
forge buildforge testSee deployment scripts in script/:
DeployAndCommit.s.sol- Deploy contracts and commit registrationRegisterAndSetup.s.sol- Complete registration after 60s wait
For full deployment details, see deployments/sepolia-2025-12-23-1429.md