@towns-protocol/contracts 0.0.455 → 1.0.2

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.
Files changed (61) hide show
  1. package/package.json +4 -4
  2. package/scripts/deployments/diamonds/DeployL1Resolver.s.sol +155 -0
  3. package/scripts/deployments/diamonds/DeployL2Registrar.s.sol +171 -0
  4. package/scripts/deployments/diamonds/DeployL2Resolver.s.sol +196 -0
  5. package/scripts/deployments/diamonds/DeploySpaceFactory.s.sol +0 -12
  6. package/scripts/deployments/facets/DeployAddrResolverFacet.s.sol +36 -0
  7. package/scripts/deployments/facets/DeployArchitect.s.sol +1 -3
  8. package/scripts/deployments/facets/DeployContentHashResolverFacet.s.sol +34 -0
  9. package/scripts/deployments/facets/DeployCreateSpace.s.sol +2 -1
  10. package/scripts/deployments/facets/{DeploySpaceFactoryInit.s.sol → DeployExtendedResolverFacet.s.sol} +10 -10
  11. package/scripts/deployments/facets/DeployL1ResolverFacet.s.sol +61 -0
  12. package/scripts/deployments/facets/DeployL2RegistrarFacet.s.sol +56 -0
  13. package/scripts/deployments/facets/DeployL2RegistryFacet.s.sol +71 -0
  14. package/scripts/deployments/facets/DeployNodeRegistry.s.sol +4 -1
  15. package/scripts/deployments/facets/DeployTextResolverFacet.s.sol +34 -0
  16. package/scripts/deployments/utils/DeployDomainFeeHook.s.sol +78 -0
  17. package/scripts/interactions/InteractAlphaPost.s.sol +0 -7
  18. package/scripts/interactions/InteractBaseAlpha.s.sol +1 -14
  19. package/scripts/interactions/InteractDomainFee.s.sol +62 -0
  20. package/scripts/interactions/InteractSetStreamDistributionBalancingAdvantage.s.sol +42 -0
  21. package/scripts/interactions/InteractSetStreamDistributionRequiredOperators.s.sol +32 -0
  22. package/scripts/interactions/helpers/RiverConfigValues.sol +6 -0
  23. package/src/domains/facets/l1/IL1ResolverService.sol +20 -0
  24. package/src/domains/facets/l1/L1ResolverFacet.sol +100 -0
  25. package/src/domains/facets/l1/L1ResolverMod.sol +245 -0
  26. package/src/domains/facets/l2/AddrResolverFacet.sol +59 -0
  27. package/src/domains/facets/l2/ContentHashResolverFacet.sol +41 -0
  28. package/src/domains/facets/l2/ExtendedResolverFacet.sol +38 -0
  29. package/src/domains/facets/l2/IL2Registry.sol +79 -0
  30. package/src/domains/facets/l2/L2RegistryFacet.sol +203 -0
  31. package/src/domains/facets/l2/TextResolverFacet.sol +43 -0
  32. package/src/domains/facets/l2/modules/AddrResolverMod.sol +110 -0
  33. package/src/domains/facets/l2/modules/ContentHashResolverMod.sol +60 -0
  34. package/src/domains/facets/l2/modules/L2RegistryMod.sol +286 -0
  35. package/src/domains/facets/l2/modules/TextResolverMod.sol +62 -0
  36. package/src/domains/facets/l2/modules/VersionRecordMod.sol +42 -0
  37. package/src/domains/facets/registrar/IL2Registrar.sol +57 -0
  38. package/src/domains/facets/registrar/L2RegistrarFacet.sol +127 -0
  39. package/src/domains/facets/registrar/L2RegistrarMod.sol +224 -0
  40. package/src/domains/hooks/DomainFeeHook.sol +212 -0
  41. package/src/factory/facets/architect/Architect.sol +48 -29
  42. package/src/factory/facets/architect/IArchitect.sol +2 -21
  43. package/src/factory/facets/architect/ImplementationStorage.sol +12 -20
  44. package/src/factory/facets/create/CreateSpace.sol +5 -0
  45. package/src/factory/facets/create/CreateSpaceBase.sol +32 -8
  46. package/src/factory/facets/create/ICreateSpace.sol +5 -0
  47. package/src/factory/facets/fee/FeeManagerFacet.sol +10 -0
  48. package/src/factory/facets/fee/FeeTypesLib.sol +3 -0
  49. package/src/river/registry/facets/node/INodeRegistry.sol +25 -0
  50. package/src/river/registry/facets/node/NodeRegistry.sol +53 -2
  51. package/src/river/registry/libraries/RegistryStorage.sol +5 -0
  52. package/src/spaces/facets/ProtocolFeeLib.sol +56 -1
  53. package/src/spaces/facets/proxy/SpaceProxyInitializer.sol +6 -8
  54. package/src/spaces/facets/xchain/SpaceEntitlementGated.sol +9 -0
  55. package/LICENSE.txt +0 -21
  56. package/scripts/bytecode-diff/README.md +0 -182
  57. package/scripts/deployments/utils/DeploySpaceProxyInitializer.s.sol +0 -28
  58. package/scripts/readme.md +0 -289
  59. package/src/diamond/readme.md +0 -50
  60. package/src/factory/SpaceFactoryInit.sol +0 -17
  61. package/src/factory/facets/architect/ArchitectBase.sol +0 -95
