@towns-protocol/contracts 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,541 @@
1
+ # Global Usernames
2
+
3
+ ## Overview
4
+
5
+ The Towns Protocol Global Usernames system implements an ENS-compatible cross-chain domain registry system that enables **Global Usernames** - portable usernames (e.g., `ben.towns.eth`) that work across all Towns spaces, channels, chats, GDMS, and DMs. Instead of showing wallet addresses, users see `@ben` with their profile.
6
+
7
+ ### Key Features
8
+
9
+ | Feature | Description |
10
+ | -------------------------- | ----------------------------------------------------------------- |
11
+ | **L2 Subdomains** | Register subdomains on Base L2 (e.g., `ben.towns.eth`) |
12
+ | **NFT Ownership** | Each subdomain is an ERC721 token, enabling transfers |
13
+ | **Cross-chain Resolution** | Resolves via ENS from L1 using CCIP-Read (EIP-3668) |
14
+ | **Profile Data** | Store display name, avatar, bio as resolver text records |
15
+ | **Multi-coin Addresses** | Support ETH (coinType 60) and chain-specific addresses (ENSIP-11) |
16
+ | **Tiered Pricing** | First registration free, subsequent charged via FeeManager |
17
+
18
+ ## Architecture
19
+
20
+ The system consists of three main components deployed across two chains:
21
+
22
+ ```
23
+ ┌─────────────────────────────────────────────────────────────────────────────┐
24
+ │ ETHEREUM L1 (MAINNET) │
25
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
26
+ │ │ L1 Resolver Diamond │ │
27
+ │ │ ┌─────────────────────┐ │ │
28
+ │ │ │ L1ResolverFacet │ ─── CCIP-Read (EIP-3668) │ │
29
+ │ │ │ - resolve() │ OffchainLookup → Gateway │ │
30
+ │ │ │ - resolveWithProof │ ← Verified response │ │
31
+ │ │ └─────────────────────┘ │ │
32
+ │ └─────────────────────────────────────────────────────────────────────┘ │
33
+ └─────────────────────────────────────────────────────────────────────────────┘
34
+
35
+ │ CCIP-Read
36
+
37
+ ┌─────────────────────────────────────────────────────────────────────────────┐
38
+ │ CCIP GATEWAY SERVICE │
39
+ │ - Receives OffchainLookup requests │
40
+ │ - Queries L2 Registry via RPC │
41
+ │ - Signs responses with expiration │
42
+ │ - Returns (result, expires, signature) │
43
+ └─────────────────────────────────────────────────────────────────────────────┘
44
+
45
+ │ RPC Query
46
+
47
+ ┌─────────────────────────────────────────────────────────────────────────────┐
48
+ │ BASE L2 │
49
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
50
+ │ │ L2 Registry Diamond │ │
51
+ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
52
+ │ │ │ L2RegistryFacet │ │ AddrResolverFacet │ │ │
53
+ │ │ │ - createSubdomain()│ │ - setAddr() │ │ │
54
+ │ │ │ - ERC721 functions │ │ - addr() │ │ │
55
+ │ │ └─────────────────────┘ └─────────────────────┘ │ │
56
+ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
57
+ │ │ │ TextResolverFacet │ │ ContentHashFacet │ │ │
58
+ │ │ │ - setText() │ │ - setContenthash() │ │ │
59
+ │ │ │ - text() │ │ - contenthash() │ │ │
60
+ │ │ └─────────────────────┘ └─────────────────────┘ │ │
61
+ │ │ ┌─────────────────────┐ │ │
62
+ │ │ │ ExtendedResolverFacet│ ← Direct L2 resolution │ │
63
+ │ │ └─────────────────────┘ │ │
64
+ │ └─────────────────────────────────────────────────────────────────────┘ │
65
+ │ │
66
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
67
+ │ │ L2 Registrar Diamond │ │
68
+ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
69
+ │ │ │ L2RegistrarFacet │ │ DomainFeeHook │ │ │
70
+ │ │ │ - register() │──│ - onChargeFee() │ │ │
71
+ │ │ │ - isAvailable() │ │ - tiered pricing │ │ │
72
+ │ │ └─────────────────────┘ └─────────────────────┘ │ │
73
+ │ └─────────────────────────────────────────────────────────────────────┘ │
74
+ └─────────────────────────────────────────────────────────────────────────────┘
75
+ ```
76
+
77
+ ### Contract Hierarchy
78
+
79
+ | Component | Location | Purpose |
80
+ | -------------------------- | -------------------- | ------------------------------------------- |
81
+ | `L1ResolverFacet` | `facets/l1/` | CCIP-Read resolver on Ethereum mainnet |
82
+ | `L1ResolverMod` | `facets/l1/` | L1 resolver storage, signature verification |
83
+ | `L2RegistryFacet` | `facets/l2/` | Domain NFT registry (ERC721) on Base |
84
+ | `L2RegistryMod` | `facets/l2/modules/` | Subdomain creation, registrar management |
85
+ | `AddrResolverFacet` | `facets/l2/` | Multi-coin address records (SLIP-44) |
86
+ | `TextResolverFacet` | `facets/l2/` | Key-value text records |
87
+ | `ContentHashResolverFacet` | `facets/l2/` | IPFS/IPNS/Swarm content hashes |
88
+ | `ExtendedResolverFacet` | `facets/l2/` | EIP-3668 direct on-chain resolution |
89
+ | `L2RegistrarFacet` | `facets/registrar/` | Subdomain registration with validation |
90
+ | `L2RegistrarMod` | `facets/registrar/` | Label validation, fee charging |
91
+ | `DomainFeeHook` | `hooks/` | Tiered pricing, first-free logic |
92
+
93
+ ### Module Files
94
+
95
+ | Module | Purpose |
96
+ | ------------------------ | ---------------------------------------------------- |
97
+ | `AddrResolverMod` | Address storage with versioning (SLIP-44 coin types) |
98
+ | `TextResolverMod` | Text record storage with versioning |
99
+ | `ContentHashResolverMod` | Content hash storage with versioning |
100
+ | `VersionRecordMod` | Record versioning for atomic invalidation |
101
+
102
+ ## Storage Layout
103
+
104
+ Each module uses the diamond storage pattern with unique storage slots:
105
+
106
+ | Module | Storage Slot | Contents |
107
+ | ------------------------ | --------------- | ------------------------------------------------------- |
108
+ | `L1ResolverMod` | `0xad5af01a...` | gatewayUrl, gatewaySigner, nameWrapper, registryByNode |
109
+ | `L2RegistryMod` | `0xd006f566...` | baseNode, names, metadata, registrars, token (ERC721) |
110
+ | `AddrResolverMod` | `0x8b8a0bb4...` | versionable_addresses[version][node][coinType] |
111
+ | `TextResolverMod` | `0xccd57d47...` | versionable_texts[version][node][key] |
112
+ | `ContentHashResolverMod` | `0x792df830...` | versionable_hashes[version][node] |
113
+ | `VersionRecordMod` | `0xf2220575...` | recordVersions[node] |
114
+ | `L2RegistrarMod` | `0xd8d41403...` | registry, coinType, spaceFactory, currency |
115
+ | `DomainFeeHook` | `0x54805b38...` | defaultPrice, priceTiers, registrationCount, feeManager |
116
+
117
+ ## Core Flows
118
+
119
+ ### 1. Cross-chain Resolution Flow (L1 → Gateway → L2)
120
+
121
+ When a user resolves `ben.towns.eth` from Ethereum mainnet:
122
+
123
+ ```
124
+ User/Client: resolve("ben.towns.eth", addr(bytes32))
125
+
126
+ ├─ 1. ENS Registry forwards to L1ResolverFacet
127
+
128
+ ├─ 2. L1ResolverFacet.resolve()
129
+ │ ├─ Parse name → extract parent domain (towns.eth)
130
+ │ ├─ Look up L2Registry for parent node
131
+ │ └─ REVERT OffchainLookup(gatewayUrl, callData, callback)
132
+
133
+ ├─ 3. Client calls CCIP Gateway
134
+ │ ├─ Gateway calls L2 Registry via RPC
135
+ │ ├─ Gets resolver data (addr, text, etc.)
136
+ │ └─ Returns signed response (result, expires, sig)
137
+
138
+ ├─ 4. Client calls L1ResolverFacet.resolveWithProof()
139
+ │ ├─ Verify signature from gatewaySigner
140
+ │ ├─ Check expiration timestamp
141
+ │ └─ Return verified result
142
+
143
+ └─ 5. Client receives resolved address
144
+ ```
145
+
146
+ ### 2. Subdomain Registration Flow
147
+
148
+ When a user registers `ben.towns.eth`:
149
+
150
+ ```
151
+ User (Smart Account): L2RegistrarFacet.register("ben", accountAddr)
152
+
153
+ ├─ 1. Verify Caller
154
+ │ └─ Must be IModularAccount (towns smart account)
155
+
156
+ ├─ 2. Validate Label
157
+ │ ├─ Length: 3-63 characters
158
+ │ ├─ Characters: a-z, 0-9, hyphen
159
+ │ └─ No leading/trailing hyphens
160
+
161
+ ├─ 3. Charge Fee (via FeeManager + DomainFeeHook)
162
+ │ ├─ First registration: FREE
163
+ │ └─ Subsequent: Tiered by label length
164
+
165
+ ├─ 4. Create Subdomain (L2Registry.createSubdomain)
166
+ │ ├─ Compute subdomainHash = keccak256(parentNode, keccak256(label))
167
+ │ ├─ Mint NFT (tokenId = uint256(subdomainHash))
168
+ │ ├─ Store DNS-encoded name
169
+ │ └─ Set initial records via delegatecall
170
+
171
+ ├─ 5. Set Address Records
172
+ │ ├─ setAddr(node, coinType, owner) for current chain
173
+ │ └─ setAddr(node, 60, owner) for ETH mainnet
174
+
175
+ └─ 6. Emit Events
176
+ ├─ NewOwner(parentNode, labelhash, owner)
177
+ ├─ SubnodeCreated(node, name, owner)
178
+ └─ NameRegistered(label, owner)
179
+ ```
180
+
181
+ ### 3. Record Update Flow
182
+
183
+ When a domain owner updates resolver records:
184
+
185
+ ```
186
+ Owner: AddrResolverFacet.setAddr(usernameHash, coinType, addr)
187
+ TextResolverFacet.setText(usernameHash, "avatar", "ipfs://...")
188
+
189
+ ├─ 1. Authorization Check (L2RegistryMod.onlyAuthorized)
190
+ │ ├─ Is caller the token owner?
191
+ │ ├─ Is caller approved by token owner?
192
+ │ └─ Is caller an approved registrar?
193
+
194
+ ├─ 2. Get Current Version
195
+ │ └─ version = VersionRecordMod.recordVersions[usernameHash]
196
+
197
+ ├─ 3. Update Storage
198
+ │ └─ versionable_addresses[version][usernameHash][coinType] = addr
199
+
200
+ └─ 4. Emit Event
201
+ └─ AddressChanged(usernameHash, coinType, addr)
202
+ ```
203
+
204
+ ### 4. Record Clearing Flow
205
+
206
+ When an owner wants to clear all records atomically:
207
+
208
+ ```
209
+ Owner: clearRecords(node)
210
+
211
+ ├─ 1. Increment version number
212
+ │ └─ recordVersions[node]++
213
+
214
+ └─ 2. All previous records become inaccessible
215
+ └─ Queries use new version, old records orphaned
216
+ ```
217
+
218
+ ## Access Control
219
+
220
+ ### L2 Registry Access Control
221
+
222
+ | Action | Who Can Perform |
223
+ | ---------------------- | ------------------------------------------- |
224
+ | `createSubdomain` | Domain owner OR approved registrar |
225
+ | `addRegistrar` | Root domain owner only |
226
+ | `removeRegistrar` | Root domain owner only |
227
+ | `setMetadata` | Approved registrar only |
228
+ | `setAddr/setText/etc.` | Node owner, approved operator, OR registrar |
229
+
230
+ ### L2 Registrar Access Control
231
+
232
+ | Action | Who Can Perform |
233
+ | ----------------- | ------------------------------------------- |
234
+ | `register` | Towns smart accounts only (IModularAccount) |
235
+ | `setRegistry` | Contract owner only |
236
+ | `setSpaceFactory` | Contract owner only |
237
+ | `setCurrency` | Contract owner only |
238
+
239
+ ### L1 Resolver Access Control
240
+
241
+ | Action | Who Can Perform |
242
+ | ------------------ | ------------------------------------------ |
243
+ | `setL2Registry` | ENS node owner (via NameWrapper or direct) |
244
+ | `setGatewayURL` | Contract owner only |
245
+ | `setGatewaySigner` | Contract owner only |
246
+
247
+ ## Label Validation Rules
248
+
249
+ The registrar enforces DNS-compatible label rules:
250
+
251
+ | Rule | Constraint |
252
+ | ------------------- | ------------------------------------- |
253
+ | **Length** | 3-63 characters |
254
+ | **Characters** | Lowercase a-z, digits 0-9, hyphen (-) |
255
+ | **Hyphen Position** | Cannot start or end with hyphen |
256
+ | **Case** | Must be lowercase (no uppercase) |
257
+
258
+ ```solidity
259
+ // Character validation bitmask
260
+ ALLOWED_LABEL_CHARS = LOWERCASE_7_BIT_ASCII | DIGITS_7_BIT_ASCII | (1 << 45)
261
+ // 45 = ASCII code for hyphen '-'
262
+ ```
263
+
264
+ ## Fee Structure
265
+
266
+ ### DomainFeeHook Pricing Logic
267
+
268
+ ```
269
+ Registration Fee Calculation:
270
+
271
+ ├─ If registrationCount[user] == 0
272
+ │ └─ Fee = 0 (first registration FREE)
273
+
274
+ └─ Else
275
+ ├─ Get labelLength from context
276
+ ├─ price = priceTiers[labelLength]
277
+ └─ If price == 0 → use defaultPrice $5.00
278
+ ```
279
+
280
+ ### Fee Hook Integration
281
+
282
+ ```solidity
283
+ // In L2RegistrarMod.chargeFee()
284
+ uint256 expectedFee = IFeeManager(spaceFactory).calculateFee(
285
+ FeeTypesLib.DOMAIN_REGISTRATION,
286
+ msg.sender,
287
+ 0,
288
+ abi.encode(bytes(label).length) // extraData = label length
289
+ );
290
+
291
+ ProtocolFeeLib.chargeAlways(
292
+ spaceFactory,
293
+ FeeTypesLib.DOMAIN_REGISTRATION,
294
+ msg.sender,
295
+ currency,
296
+ expectedFee,
297
+ expectedFee,
298
+ extraData
299
+ );
300
+ ```
301
+
302
+ ## ENS Compatibility
303
+
304
+ ### Supported Resolver Interfaces
305
+
306
+ | Interface | EIP | Function |
307
+ | ---------------------- | -------- | ---------------------------------------------- |
308
+ | `IAddrResolver` | EIP-137 | `addr(bytes32 node)` → ETH address |
309
+ | `IAddressResolver` | EIP-2304 | `addr(bytes32, uint256 coinType)` → multi-coin |
310
+ | `ITextResolver` | EIP-634 | `text(bytes32, string key)` → text records |
311
+ | `IContentHashResolver` | EIP-1577 | `contenthash(bytes32)` → IPFS/Swarm |
312
+ | `IExtendedResolver` | EIP-3668 | `resolve(bytes, bytes)` → CCIP-Read |
313
+
314
+ ### Coin Type Mapping (ENSIP-11)
315
+
316
+ For L2 address resolution, coin types are computed as:
317
+
318
+ ```solidity
319
+ // ENSIP-11: Maps EVM chainId to ENS coinType
320
+ coinType = 0x80000000 | block.chainid
321
+
322
+ // Examples:
323
+ // Base (chainId 8453) → coinType 2147492101
324
+ // Ethereum (chainId 1) → coinType 60 (SLIP-44 standard)
325
+ ```
326
+
327
+ ### Common Text Record Keys
328
+
329
+ | Key | Purpose |
330
+ | --------------------- | --------------------------------- |
331
+ | `displayName` | Human-readable display name |
332
+ | `avatar` | Profile picture URL (IPFS, HTTPS) |
333
+ | `description` / `bio` | User bio text |
334
+ | `url` | Personal website |
335
+ | `email` | Email address |
336
+ | `com.twitter` | Twitter handle |
337
+ | `com.discord` | Discord username |
338
+ | `com.github` | GitHub username |
339
+
340
+ ## Integration Examples
341
+
342
+ ### Register a Subdomain
343
+
344
+ ```solidity
345
+ // User must be a Towns smart account (IModularAccount)
346
+ IL2Registrar registrar = IL2Registrar(REGISTRAR_ADDRESS);
347
+
348
+ // Check availability first
349
+ require(registrar.isAvailable("ben"), "Not available");
350
+
351
+ // Register (first one is free!)
352
+ registrar.register("ben", msg.sender);
353
+
354
+ // Result: ben.towns.eth is now owned by msg.sender
355
+ ```
356
+
357
+ ### Resolve an Address (L2 Direct)
358
+
359
+ ```solidity
360
+ IL2Registry registry = IL2Registry(REGISTRY_ADDRESS);
361
+
362
+ // Compute namehash
363
+ bytes32 node = registry.namehash("ben.towns.eth");
364
+
365
+ // Get ETH address (coinType 60)
366
+ address owner = IAddrResolver(address(registry)).addr(node);
367
+
368
+ // Get Base address (coinType 2147492101)
369
+ bytes memory baseAddr = IAddressResolver(address(registry)).addr(node, 2147492101);
370
+ ```
371
+
372
+ ### Set Profile Records
373
+
374
+ ```solidity
375
+ IL2Registry registry = IL2Registry(REGISTRY_ADDRESS);
376
+ bytes32 usernameHash = registry.namehash("ben.towns.eth");
377
+
378
+ // Only usernameHash owner can set records
379
+ ITextResolver(address(registry)).setText(usernameHash, "displayName", "ben");
380
+ ITextResolver(address(registry)).setText(usernameHash, "avatar", "ipfs://Qm...");
381
+ ITextResolver(address(registry)).setText(usernameHash, "bio", "Building on Towns");
382
+ ```
383
+
384
+ ### Resolve via CCIP-Read (L1)
385
+
386
+ ```typescript
387
+ // Using viem/ethers with ENS resolution
388
+ import { normalize } from "viem/ens";
389
+
390
+ const client = createPublicClient({ chain: mainnet, transport: http() });
391
+
392
+ // This triggers CCIP-Read automatically
393
+ const address = await client.getEnsAddress({
394
+ name: normalize("ben.towns.eth"),
395
+ });
396
+ ```
397
+
398
+ ## Events
399
+
400
+ ### L2 Registry Events
401
+
402
+ | Event | Parameters | When |
403
+ | ------------------ | ------------------------------ | ------------------------------ |
404
+ | `SubnodeCreated` | `node, name, owner` | New subdomain minted |
405
+ | `NewOwner` | `parentNode, labelhash, owner` | ENS-compatible ownership event |
406
+ | `RegistrarAdded` | `registrar` | New registrar approved |
407
+ | `RegistrarRemoved` | `registrar` | Registrar removed |
408
+ | `MetadataSet` | `node, metadata` | Subdomain metadata updated |
409
+
410
+ ### Resolver Events
411
+
412
+ | Event | Parameters | When |
413
+ | -------------------- | ----------------------- | -------------------------- |
414
+ | `AddrChanged` | `node, addr` | ETH address updated |
415
+ | `AddressChanged` | `node, coinType, addr` | Multi-coin address updated |
416
+ | `TextChanged` | `node, key, key, value` | Text record updated |
417
+ | `ContenthashChanged` | `node, hash` | Content hash updated |
418
+ | `VersionChanged` | `node, version` | Records cleared |
419
+
420
+ ### L1 Resolver Events
421
+
422
+ | Event | Parameters | When |
423
+ | ------------------ | -------------------------------- | ------------------------- |
424
+ | `GatewayURLSet` | `gatewayUrl` | Gateway URL updated |
425
+ | `GatewaySignerSet` | `gatewaySigner` | Signer address updated |
426
+ | `L2RegistrySet` | `node, chainId, registryAddress` | L2 registry mapping added |
427
+
428
+ ## Error Reference
429
+
430
+ ### L1 Resolver Errors
431
+
432
+ | Error | Cause |
433
+ | ---------------------------------- | -------------------------------- |
434
+ | `L1Resolver__InvalidGatewayURL` | Empty gateway URL |
435
+ | `L1Resolver__InvalidGatewaySigner` | Zero address signer |
436
+ | `L1Resolver__InvalidL2Registry` | No L2 registry for parent domain |
437
+ | `L1Resolver__InvalidName` | Name has fewer than 2 parts |
438
+ | `L1Resolver__InvalidOwner` | Caller not ENS node owner |
439
+ | `L1Resolver__SignatureExpired` | Gateway response expired |
440
+ | `L1Resolver__InvalidSignature` | Signature verification failed |
441
+
442
+ ### L2 Registry Errors
443
+
444
+ | Error | Cause |
445
+ | ----------------------------------- | ----------------------------------- |
446
+ | `L2RegistryMod_LabelTooShort` | Label < 1 character |
447
+ | `L2RegistryMod_LabelTooLong` | Label > 255 characters |
448
+ | `L2RegistryMod_NotAvailable` | Subdomain already exists |
449
+ | `L2RegistryMod_NotOwnerOrRegistrar` | Unauthorized for subdomain creation |
450
+ | `L2RegistryMod_NotOwner` | Not root domain owner |
451
+ | `L2RegistryMod_NotRegistrar` | Not approved registrar |
452
+ | `L2RegistryMod_NotAuthorized` | Not authorized for record updates |
453
+
454
+ ### L2 Registrar Errors
455
+
456
+ | Error | Cause |
457
+ | ------------------------------ | ---------------------------- |
458
+ | `L2Registrar__InvalidLabel` | Label fails validation rules |
459
+ | `L2Registrar__NotSmartAccount` | Caller not IModularAccount |
460
+
461
+ ### DomainFeeHook Errors
462
+
463
+ | Error | Cause |
464
+ | ------------------------------- | --------------------------------------- |
465
+ | `DomainFeeHook__InvalidContext` | Missing/invalid label length in context |
466
+ | `DomainFeeHook__LengthMismatch` | Array lengths don't match in batch set |
467
+ | `DomainFeeHook__Unauthorized` | Caller not authorized FeeManager |
468
+ | `DomainFeeHook__TooManyLengths` | Batch update exceeds 10 tiers |
469
+
470
+ ## Security Considerations
471
+
472
+ 1. **Gateway Signer Key Security**: The L1 resolver trusts responses signed by `gatewaySigner`. Key compromise allows forged resolutions.
473
+
474
+ 2. **Signature Expiration**: Gateway responses include expiration timestamps. Clients should verify freshness.
475
+
476
+ 3. **Registrar Approval**: Only approved registrars can mint subdomains without being domain owners. Carefully manage registrar list.
477
+
478
+ 4. **Smart Account Requirement**: Registration requires IModularAccount to prevent bot spam and ensure identity verification.
479
+
480
+ 5. **Record Versioning**: Incrementing a node's version atomically invalidates all records - useful for ownership transfers.
481
+
482
+ 6. **Cross-chain Consistency**: L1 resolver maps to specific L2 registry addresses. Ensure mappings stay synchronized.
483
+
484
+ ## Appendix: Key Functions
485
+
486
+ ### Registration
487
+
488
+ ```solidity
489
+ // L2RegistrarFacet.sol
490
+ function register(string calldata label, address owner) external nonReentrant
491
+
492
+ // L2RegistryFacet.sol
493
+ function createSubdomain(
494
+ bytes32 domainHash,
495
+ string calldata subdomain,
496
+ address owner,
497
+ bytes[] calldata records,
498
+ bytes calldata metadata
499
+ ) external
500
+ ```
501
+
502
+ ### Resolution
503
+
504
+ ```solidity
505
+ // L1ResolverFacet.sol
506
+ function resolve(bytes calldata name, bytes calldata data) external view returns (bytes memory)
507
+ function resolveWithProof(bytes calldata response, bytes calldata extraData) external view returns (bytes memory)
508
+
509
+ // AddrResolverFacet.sol
510
+ function addr(bytes32 node) external view returns (address payable)
511
+ function addr(bytes32 node, uint256 coinType) external view returns (bytes memory)
512
+ ```
513
+
514
+ ### Record Management
515
+
516
+ ```solidity
517
+ // TextResolverFacet.sol
518
+ function setText(bytes32 node, string calldata key, string calldata value) external
519
+ function text(bytes32 node, string calldata key) external view returns (string memory)
520
+
521
+ // AddrResolverFacet.sol
522
+ function setAddr(bytes32 node, address a) external
523
+ function setAddr(bytes32 node, uint256 coinType, bytes memory a) external
524
+
525
+ // ContentHashResolverFacet.sol
526
+ function setContenthash(bytes32 node, bytes calldata hash) external
527
+ function contenthash(bytes32 node) external view returns (bytes memory)
528
+ ```
529
+
530
+ ### Administration
531
+
532
+ ```solidity
533
+ // L2RegistryFacet.sol
534
+ function addRegistrar(address registrar) external
535
+ function removeRegistrar(address registrar) external
536
+
537
+ // L1ResolverFacet.sol
538
+ function setL2Registry(bytes32 node, uint64 chainId, address registryAddress) external
539
+ function setGatewayURL(string calldata gatewayUrl) external onlyOwner
540
+ function setGatewaySigner(address gatewaySigner) external onlyOwner
541
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@towns-protocol/contracts",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "scripts": {
5
5
  "clean": "forge clean",
6
6
  "compile": "forge build",