@ethlimo/ens-hooks-release-testing 0.1.11

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,82 @@
1
+ # Changelog
2
+
3
+ ## [0.1.11](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.10...v0.1.11) (2026-02-18)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * publish ([#23](https://github.com/ethlimo/ens-hooks-release-testing/issues/23)) ([30c62ee](https://github.com/ethlimo/ens-hooks-release-testing/commit/30c62ee3116b99a58bbe69ab665b32a288aaefde))
9
+
10
+ ## [0.1.10](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.9...v0.1.10) (2026-02-18)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * test publish ([#21](https://github.com/ethlimo/ens-hooks-release-testing/issues/21)) ([31fd33c](https://github.com/ethlimo/ens-hooks-release-testing/commit/31fd33ced774bfaa943d1c3e558b0ce531e8b6ad))
16
+
17
+ ## [0.1.9](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.8...v0.1.9) (2026-02-18)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * separate actions ([#19](https://github.com/ethlimo/ens-hooks-release-testing/issues/19)) ([42ae684](https://github.com/ethlimo/ens-hooks-release-testing/commit/42ae684e8982b6b3e745805729429536f3d073bb))
23
+
24
+ ## [0.1.8](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.7...v0.1.8) (2026-02-17)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * trigger release ([#17](https://github.com/ethlimo/ens-hooks-release-testing/issues/17)) ([802d07c](https://github.com/ethlimo/ens-hooks-release-testing/commit/802d07c14ecf75eb0e814ecb6c4a8b675a71d32f))
30
+
31
+ ## [0.1.7](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.6...v0.1.7) (2026-02-17)
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * trigger release ([#14](https://github.com/ethlimo/ens-hooks-release-testing/issues/14)) ([c25b5f2](https://github.com/ethlimo/ens-hooks-release-testing/commit/c25b5f278fb6c661f1a7568b285ac26e925899d5))
37
+
38
+ ## [0.1.6](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.5...v0.1.6) (2026-02-17)
39
+
40
+
41
+ ### Bug Fixes
42
+
43
+ * test trusted publishing ([#11](https://github.com/ethlimo/ens-hooks-release-testing/issues/11)) ([17ab7e5](https://github.com/ethlimo/ens-hooks-release-testing/commit/17ab7e5785555f9d228fabf1dc872ce77c3708a8))
44
+
45
+ ## [0.1.5](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.4...v0.1.5) (2026-02-17)
46
+
47
+
48
+ ### Bug Fixes
49
+
50
+ * disable provenance for testing ([#9](https://github.com/ethlimo/ens-hooks-release-testing/issues/9)) ([ccd2557](https://github.com/ethlimo/ens-hooks-release-testing/commit/ccd2557667a79fa457d9b97932e88db40c5dab85))
51
+
52
+ ## [0.1.4](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.3...v0.1.4) (2026-02-17)
53
+
54
+
55
+ ### Bug Fixes
56
+
57
+ * test another release (yet again) ([#7](https://github.com/ethlimo/ens-hooks-release-testing/issues/7)) ([ee58ec5](https://github.com/ethlimo/ens-hooks-release-testing/commit/ee58ec5cd4a15027a89291420223e0c2548b9459))
58
+
59
+ ## [0.1.3](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.2...v0.1.3) (2026-02-17)
60
+
61
+
62
+ ### Bug Fixes
63
+
64
+ * adjust package name ([6fa3eed](https://github.com/ethlimo/ens-hooks-release-testing/commit/6fa3eed077da53be6af8fd9b1a03a2d8832ff2aa))
65
+ * adjust package name ([0189730](https://github.com/ethlimo/ens-hooks-release-testing/commit/018973049aa0c93c00247e3c97aa79adec6c7d93))
66
+ * testing release ([8df0ba5](https://github.com/ethlimo/ens-hooks-release-testing/commit/8df0ba528fbe8ed56e3d14649eaf3ff8d1ae9b69))
67
+
68
+ ## [0.1.2](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.1...v0.1.2) (2026-02-17)
69
+
70
+
71
+ ### Bug Fixes
72
+
73
+ * adjust description to trigger release ([962d47f](https://github.com/ethlimo/ens-hooks-release-testing/commit/962d47f26532d4873bb554081670ea2201c2c261))
74
+ * adjust description to trigger release ([dd5fa06](https://github.com/ethlimo/ens-hooks-release-testing/commit/dd5fa0600abe82d99bf960263f5c5ee8bf786c9f))
75
+ * modify to trigger release ([6b6f15f](https://github.com/ethlimo/ens-hooks-release-testing/commit/6b6f15f6b62f9d2792ce295d5db60f1acc66f0dc))
76
+
77
+ ## [0.1.1](https://github.com/ethlimo/ens-hooks-release-testing/compare/v0.1.0...v0.1.1) (2026-02-17)
78
+
79
+
80
+ ### Features
81
+
82
+ * initial release ([d43c7b1](https://github.com/ethlimo/ens-hooks-release-testing/commit/d43c7b1c10a436a526b73614ba1de434e7ee9641))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Last Mile Labs, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # ENS Hooks - EIP-8121 Implementation
2
+
3
+ TypeScript library for encoding, decoding, and executing [EIP-8121](./notes/EIP-8121.md) hooks with ERC-7930 interoperable addresses. Enables cross-chain function calls for ENS contenthash resolution with optional trust verification.
4
+
5
+ ## Overview
6
+
7
+ This library implements [EIP-8121](./notes/EIP-8121.md) hooks, a specification for cross-chain function calls. A hook fully specifies what function to call, with what parameters, on which contract, on which chain using [ERC-7930](https://eips.ethereum.org/EIPS/eip-7930) interoperable addresses.
8
+
9
+ **Key Features:**
10
+ - EIP-8121 hook encoding (selector `0x6113bfa3`)
11
+ - ERC-7930 interoperable addresses
12
+ - Multi-chain execution (EIP-155)
13
+ - 0-2 fixed-size primitive parameters
14
+ - Optional trust verification
15
+ - Contenthash encoding/decoding
16
+
17
+ ## Scope
18
+
19
+ This is a **focused implementation** for contenthash resolution. See [LIMITATIONS.md](./notes/LIMITATIONS.md) for scope restrictions:
20
+ - 0-2 parameters of fixed-size primitives and strings (bool, address, uintN, intN, bytesN, string)
21
+ - String parameters limited to 512 characters
22
+ - EIP-155 chains only (EVM)
23
+ - Bytes return type only
24
+ - No recursive resolution
25
+ - No struct or dynamic type support (dynamic bytes, arrays)
26
+ - Requires ERC-3668 (CCIP-Read) enabled provider
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @ethlimo/ens-hooks
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Encoding a Hook
37
+
38
+ ```typescript
39
+ import { encodeHook, EIP8121Target } from '@ethlimo/ens-hooks';
40
+
41
+ const target: EIP8121Target = {
42
+ chainId: 1, // Ethereum mainnet
43
+ address: "0x1234567890123456789012345678901234567890"
44
+ };
45
+
46
+ const hookData = await encodeHook(
47
+ "data(bytes32)", // function signature
48
+ "data(0x1234...)", // function call with values
49
+ "(bytes)", // return type
50
+ target
51
+ );
52
+ ```
53
+
54
+ ### Decoding and Executing a Hook
55
+
56
+ ```typescript
57
+ import { decodeHook, executeHook, ProviderMap } from '@ethlimo/ens-hooks';
58
+ import { ethers, namehash } from 'ethers';
59
+
60
+ // Decode the hook
61
+ const hook = await decodeHook(hookData);
62
+
63
+ // Set up provider map for cross-chain calls
64
+ const providerMap: ProviderMap = new Map([
65
+ [1, new ethers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/...")]
66
+ ]);
67
+
68
+ // Execute the hook
69
+ const result = await executeHook(hook!, {
70
+ params: [namehash("example.eth")],
71
+ providerMap
72
+ });
73
+
74
+ if (result._tag === "HookExecutionResult") {
75
+ console.log("Result:", result.data);
76
+ } else {
77
+ console.error("Error:", result.message);
78
+ }
79
+ ```
80
+
81
+ ### Parameter Support
82
+
83
+ Hooks support functions with 0-2 parameters of fixed-size Solidity primitives:
84
+ - **0 parameters**: `getData()` - Global data
85
+ - **1 parameter**: `data(bytes32)` - Node-specific data
86
+ - **2 parameters**: `data(bytes32,bytes32)` - Node with additional context
87
+
88
+ **Recommended Parameter Patterns:**
89
+ - `()` - Zero parameters for global data
90
+ - `(nodehash: bytes32)` - Single nodehash parameter for node-specific data
91
+ - `(nodehash: bytes32, hashOfContent: bytes32)` - Two parameters for cache-busting
92
+ - `hashOfContent` should be either:
93
+ - A hash of the expected content (for integrity verification)
94
+ - An autoincrement value (for cache invalidation in web gateways)
95
+
96
+ **Supported Types:** `bool`, `address`, `uint8-256`, `int8-256`, `bytes1-32`, `string` (max 512 chars)
97
+
98
+ ```typescript
99
+ import { executeHook } from '@ethlimo/ens-hooks';
100
+ import { namehash } from 'ethers';
101
+
102
+ // 0-parameter hook
103
+ const globalHook = await encodeHook(
104
+ "getData()",
105
+ "getData()",
106
+ "(bytes)",
107
+ { chainId: 1, address: "0x..." }
108
+ );
109
+
110
+ const globalResult = await executeHook(await decodeHook(globalHook)!, {
111
+ providerMap // No params
112
+ });
113
+
114
+ // 1-parameter hook (node-specific data)
115
+ const node = namehash("example.eth");
116
+ const nodeHook = await encodeHook(
117
+ "data(bytes32)",
118
+ `data(${node})`,
119
+ "(bytes)",
120
+ { chainId: 1, address: "0x..." }
121
+ );
122
+
123
+ const nodeResult = await executeHook(await decodeHook(nodeHook)!, {
124
+ params: [node], // Single parameter array
125
+ providerMap
126
+ });
127
+
128
+ // 2-parameter hook (with cache-busting)
129
+ const hashOfContent = "0xabcd..."; // Hash or autoincrement
130
+ const twoParamHook = await encodeHook(
131
+ "data(bytes32,bytes32)",
132
+ `data(${node},${hashOfContent})`,
133
+ "(bytes)",
134
+ { chainId: 1, address: "0x..." }
135
+ );
136
+
137
+ const twoParamResult = await executeHook(await decodeHook(twoParamHook)!, {
138
+ params: [node, hashOfContent], // Two parameter array
139
+ providerMap
140
+ });
141
+ ```
142
+
143
+ ### Trust Verification
144
+
145
+ Optionally verify hook targets against a trusted list:
146
+
147
+ ```typescript
148
+ import { executeHook, createTrustedTargets } from '@ethlimo/ens-hooks';
149
+
150
+ // Create trusted targets set
151
+ const trustedTargets = createTrustedTargets([
152
+ {
153
+ chainId: 1,
154
+ address: "0x1234567890123456789012345678901234567890",
155
+ description: "ENS Public Resolver"
156
+ }
157
+ ]);
158
+
159
+ // Execute with trust verification
160
+ const result = await executeHook(hook!, {
161
+ params: [namehash("example.eth")],
162
+ providerMap,
163
+ trustedTargets
164
+ });
165
+ ```
166
+
167
+ Three verification modes supported:
168
+ 1. **Array**: `TrustedTarget[]` - Verified via array iteration
169
+ 2. **Set**: `Set<string>` - Fast lookup with "chainId:address" keys
170
+ 3. **Function**: `(target: EIP8121Target) => boolean` - Custom logic
171
+
172
+ ## API Reference
173
+
174
+ ### Hook Encoding/Decoding
175
+ - `encodeHook()` - Encode EIP-8121 hook (bytes format, 5 parameters)
176
+ - `decodeHook()` - Decode EIP-8121 hook (bytes format)
177
+ - `computeSelector()` - Compute 4-byte function selector from signature
178
+ - `parseParameterTypes()` - Parse and validate parameter types (0-2 fixed-size primitives)
179
+ - `validateFunctionCallMatchesSignature()` - Strict validation of call vs signature
180
+
181
+ ### Hook Execution
182
+ - `executeHook(hook, options)` - Execute hook on target chain with type-safe options:
183
+ - For 0-parameter hooks: `{ providerMap, trustedTargets?, throwOnUntrusted? }`
184
+ - For 1-parameter hooks: `{ params: [string], providerMap, trustedTargets?, throwOnUntrusted? }`
185
+ - For 2-parameter hooks: `{ params: [string, string], providerMap, trustedTargets?, throwOnUntrusted? }`
186
+ - `validateHook()` - Validate parameter count (0-2) and types (fixed-size primitives)
187
+ - `detectAndDecodeHook()` - Detect and decode hooks from bytes format
188
+
189
+ ### Contenthash Support
190
+ - `encodeEIP8121HookForContenthash()` - Wrap hook for contenthash storage
191
+ - `tryDecodeEIP8121HookFromContenthash()` - Extract hook from contenthash
192
+ - `encodeDataUri()` - Encode plain URI for contenthash
193
+ - `tryDecodeDataUri()` - Decode plain URI from contenthash
194
+
195
+ ### Trust Verification
196
+ - `verifyTrustedTarget()` - Check if target is trusted
197
+ - `createTrustedTargets()` - Create fast-lookup Set from array
198
+ - `createTargetKey()` - Generate "chainId:address" key
199
+
200
+ ## Testing
201
+
202
+ ```bash
203
+ npm test
204
+ ```
205
+
206
+ 69 tests covering encoding, decoding, execution, and trust verification.
207
+
208
+ ## Specification
209
+
210
+ See [notes/EIP-8121.md](./notes/EIP-8121.md) for the full EIP-8121 specification and [notes/LIMITATIONS.md](./notes/LIMITATIONS.md) for implementation scope.
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,155 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import '@openzeppelin/contracts/access/Ownable.sol';
5
+
6
+ /**
7
+ * Test contract with all permutations of 0-2 fixed-size primitive parameters.
8
+ * For local node testing only - not deployed to testnets.
9
+ */
10
+ contract AllParameterPermutationsHookTarget is Ownable {
11
+ // Storage for each permutation
12
+ bytes private data0;
13
+ mapping(bytes32 => bytes) private data1Bytes32;
14
+ mapping(address => bytes) private data1Address;
15
+ mapping(uint256 => bytes) private data1Uint256;
16
+ mapping(bool => bytes) private data1Bool;
17
+ mapping(bytes32 => mapping(bytes32 => bytes)) private data2Bytes32Bytes32;
18
+ mapping(bytes32 => mapping(uint256 => bytes)) private data2Bytes32Uint256;
19
+ mapping(bytes32 => mapping(address => bytes)) private data2Bytes32Address;
20
+ mapping(address => mapping(uint256 => bytes)) private data2AddressUint256;
21
+ mapping(uint256 => mapping(uint256 => bytes)) private data2Uint256Uint256;
22
+ mapping(bool => mapping(bytes32 => bytes)) private data2BoolBytes32;
23
+
24
+ // ========================================================================
25
+ // 0 Parameters
26
+ // ========================================================================
27
+
28
+ function get0() external view returns (bytes memory) {
29
+ return data0;
30
+ }
31
+
32
+ function set0(bytes calldata value) external onlyOwner {
33
+ data0 = value;
34
+ }
35
+
36
+ // ========================================================================
37
+ // 1 Parameter - bytes32
38
+ // ========================================================================
39
+
40
+ function get1Bytes32(bytes32 key) external view returns (bytes memory) {
41
+ return data1Bytes32[key];
42
+ }
43
+
44
+ function set1Bytes32(bytes32 key, bytes calldata value) external onlyOwner {
45
+ data1Bytes32[key] = value;
46
+ }
47
+
48
+ // ========================================================================
49
+ // 1 Parameter - address
50
+ // ========================================================================
51
+
52
+ function get1Address(address key) external view returns (bytes memory) {
53
+ return data1Address[key];
54
+ }
55
+
56
+ function set1Address(address key, bytes calldata value) external onlyOwner {
57
+ data1Address[key] = value;
58
+ }
59
+
60
+ // ========================================================================
61
+ // 1 Parameter - uint256
62
+ // ========================================================================
63
+
64
+ function get1Uint256(uint256 key) external view returns (bytes memory) {
65
+ return data1Uint256[key];
66
+ }
67
+
68
+ function set1Uint256(uint256 key, bytes calldata value) external onlyOwner {
69
+ data1Uint256[key] = value;
70
+ }
71
+
72
+ // ========================================================================
73
+ // 1 Parameter - bool
74
+ // ========================================================================
75
+
76
+ function get1Bool(bool key) external view returns (bytes memory) {
77
+ return data1Bool[key];
78
+ }
79
+
80
+ function set1Bool(bool key, bytes calldata value) external onlyOwner {
81
+ data1Bool[key] = value;
82
+ }
83
+
84
+ // ========================================================================
85
+ // 2 Parameters - bytes32, bytes32
86
+ // ========================================================================
87
+
88
+ function get2Bytes32Bytes32(bytes32 key1, bytes32 key2) external view returns (bytes memory) {
89
+ return data2Bytes32Bytes32[key1][key2];
90
+ }
91
+
92
+ function set2Bytes32Bytes32(bytes32 key1, bytes32 key2, bytes calldata value) external onlyOwner {
93
+ data2Bytes32Bytes32[key1][key2] = value;
94
+ }
95
+
96
+ // ========================================================================
97
+ // 2 Parameters - bytes32, uint256
98
+ // ========================================================================
99
+
100
+ function get2Bytes32Uint256(bytes32 key1, uint256 key2) external view returns (bytes memory) {
101
+ return data2Bytes32Uint256[key1][key2];
102
+ }
103
+
104
+ function set2Bytes32Uint256(bytes32 key1, uint256 key2, bytes calldata value) external onlyOwner {
105
+ data2Bytes32Uint256[key1][key2] = value;
106
+ }
107
+
108
+ // ========================================================================
109
+ // 2 Parameters - bytes32, address
110
+ // ========================================================================
111
+
112
+ function get2Bytes32Address(bytes32 key1, address key2) external view returns (bytes memory) {
113
+ return data2Bytes32Address[key1][key2];
114
+ }
115
+
116
+ function set2Bytes32Address(bytes32 key1, address key2, bytes calldata value) external onlyOwner {
117
+ data2Bytes32Address[key1][key2] = value;
118
+ }
119
+
120
+ // ========================================================================
121
+ // 2 Parameters - address, uint256
122
+ // ========================================================================
123
+
124
+ function get2AddressUint256(address key1, uint256 key2) external view returns (bytes memory) {
125
+ return data2AddressUint256[key1][key2];
126
+ }
127
+
128
+ function set2AddressUint256(address key1, uint256 key2, bytes calldata value) external onlyOwner {
129
+ data2AddressUint256[key1][key2] = value;
130
+ }
131
+
132
+ // ========================================================================
133
+ // 2 Parameters - uint256, uint256
134
+ // ========================================================================
135
+
136
+ function get2Uint256Uint256(uint256 key1, uint256 key2) external view returns (bytes memory) {
137
+ return data2Uint256Uint256[key1][key2];
138
+ }
139
+
140
+ function set2Uint256Uint256(uint256 key1, uint256 key2, bytes calldata value) external onlyOwner {
141
+ data2Uint256Uint256[key1][key2] = value;
142
+ }
143
+
144
+ // ========================================================================
145
+ // 2 Parameters - bool, bytes32
146
+ // ========================================================================
147
+
148
+ function get2BoolBytes32(bool key1, bytes32 key2) external view returns (bytes memory) {
149
+ return data2BoolBytes32[key1][key2];
150
+ }
151
+
152
+ function set2BoolBytes32(bool key1, bytes32 key2, bytes calldata value) external onlyOwner {
153
+ data2BoolBytes32[key1][key2] = value;
154
+ }
155
+ }
@@ -0,0 +1,17 @@
1
+ pragma solidity ^0.8.28;
2
+ import '@openzeppelin/contracts/access/Ownable.sol';
3
+ import '@ensdomains/ens-contracts/contracts/resolvers/profiles/ExtendedResolver.sol';
4
+ import './IDataResolver.sol';
5
+
6
+ contract DataResolver is Ownable, ExtendedResolver, IDataResolver {
7
+ mapping(bytes32 node => bytes data) private dataStore;
8
+
9
+ function data(bytes32 node) external view returns (bytes memory) {
10
+ return dataStore[node];
11
+ }
12
+
13
+ function setData(bytes32 node, bytes calldata value) external onlyOwner {
14
+ dataStore[node] = value;
15
+ emit DataChanged(node, keccak256(value));
16
+ }
17
+ }