@@ -0,0 +1,100 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {IExtendedResolver} from "@ensdomains/ens-contracts/resolvers/profiles/IExtendedResolver.sol";
6
+
7
+ // libraries
8
+ import {L1ResolverMod} from "./L1ResolverMod.sol";
9
+
10
+ // contracts
11
+ import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBase.sol";
12
+ import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
13
+
14
+ contract L1ResolverFacet is IExtendedResolver, OwnableBase, Facet {
15
+ using L1ResolverMod for L1ResolverMod.Layout;
16
+
17
+ /// @notice Initializes the resolver with a gateway URL and signer
18
+ /// @param gatewayUrl The URL of the CCIP gateway
19
+ /// @param gatewaySigner The address of the gateway signer
20
+ function __L1Resolver_init(
21
+ string calldata gatewayUrl,
22
+ address gatewaySigner
23
+ ) external onlyInitializing {
24
+ _addInterface(type(IExtendedResolver).interfaceId);
25
+ __L1Resolver_init_unchained(gatewayUrl, gatewaySigner);
26
+ }
27
+
28
+ function __L1Resolver_init_unchained(
29
+ string calldata gatewayUrl,
30
+ address gatewaySigner
31
+ ) internal {
32
+ L1ResolverMod.Layout storage $ = L1ResolverMod.getStorage();
33
+ $.setGatewayURL(gatewayUrl);
34
+ $.setGatewaySigner(gatewaySigner);
35
+ $.setNameWrapper();
36
+ }
37
+
38
+ /// @notice Sets the L2 registry for a given node
39
+ /// @param node The node to set the L2 registry for
40
+ /// @param chainId The chain ID of the L2 registry
41
+ /// @param registryAddress The address of the L2 registry
42
+ function setL2Registry(bytes32 node, uint64 chainId, address registryAddress) external {
43
+ L1ResolverMod.getStorage().setL2Registry(node, chainId, registryAddress);
44
+ }
45
+
46
+ /// @notice Sets the gateway URL
47
+ /// @param gatewayUrl The URL of the CCIP gateway
48
+ function setGatewayURL(string calldata gatewayUrl) external onlyOwner {
49
+ L1ResolverMod.getStorage().setGatewayURL(gatewayUrl);
50
+ }
51
+
52
+ /// @notice Sets the gateway signer
53
+ /// @param gatewaySigner The address of the gateway signer
54
+ function setGatewaySigner(address gatewaySigner) external onlyOwner {
55
+ L1ResolverMod.getStorage().setGatewaySigner(gatewaySigner);
56
+ }
57
+
58
+ /// @inheritdoc IExtendedResolver
59
+ /// @dev Always reverts with OffchainLookup to trigger CCIP-Read
60
+ function resolve(
61
+ bytes calldata name,
62
+ bytes calldata data
63
+ ) external view returns (bytes memory) {
64
+ return L1ResolverMod.getStorage().resolve(name, data, this.resolveWithProof.selector);
65
+ }
66
+
67
+ /// @notice Callback for CCIP-Read to verify and return the resolved data
68
+ /// @param response The response from the gateway (result, expires, sig)
69
+ /// @param extraData The original request data for signature verification
70
+ /// @return The verified resolved data
71
+ function resolveWithProof(
72
+ bytes calldata response,
73
+ bytes calldata extraData
74
+ ) external view returns (bytes memory) {
75
+ return L1ResolverMod.getStorage().resolveWithProof(response, extraData);
76
+ }
77
+
78
+ /// @notice Returns the L2 registry for a given node
79
+ /// @param node The node to get the L2 registry for
80
+ /// @return chainId The chain ID of the L2 registry
81
+ /// @return registryAddress The address of the L2 registry
82
+ function getL2Registry(
83
+ bytes32 node
84
+ ) external view returns (uint64 chainId, address registryAddress) {
85
+ L1ResolverMod.L2Registry memory registry = L1ResolverMod.getStorage().registryByNode[node];
86
+ return (registry.chainId, registry.registryAddress);
87
+ }
88
+
89
+ /// @notice Returns the gateway signer address
90
+ /// @return The address of the gateway signer
91
+ function getGatewaySigner() external view returns (address) {
92
+ return L1ResolverMod.getStorage().gatewaySigner;
93
+ }
94
+
95
+ /// @notice Returns the gateway URL
96
+ /// @return The URL of the CCIP gateway
97
+ function getGatewayURL() external view returns (string memory) {
98
+ return L1ResolverMod.getStorage().gatewayUrl;
99
+ }
100
+ }
@@ -0,0 +1,245 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {INameWrapper} from "@ensdomains/ens-contracts/wrapper/INameWrapper.sol";
6
+ import {IAddrResolver} from "@ensdomains/ens-contracts/resolvers/profiles/IAddrResolver.sol";
7
+ import {IL1ResolverService} from "./IL1ResolverService.sol";
8
+
9
+ // libraries
10
+ import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
11
+ import {NameCoder} from "@ensdomains/ens-contracts/utils/NameCoder.sol";
12
+ import {LibString} from "solady/utils/LibString.sol";
13
+ import {OffchainLookup} from "@ensdomains/ens-contracts/ccipRead/EIP3668.sol";
14
+ import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
15
+
16
+ // contracts
17
+ import {ENS} from "@ensdomains/ens-contracts/registry/ENS.sol";
18
+
19
+ library L1ResolverMod {
20
+ using CustomRevert for bytes4;
21
+ using LibString for string;
22
+
23
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
24
+ /* STORAGE */
25
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
26
+
27
+ // keccak256(abi.encode(uint256(keccak256("towns.domains.resolver.storage")) - 1)) & ~bytes32(uint256(0xff))
28
+ bytes32 internal constant STORAGE_SLOT =
29
+ 0xad5af01a9aec1f0fbc422f62e21406a58de498f1c0ee36ca202bc49bd857fd00;
30
+
31
+ // ENS protocol address
32
+ ENS internal constant ens = ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e);
33
+
34
+ // ENS name wrapper node
35
+ bytes32 internal constant NAME_WRAPPER_NODE =
36
+ 0xdee478ba2734e34d81c6adc77a32d75b29007895efa2fe60921f1c315e1ec7d9; // namewrapper.eth
37
+
38
+ /// @notice L2 registry information
39
+ /// @param chainId The chain ID of the L2 registry
40
+ /// @param registryAddress The address of the L2 registry
41
+ struct L2Registry {
42
+ uint64 chainId;
43
+ address registryAddress;
44
+ }
45
+
46
+ /// @notice Storage layout for the L1Resolver
47
+ /// @param gatewayUrl The URL of the CCIP gateway
48
+ /// @param gatewaySigner The address of the gateway signer
49
+ /// @param nameWrapper The name wrapper contract
50
+ /// @param registryByNode Mapping of node to L2 registry information
51
+ struct Layout {
52
+ string gatewayUrl;
53
+ address gatewaySigner;
54
+ INameWrapper nameWrapper;
55
+ mapping(bytes32 node => L2Registry l2Registry) registryByNode;
56
+ }
57
+
58
+ /// @notice Returns the storage layout for the L1Resolver
59
+ /// @return $ The storage layout
60
+ /// @custom:storage-location erc7201:towns.domains.resolver.storage
61
+ function getStorage() internal pure returns (Layout storage $) {
62
+ assembly {
63
+ $.slot := STORAGE_SLOT
64
+ }
65
+ }
66
+
67
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
68
+ /* EVENTS */
69
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
70
+
71
+ event GatewayURLSet(string gatewayUrl);
72
+ event GatewaySignerSet(address gatewaySigner);
73
+ event L2RegistrySet(
74
+ bytes32 indexed node,
75
+ uint64 indexed chainId,
76
+ address indexed registryAddress
77
+ );
78
+
79
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
80
+ /* ERRORS */
81
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
82
+
83
+ error L1Resolver__InvalidGatewayURL();
84
+ error L1Resolver__InvalidGatewaySigner();
85
+ error L1Resolver__InvalidL2Registry();
86
+ error L1Resolver__InvalidName();
87
+ error L1Resolver__InvalidNameWrapper();
88
+ error L1Resolver__InvalidNode();
89
+ error L1Resolver__InvalidChainId();
90
+ error L1Resolver__InvalidOwner();
91
+ error L1Resolver__SignatureExpired();
92
+ error L1Resolver__InvalidSignature();
93
+
94
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
95
+ /* FUNCTIONS */
96
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
97
+ function setGatewayURL(Layout storage $, string calldata gatewayUrl) internal {
98
+ if (bytes(gatewayUrl).length == 0) L1Resolver__InvalidGatewayURL.selector.revertWith();
99
+ $.gatewayUrl = gatewayUrl;
100
+ emit GatewayURLSet(gatewayUrl);
101
+ }
102
+
103
+ function setGatewaySigner(Layout storage $, address gatewaySigner) internal {
104
+ if (gatewaySigner == address(0)) L1Resolver__InvalidGatewaySigner.selector.revertWith();
105
+ $.gatewaySigner = gatewaySigner;
106
+ emit GatewaySignerSet(gatewaySigner);
107
+ }
108
+
109
+ function setNameWrapper(Layout storage $) internal {
110
+ address wrapperResolver = ens.resolver(NAME_WRAPPER_NODE);
111
+ if (wrapperResolver == address(0)) L1Resolver__InvalidNameWrapper.selector.revertWith();
112
+ address wrapperAddress = IAddrResolver(wrapperResolver).addr(NAME_WRAPPER_NODE);
113
+ if (wrapperAddress == address(0)) L1Resolver__InvalidNameWrapper.selector.revertWith();
114
+ $.nameWrapper = INameWrapper(wrapperAddress);
115
+ }
116
+
117
+ function setL2Registry(
118
+ Layout storage $,
119
+ bytes32 node,
120
+ uint64 chainId,
121
+ address registryAddress
122
+ ) internal {
123
+ if (node == bytes32(0)) L1Resolver__InvalidNode.selector.revertWith();
124
+ if (chainId == 0) L1Resolver__InvalidChainId.selector.revertWith();
125
+ if (registryAddress == address(0)) L1Resolver__InvalidL2Registry.selector.revertWith();
126
+
127
+ address owner = ens.owner(node);
128
+
129
+ if (owner == address($.nameWrapper)) {
130
+ owner = $.nameWrapper.ownerOf(uint256(node));
131
+ }
132
+
133
+ if (owner != msg.sender) L1Resolver__InvalidOwner.selector.revertWith();
134
+
135
+ $.registryByNode[node] = L2Registry(chainId, registryAddress);
136
+ emit L2RegistrySet(node, chainId, registryAddress);
137
+ }
138
+
139
+ /// @notice Resolve a name via CCIP-Read
140
+ /// @dev Always reverts with OffchainLookup to trigger CCIP-Read flow
141
+ /// @param $ The storage layout
142
+ /// @param name The DNS-encoded name to resolve
143
+ /// @param data The resolution data (e.g., addr(bytes32) calldata)
144
+ /// @param callbackSelector The selector for the callback function (resolveWithProof)
145
+ /// @return This function always reverts, return type is for interface compatibility
146
+ function resolve(
147
+ Layout storage $,
148
+ bytes calldata name,
149
+ bytes calldata data,
150
+ bytes4 callbackSelector
151
+ ) internal view returns (bytes memory) {
152
+ string memory decodedName = NameCoder.decode(name); // 'sub.name.eth'
153
+ string[] memory parts = LibString.split(decodedName, ".");
154
+
155
+ // Require at least 2 parts (2LD + TLD)
156
+ if (parts.length < 2) L1Resolver__InvalidName.selector.revertWith();
157
+
158
+ // get the 2LD + TLD (final 2 parts), regardless of how many labels the name has
159
+ string memory parentName = string.concat(
160
+ parts[parts.length - 2],
161
+ ".",
162
+ parts[parts.length - 1]
163
+ );
164
+
165
+ bytes memory parentDnsName = NameCoder.encode(parentName);
166
+ bytes32 parentNode = NameCoder.namehash(parentDnsName, 0);
167
+
168
+ L2Registry memory registry = $.registryByNode[parentNode];
169
+ if (registry.registryAddress == address(0)) {
170
+ L1Resolver__InvalidL2Registry.selector.revertWith();
171
+ }
172
+
173
+ // Build callData for the gateway
174
+ bytes memory callData = abi.encodeCall(
175
+ IL1ResolverService.stuffedResolveCall,
176
+ (name, data, registry.chainId, registry.registryAddress)
177
+ );
178
+
179
+ // Build the gateway URL array
180
+ string[] memory urls = new string[](1);
181
+ urls[0] = $.gatewayUrl;
182
+
183
+ // Revert with OffchainLookup to trigger CCIP-Read
184
+ revert OffchainLookup(
185
+ address(this),
186
+ urls,
187
+ callData,
188
+ callbackSelector,
189
+ callData // extraData same as callData for verification
190
+ );
191
+ }
192
+
193
+ /// @notice Verifies the gateway response and returns the resolved data
194
+ /// @param $ The storage layout
195
+ /// @param response The gateway response (result, expires, sig)
196
+ /// @param extraData The original request data for signature verification
197
+ /// @return result The verified result data
198
+ function resolveWithProof(
199
+ Layout storage $,
200
+ bytes calldata response,
201
+ bytes calldata extraData
202
+ ) internal view returns (bytes memory result) {
203
+ address signer = $.gatewaySigner;
204
+ if (signer == address(0)) L1Resolver__InvalidGatewaySigner.selector.revertWith();
205
+
206
+ // Decode the gateway response
207
+ uint64 expires;
208
+ bytes memory sig;
209
+ (result, expires, sig) = abi.decode(response, (bytes, uint64, bytes));
210
+
211
+ // Check expiration
212
+ if (block.timestamp > expires) {
213
+ L1Resolver__SignatureExpired.selector.revertWith();
214
+ }
215
+
216
+ // Create the signature hash (matching the gateway's signing format)
217
+ bytes32 hash = _makeSignatureHash(
218
+ address(this),
219
+ expires,
220
+ keccak256(extraData),
221
+ keccak256(result)
222
+ );
223
+
224
+ // Verify signature using SignatureCheckerLib
225
+ // Supports both EOA (ECDSA) and smart contract wallets (ERC-1271)
226
+ if (!SignatureCheckerLib.isValidSignatureNow(signer, hash, sig)) {
227
+ L1Resolver__InvalidSignature.selector.revertWith();
228
+ }
229
+ }
230
+
231
+ /// @dev Generates a hash for signing/verifying gateway responses
232
+ /// @param target The address the signature is for (this contract)
233
+ /// @param expires The expiration timestamp
234
+ /// @param request The original request data
235
+ /// @param result The result data from the gateway
236
+ /// @return The hash to be signed/verified
237
+ function _makeSignatureHash(
238
+ address target,
239
+ uint64 expires,
240
+ bytes32 request,
241
+ bytes32 result
242
+ ) private pure returns (bytes32) {
243
+ return keccak256(abi.encodePacked(hex"1900", target, expires, request, result));
244
+ }
245
+ }
@@ -0,0 +1,59 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {IAddrResolver} from "@ensdomains/ens-contracts/resolvers/profiles/IAddrResolver.sol";
6
+ import {IAddressResolver} from "@ensdomains/ens-contracts/resolvers/profiles/IAddressResolver.sol";
7
+
8
+ // libraries
9
+ import {AddrResolverMod} from "./modules/AddrResolverMod.sol";
10
+ import {L2RegistryMod} from "./modules/L2RegistryMod.sol";
11
+ import {VersionRecordMod} from "./modules/VersionRecordMod.sol";
12
+
13
+ // contracts
14
+ import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
15
+
16
+ /// @title AddrResolverFacet
17
+ /// @notice ENS address resolver facet for storing and retrieving blockchain addresses on L2
18
+ /// @dev Implements IAddrResolver (ETH only) and IAddressResolver (multi-coin) with versioned records
19
+ contract AddrResolverFacet is IAddrResolver, IAddressResolver, Facet {
20
+ using AddrResolverMod for AddrResolverMod.Layout;
21
+ using VersionRecordMod for VersionRecordMod.Layout;
22
+
23
+ /// @notice Initializes the facet by registering resolver interfaces
24
+ function __AddrResolverFacet_init() external onlyInitializing {
25
+ _addInterface(type(IAddrResolver).interfaceId);
26
+ _addInterface(type(IAddressResolver).interfaceId);
27
+ }
28
+
29
+ /// @notice Sets the address for a node and coin type (SLIP-44 standard)
30
+ /// @param node The ENS node to update
31
+ /// @param coinType The SLIP-44 coin type (e.g., 60 for ETH, 0 for BTC)
32
+ /// @param a The address bytes to set
33
+ function setAddr(bytes32 node, uint256 coinType, bytes memory a) external {
34
+ L2RegistryMod.onlyAuthorized(node);
35
+ uint64 version = VersionRecordMod.getStorage().recordVersions[node];
36
+ AddrResolverMod.getStorage().setAddr(version, node, coinType, a);
37
+ }
38
+
39
+ /// @notice Sets the Ethereum address (coinType 60) for a node
40
+ /// @param node The ENS node to update
41
+ /// @param a The Ethereum address to set
42
+ function setAddr(bytes32 node, address a) external {
43
+ L2RegistryMod.onlyAuthorized(node);
44
+ uint64 version = VersionRecordMod.getStorage().recordVersions[node];
45
+ AddrResolverMod.getStorage().setAddr(version, node, a);
46
+ }
47
+
48
+ /// @inheritdoc IAddressResolver
49
+ function addr(bytes32 node, uint256 coinType) external view returns (bytes memory) {
50
+ uint64 version = VersionRecordMod.getStorage().recordVersions[node];
51
+ return AddrResolverMod.getStorage().addr(version, node, coinType);
52
+ }
53
+
54
+ /// @inheritdoc IAddrResolver
55
+ function addr(bytes32 node) external view returns (address payable) {
56
+ uint64 version = VersionRecordMod.getStorage().recordVersions[node];
57
+ return AddrResolverMod.getStorage().addr(version, node);
58
+ }
59
+ }
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {IContentHashResolver} from "@ensdomains/ens-contracts/resolvers/profiles/IContentHashResolver.sol";
6
+
7
+ // libraries
8
+ import {ContentHashResolverMod} from "./modules/ContentHashResolverMod.sol";
9
+ import {L2RegistryMod} from "./modules/L2RegistryMod.sol";
10
+ import {VersionRecordMod} from "./modules/VersionRecordMod.sol";
11
+
12
+ // contracts
13
+ import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
14
+
15
+ /// @title ContentHashResolverFacet
16
+ /// @notice ENS content hash resolver facet for storing IPFS/IPNS/Swarm hashes on L2
17
+ /// @dev Implements IContentHashResolver with versioned records
18
+ contract ContentHashResolverFacet is IContentHashResolver, Facet {
19
+ using ContentHashResolverMod for ContentHashResolverMod.Layout;
20
+ using VersionRecordMod for VersionRecordMod.Layout;
21
+
22
+ /// @notice Initializes the facet by registering resolver interface
23
+ function __ContentHashResolverFacet_init() external onlyInitializing {
24
+ _addInterface(type(IContentHashResolver).interfaceId);
25
+ }
26
+
27
+ /// @notice Sets the content hash for a node
28
+ /// @param node The ENS node to update
29
+ /// @param hash The content hash bytes (IPFS, IPNS, Swarm, etc.)
30
+ function setContenthash(bytes32 node, bytes calldata hash) external {
31
+ L2RegistryMod.onlyAuthorized(node);
32
+ uint64 version = VersionRecordMod.getStorage().recordVersions[node];
33
+ ContentHashResolverMod.getStorage().setContenthash(version, node, hash);
34
+ }
35
+
36
+ /// @inheritdoc IContentHashResolver
37
+ function contenthash(bytes32 node) external view returns (bytes memory) {
38
+ uint64 version = VersionRecordMod.getStorage().recordVersions[node];
39
+ return ContentHashResolverMod.getStorage().contenthash(version, node);
40
+ }
41
+ }
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {IExtendedResolver} from "@ensdomains/ens-contracts/resolvers/profiles/IExtendedResolver.sol";
6
+
7
+ // libraries
8
+
9
+ // contracts
10
+ import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
11
+
12
+ /// @title ExtendedResolverFacet
13
+ /// @notice Implements EIP-3668 (CCIP Read) resolve function for on-chain resolution without offchain lookup
14
+ /// @dev Executes the encoded resolver call directly via staticcall and returns the result or propagates revert
15
+ contract ExtendedResolverFacet is IExtendedResolver, Facet {
16
+ /// @notice Initializes the facet by registering the IExtendedResolver interface
17
+ function __ExtendedResolverFacet_init() external onlyInitializing {
18
+ _addInterface(type(IExtendedResolver).interfaceId);
19
+ }
20
+
21
+ /// @notice Resolves ENS data by executing the encoded call directly on this contract
22
+ /// @param data The encoded resolver function call (e.g., addr(bytes32), text(bytes32,string))
23
+ /// @return The result of the resolver call
24
+ function resolve(
25
+ bytes memory /* name */,
26
+ bytes memory data
27
+ ) external view returns (bytes memory) {
28
+ (bool success, bytes memory result) = address(this).staticcall(data);
29
+ if (success) {
30
+ return result;
31
+ } else {
32
+ // Revert with the reason provided by the call
33
+ assembly {
34
+ revert(add(result, 0x20), mload(result))
35
+ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,79 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {IERC721A} from "../../../diamond/facets/token/ERC721A/IERC721A.sol";
6
+
7
+ // libraries
8
+
9
+ // contracts
10
+
11
+ /// @title IL2Registry
12
+ /// @notice Interface for L2 ENS-compatible domain registry that mints subdomains as NFTs
13
+ /// @dev Extends IERC721A to provide ERC721 functionality and adds domain-specific operations
14
+ interface IL2Registry is IERC721A {
15
+ /// @notice Creates a subdomain under an existing domain and optionally sets resolver records
16
+ /// @param domainHash The parent domain's namehash
17
+ /// @param subdomain The subdomain label (e.g., "alice" for "alice.towns.eth")
18
+ /// @param owner The address that will own the subdomain NFT
19
+ /// @param records Encoded resolver calls to set initial records (addr, text, etc.)
20
+ /// @param metadata Arbitrary bytes for registrar use (e.g., expiration, tier, etc.)
21
+ function createSubdomain(
22
+ bytes32 domainHash,
23
+ string calldata subdomain,
24
+ address owner,
25
+ bytes[] calldata records,
26
+ bytes calldata metadata
27
+ ) external;
28
+
29
+ /// @notice Adds an address as an approved registrar that can mint subdomains
30
+ /// @param registrar The address to approve as a registrar
31
+ function addRegistrar(address registrar) external;
32
+
33
+ /// @notice Removes an address from the approved registrars list
34
+ /// @param registrar The address to remove
35
+ function removeRegistrar(address registrar) external;
36
+
37
+ /// @notice Sets or updates the metadata for a subdomain (registrar only)
38
+ /// @dev Metadata is arbitrary bytes that the registrar can interpret (e.g., expiration, tier, etc.)
39
+ /// @param node The namehash of the subdomain
40
+ /// @param data The metadata bytes to store
41
+ function setMetadata(bytes32 node, bytes calldata data) external;
42
+
43
+ /// @notice Returns the metadata bytes for a subdomain
44
+ /// @param node The namehash of the subdomain
45
+ /// @return The metadata bytes (empty if not set)
46
+ function getMetadata(bytes32 node) external view returns (bytes memory);
47
+
48
+ /// @notice Returns the owner of the root domain
49
+ /// @return The address that owns the root domain NFT
50
+ function domainOwner() external view returns (address);
51
+
52
+ /// @notice Returns the owner of a subdomain by its namehash
53
+ /// @param nameHash The namehash of the subdomain
54
+ /// @return The address that owns the subdomain NFT
55
+ function subdomainOwner(bytes32 nameHash) external view returns (address);
56
+
57
+ /// @notice Returns the namehash of the base domain
58
+ /// @return The namehash of the base domain
59
+ function baseDomainHash() external view returns (bytes32);
60
+
61
+ /// @notice Computes the namehash of a domain name
62
+ /// @param node The domain name (e.g., "alice.towns.eth")
63
+ /// @return The namehash (bytes32)
64
+ function namehash(string calldata node) external pure returns (bytes32);
65
+
66
+ /// @notice Decodes a DNS-encoded name to a human-readable string
67
+ /// @param node The DNS-encoded name bytes
68
+ /// @return The decoded domain name
69
+ function decodeName(bytes calldata node) external pure returns (string memory);
70
+
71
+ /// @notice Helper to derive a node from a parent node and label
72
+ /// @param domainHash The namehash of the domain, e.g. `namehash("name.eth")` for "name.eth"
73
+ /// @param subdomain The label of the subnode, e.g. "x" for "x.name.eth"
74
+ /// @return The resulting subnode, e.g. `namehash("x.name.eth")` for "x.name.eth"
75
+ function encodeSubdomain(
76
+ bytes32 domainHash,
77
+ string calldata subdomain
78
+ ) external pure returns (bytes32);
79
+ }