|
1 | 1 | # ENS Config Resolver |
2 | 2 |
|
3 | | -A set of ENS contracts that enable users to claim address-based subnames under your ENS name. |
| 3 | +A set of ENS contracts that enable users to claim address-based subnames under your ENS name, with optional L1 → L2 CCIP-Read resolution. |
4 | 4 |
|
5 | 5 | ## Overview |
6 | 6 |
|
7 | | -This project provides two main contracts: |
| 7 | +This project provides three main contracts: |
8 | 8 |
|
9 | 9 | | Contract | Description | |
10 | 10 | | --------------------------- | ------------------------------------------------------------------------------------- | |
11 | 11 | | **ConfigResolver** | A general-purpose ENS resolver for setting records (text, address, contenthash, etc.) | |
12 | | -| **AddressSubnameRegistrar** | Enables users to claim `<their-address>.yourname.eth` subnames | |
| 12 | +| **AddressSubnameRegistrar** | Enables users to claim `0x<address>.yourname.eth` subnames | |
| 13 | +| **L1ConfigResolver** | Reads L2 ConfigResolver records from L1 via CCIP-Read (Unruggable Gateways) | |
| 14 | + |
| 15 | +## Deployments |
| 16 | + |
| 17 | +### Testnets |
| 18 | + |
| 19 | +| Contract | Network | Address | |
| 20 | +| ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | |
| 21 | +| ConfigResolver | Base Sepolia | [`0xA66c55a6b76967477af18A03F2f12d52251Dc2C0`](https://sepolia.basescan.org/address/0xA66c55a6b76967477af18A03F2f12d52251Dc2C0) | |
| 22 | +| L1ConfigResolver | Sepolia | [`0x380e926f5D78F21b80a6EfeF2B3CEf9CcC89356B`](https://sepolia.etherscan.io/address/0x380e926f5D78F21b80a6EfeF2B3CEf9CcC89356B) | |
| 23 | + |
| 24 | +### Mainnet |
| 25 | + |
| 26 | +| Contract | Network | Address | |
| 27 | +| ---------------- | -------- | ------- | |
| 28 | +| ConfigResolver | Base | TBD | |
| 29 | +| L1ConfigResolver | Ethereum | TBD | |
13 | 30 |
|
14 | 31 | ### Example |
15 | 32 |
|
@@ -37,12 +54,57 @@ forge test |
37 | 54 |
|
38 | 55 | ### Deploy |
39 | 56 |
|
| 57 | +Deploy ConfigResolver + AddressSubnameRegistrar: |
| 58 | + |
40 | 59 | ```bash |
41 | | -# Deploy to Sepolia |
42 | | -./script/deploy.sh |
| 60 | +# Sepolia |
| 61 | +PARENT_NODE=$(cast namehash "yourname.eth") \ |
| 62 | + forge script script/Deploy.s.sol \ |
| 63 | + --rpc-url https://eth-sepolia.g.alchemy.com/v2/$ALCHEMY_API_KEY \ |
| 64 | + --account deployer \ |
| 65 | + --broadcast \ |
| 66 | + --verify |
| 67 | + |
| 68 | +# Mainnet |
| 69 | +PARENT_NODE=$(cast namehash "yourname.eth") \ |
| 70 | + forge script script/Deploy.s.sol \ |
| 71 | + --rpc-url https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY \ |
| 72 | + --account deployer \ |
| 73 | + --broadcast \ |
| 74 | + --verify |
| 75 | +``` |
43 | 76 |
|
44 | | -# Deploy to Mainnet |
45 | | -./script/deploy.sh mainnet |
| 77 | +Deploy ConfigResolver only: |
| 78 | + |
| 79 | +```bash |
| 80 | +forge script script/Deploy.s.sol --sig "deployConfigResolver()" \ |
| 81 | + --rpc-url $RPC_URL \ |
| 82 | + --account deployer \ |
| 83 | + --broadcast \ |
| 84 | + --verify |
| 85 | +``` |
| 86 | + |
| 87 | +Deploy L1ConfigResolver (for reading L2 records from L1): |
| 88 | + |
| 89 | +```bash |
| 90 | +L2_CONFIG_RESOLVER=0x... \ |
| 91 | + forge script script/Deploy.s.sol --sig "deployL1Resolver()" \ |
| 92 | + --rpc-url $RPC_URL \ |
| 93 | + --account deployer \ |
| 94 | + --broadcast \ |
| 95 | + --verify |
| 96 | +``` |
| 97 | + |
| 98 | +Deploy L1 AddressSubnameRegistrar (for L1 claiming with L2 storage): |
| 99 | + |
| 100 | +```bash |
| 101 | +PARENT_NODE=$(cast namehash "yourname.eth") \ |
| 102 | +L1_CONFIG_RESOLVER=0x... \ |
| 103 | + forge script script/Deploy.s.sol --sig "deployL1Registrar()" \ |
| 104 | + --rpc-url $RPC_URL \ |
| 105 | + --account deployer \ |
| 106 | + --broadcast \ |
| 107 | + --verify |
46 | 108 | ``` |
47 | 109 |
|
48 | 110 | See [DEPLOYMENT.md](./DEPLOYMENT.md) for the full deployment and setup guide. |
@@ -90,6 +152,32 @@ registrar.getLabel(addr); // "0x8d25687829d6b85d9e0020b8c89e3ca24de20a89" |
90 | 152 | registrar.node(addr); |
91 | 153 | ``` |
92 | 154 |
|
| 155 | +### L1ConfigResolver |
| 156 | + |
| 157 | +An L1 resolver that reads ENS records from a ConfigResolver deployed on L2 (Base) using CCIP-Read. Implements the `IL1ConfigResolver` interface. |
| 158 | + |
| 159 | +```solidity |
| 160 | +// Supports standard ENS resolution methods |
| 161 | +resolver.addr(node); // Get ETH address |
| 162 | +resolver.text(node, "url"); // Get text record |
| 163 | +resolver.contenthash(node); // Get contenthash |
| 164 | +
|
| 165 | +// Also supports ENSIP-10 extended resolution |
| 166 | +resolver.resolve(name, data); |
| 167 | +
|
| 168 | +// IL1ConfigResolver interface |
| 169 | +resolver.l2ChainId(); // Get the L2 chain ID |
| 170 | +resolver.l2ConfigResolver(); // Get the L2 ConfigResolver address |
| 171 | +``` |
| 172 | + |
| 173 | +**Default Verifiers (Base):** |
| 174 | +| Network | Verifier | L2 Chain ID | |
| 175 | +|---------|----------|-------------| |
| 176 | +| Sepolia | `0x7F68510F0fD952184ec0b976De429a29A2Ec0FE3` | 84532 (Base Sepolia) | |
| 177 | +| Mainnet | `0x0bC6c539e5fc1fb92F31dE34426f433557A9A5A2` | 8453 (Base) | |
| 178 | + |
| 179 | +Custom verifiers and L2 chain IDs can be specified via environment variables during deployment. |
| 180 | + |
93 | 181 | ## Development |
94 | 182 |
|
95 | 183 | ### Prerequisites |
@@ -123,25 +211,68 @@ forge snapshot |
123 | 211 |
|
124 | 212 | ## Architecture |
125 | 213 |
|
| 214 | +### L1 Claiming with L2 Storage (Recommended) |
| 215 | + |
| 216 | +Users claim subnames on L1 (Ethereum) and can change their resolver. Records are stored on L2 (Base) for lower gas costs. |
| 217 | + |
| 218 | +``` |
| 219 | +┌─────────────────────────────────────────────────────────────────────────┐ |
| 220 | +│ L1 (Ethereum) │ |
| 221 | +├─────────────────────────────────────────────────────────────────────────┤ |
| 222 | +│ │ |
| 223 | +│ ┌─────────────────────────────┐ ┌───────────────────────────────┐ │ |
| 224 | +│ │ AddressSubnameRegistrar │ │ L1ConfigResolver │ │ |
| 225 | +│ │ ───────────────────────── │ │ ─────────────────────────── │ │ |
| 226 | +│ │ • Users call claim() │────▶│ • Default resolver for │ │ |
| 227 | +│ │ • Creates ENS node on L1 │ │ claimed subnames │ │ |
| 228 | +│ │ │ │ • Reads via CCIP-Read │ │ |
| 229 | +│ └─────────────────────────────┘ └───────────────┬───────────────┘ │ |
| 230 | +│ │ │ |
| 231 | +│ User owns ENS node → can change resolver if desired │ │ |
| 232 | +└───────────────────────────────────────────────────────┼──────────────────┘ |
| 233 | + │ CCIP-Read |
| 234 | + ▼ |
| 235 | +┌─────────────────────────────────────────────────────────────────────────┐ |
| 236 | +│ L2 (Base) │ |
| 237 | +├─────────────────────────────────────────────────────────────────────────┤ |
| 238 | +│ ┌─────────────────────────────────────────────────────────────────┐ │ |
| 239 | +│ │ ConfigResolver - stores records (text, address, contenthash) │ │ |
| 240 | +│ └─────────────────────────────────────────────────────────────────┘ │ |
| 241 | +└─────────────────────────────────────────────────────────────────────────┘ |
| 242 | +``` |
| 243 | + |
| 244 | +**User Flow:** |
| 245 | + |
| 246 | +1. Claim subname on L1 → `registrar.claim()` |
| 247 | +2. Set records on L2 → `resolver.setText(node, "url", "https://...")` |
| 248 | +3. L1 resolution reads from L2 via CCIP-Read |
| 249 | +4. Optionally change resolver on L1 (user owns the ENS node) |
| 250 | + |
| 251 | +### CCIP-Read Flow |
| 252 | + |
126 | 253 | ``` |
127 | | -┌─────────────────────────────────────────────────────────────┐ |
128 | | -│ User's Wallet │ |
129 | | -└─────────────────────────────────────────────────────────────┘ |
130 | | - │ |
131 | | - ▼ |
132 | | -┌─────────────────────────────────────────────────────────────┐ |
133 | | -│ AddressSubnameRegistrar │ |
134 | | -│ ┌─────────────────────────────────────────────────────┐ │ |
135 | | -│ │ claim() → creates <address>.parent.eth │ │ |
136 | | -│ └─────────────────────────────────────────────────────┘ │ |
137 | | -└─────────────────────────────────────────────────────────────┘ |
138 | | - │ |
139 | | - ┌───────────────┴───────────────┐ |
140 | | - ▼ ▼ |
141 | | -┌─────────────────────────┐ ┌─────────────────────────────┐ |
142 | | -│ ENS Registry │ │ ConfigResolver │ |
143 | | -│ (stores ownership) │ │ (stores records) │ |
144 | | -└─────────────────────────┘ └─────────────────────────────┘ |
| 254 | +┌──────────────────┐ 1. Call ┌─────────────────────┐ |
| 255 | +│ Your dApp │ ───────────────► │ L1ConfigResolver │ |
| 256 | +│ (Frontend) │ │ (Ethereum L1) │ |
| 257 | +└──────────────────┘ └────────┬────────────┘ |
| 258 | + ▲ │ |
| 259 | + │ 2. Reverts with |
| 260 | + │ OffchainLookup |
| 261 | + │ │ |
| 262 | + │ 5. Return ▼ |
| 263 | + │ verified ┌─────────────────────────────┐ |
| 264 | + │ data │ Gateway (off-chain) │ |
| 265 | + │ └─────────────┬───────────────┘ |
| 266 | + │ │ |
| 267 | + │ 3. Fetch proofs from L2 |
| 268 | + │ │ |
| 269 | + │ ▼ |
| 270 | + │ ┌─────────────────────────────┐ |
| 271 | + │ │ ConfigResolver (Base L2) │ |
| 272 | + │ └─────────────────────────────┘ |
| 273 | + │ │ |
| 274 | + │ 4. Return proofs |
| 275 | + └─────────────────────────────────┘ |
145 | 276 | ``` |
146 | 277 |
|
147 | 278 | ## License |
|
0 commit comments