@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.
@@ -0,0 +1,164 @@
1
+ import { Contract, Provider, AbiCoder } from "ethers";
2
+ import {
3
+ DecodedEIP8121Hook,
4
+ extractFunctionName,
5
+ parseParameterTypes,
6
+ parseFunctionCallValues,
7
+ isEIP8121Hook,
8
+ decodeHook
9
+ } from "./encoding.js";
10
+ import { TrustedTargets, verifyTrustedTarget } from "./trust.js";
11
+
12
+ export type ProviderMap = Map<number, Provider>;
13
+
14
+ export interface HookExecutionResult {
15
+ _tag: "HookExecutionResult";
16
+ data: string;
17
+ }
18
+
19
+ export interface HookExecutionError {
20
+ _tag: "HookExecutionError";
21
+ error: true;
22
+ message: string;
23
+ cause?: unknown;
24
+ }
25
+
26
+ export type ExecutionResult = HookExecutionResult | HookExecutionError;
27
+
28
+ export interface HookValidationResult {
29
+ isValid: boolean;
30
+ error?: string;
31
+ }
32
+
33
+ /**
34
+ * Validates an EIP-8121 hook.
35
+ * Checks: 0-2 fixed-size primitive parameters, (bytes) return type.
36
+ */
37
+ export function validateHook(hook: DecodedEIP8121Hook): HookValidationResult {
38
+ try {
39
+ // Validate function signature and parameters
40
+ const params = parseParameterTypes(hook.functionSignature);
41
+
42
+ // Validate parameter count (0-2 allowed)
43
+ if (params.length > 2) {
44
+ return {
45
+ isValid: false,
46
+ error: `Too many parameters: expected 0-2, got ${params.length}`
47
+ };
48
+ }
49
+
50
+ // Validate return type
51
+ if (hook.returnType !== '(bytes)') {
52
+ return {
53
+ isValid: false,
54
+ error: `Invalid return type: expected (bytes), got ${hook.returnType}`
55
+ };
56
+ }
57
+
58
+ return { isValid: true };
59
+ } catch (error) {
60
+ return {
61
+ isValid: false,
62
+ error: `Validation failed: ${error instanceof Error ? error.message : String(error)}`
63
+ };
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Decodes result from hook execution.
69
+ * Note: ethers already ABI-decodes the return value, so we just validate and return.
70
+ */
71
+ export function decodeResult(resultBytes: string, returnType: string): string {
72
+ if (returnType !== '(bytes)') {
73
+ throw new Error(`Invalid return type: expected (bytes), got ${returnType}`);
74
+ }
75
+
76
+ // ethers already decoded the bytes return value
77
+ return resultBytes;
78
+ }
79
+
80
+ export interface ExecuteHookOptions {
81
+ providerMap: ProviderMap;
82
+ trustedTargets?: TrustedTargets;
83
+ throwOnUntrusted?: boolean;
84
+ }
85
+
86
+ export async function executeHook(
87
+ hook: DecodedEIP8121Hook,
88
+ options: ExecuteHookOptions
89
+ ): Promise<ExecutionResult> {
90
+ try {
91
+ const validation = validateHook(hook);
92
+ if (!validation.isValid) {
93
+ return {
94
+ _tag: "HookExecutionError",
95
+ error: true,
96
+ message: `Hook validation failed: ${validation.error}`
97
+ };
98
+ }
99
+
100
+ if (options.trustedTargets) {
101
+ const isTrusted = verifyTrustedTarget(hook.target, options.trustedTargets);
102
+ if (!isTrusted) {
103
+ const errorMsg = `Untrusted target: ${hook.target.address} on chain ${hook.target.chainId}`;
104
+ if (options.throwOnUntrusted) {
105
+ throw new Error(errorMsg);
106
+ }
107
+ return {
108
+ _tag: "HookExecutionError",
109
+ error: true,
110
+ message: errorMsg
111
+ };
112
+ }
113
+ }
114
+
115
+ const provider = options.providerMap.get(hook.target.chainId);
116
+ if (!provider) {
117
+ return {
118
+ _tag: "HookExecutionError",
119
+ error: true,
120
+ message: `No provider available for chain ID ${hook.target.chainId}`
121
+ };
122
+ }
123
+
124
+ // Parse parameter values from functionCall (with generic error messages for security)
125
+ const paramValues = parseFunctionCallValues(hook.functionCall, hook.functionSignature, false);
126
+
127
+ const functionName = extractFunctionName(hook.functionSignature);
128
+
129
+ const abi = [`function ${hook.functionSignature} view returns (bytes)`];
130
+
131
+ const contract = new Contract(
132
+ hook.target.address,
133
+ abi,
134
+ provider
135
+ );
136
+
137
+ const resultBytes = await contract[functionName](...paramValues);
138
+ const result = decodeResult(resultBytes, hook.returnType);
139
+
140
+ return {
141
+ _tag: "HookExecutionResult",
142
+ data: result
143
+ };
144
+ } catch (error) {
145
+ return {
146
+ _tag: "HookExecutionError",
147
+ error: true,
148
+ message: `Hook execution failed: ${error instanceof Error ? error.message : String(error)}`,
149
+ cause: error
150
+ };
151
+ }
152
+ }
153
+
154
+ export async function detectAndDecodeHook(data: string): Promise<DecodedEIP8121Hook | null> {
155
+ if (!isEIP8121Hook(data)) {
156
+ return null;
157
+ }
158
+
159
+ const { tryDecodeEIP8121HookFromContenthash } = await import("./encoding.js");
160
+ const stripped = tryDecodeEIP8121HookFromContenthash(data);
161
+ const hookData = stripped ?? data;
162
+
163
+ return await decodeHook(hookData);
164
+ }
@@ -0,0 +1,39 @@
1
+ import { EIP8121Target } from "./encoding.js";
2
+
3
+ export interface TrustedTarget {
4
+ chainId: number;
5
+ address: string;
6
+ description?: string;
7
+ }
8
+
9
+ export type TrustedTargets =
10
+ | TrustedTarget[]
11
+ | Set<string>
12
+ | ((target: EIP8121Target) => boolean);
13
+
14
+ export function verifyTrustedTarget(
15
+ target: EIP8121Target,
16
+ trustedTargets: TrustedTargets
17
+ ): boolean {
18
+ if (typeof trustedTargets === 'function') {
19
+ return trustedTargets(target);
20
+ }
21
+
22
+ if (trustedTargets instanceof Set) {
23
+ const key = createTargetKey(target.chainId, target.address);
24
+ return trustedTargets.has(key);
25
+ }
26
+
27
+ return trustedTargets.some(trusted =>
28
+ trusted.chainId === target.chainId &&
29
+ trusted.address.toLowerCase() === target.address.toLowerCase()
30
+ );
31
+ }
32
+
33
+ export function createTargetKey(chainId: number, address: string): string {
34
+ return `${chainId}:${address.toLowerCase()}`;
35
+ }
36
+
37
+ export function createTrustedTargets(targets: TrustedTarget[]): Set<string> {
38
+ return new Set(targets.map(t => createTargetKey(t.chainId, t.address)));
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ export {
2
+ executeHook,
3
+ validateHook,
4
+ decodeResult,
5
+ detectAndDecodeHook,
6
+ type ProviderMap,
7
+ type HookExecutionResult,
8
+ type HookExecutionError,
9
+ type ExecutionResult,
10
+ type HookValidationResult,
11
+ type ExecuteHookOptions,
12
+ } from "./dataurl/index.js";
13
+
14
+ export {
15
+ encodeHook,
16
+ decodeHook,
17
+ computeSelector,
18
+ extractFunctionName,
19
+ parseParameterTypes,
20
+ parseFunctionCallValues,
21
+ encodeERC7930Target,
22
+ decodeERC7930Target,
23
+ isEIP8121Hook,
24
+ isAllowedParameterType,
25
+ validateFunctionCallMatchesSignature,
26
+ MAX_STRING_LENGTH,
27
+ type DecodedEIP8121Hook,
28
+ type EIP8121Target,
29
+ type HookParameter
30
+ } from "./dataurl/encoding.js";
31
+
32
+ export {
33
+ HOOK_SELECTOR
34
+ } from "./dataurl/constants.js";
35
+
36
+ export {
37
+ verifyTrustedTarget,
38
+ createTrustedTargets,
39
+ createTargetKey,
40
+ type TrustedTarget,
41
+ type TrustedTargets
42
+ } from "./dataurl/trust.js";
43
+
44
+ export {
45
+ encodeDataUri,
46
+ tryDecodeDataUri,
47
+ encodeEIP8121HookForContenthash,
48
+ tryDecodeEIP8121HookFromContenthash
49
+ } from "./dataurl/encoding.js";