@cofhe/sdk 0.1.1 → 0.2.1

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 (107) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/adapters/ethers6.ts +28 -28
  3. package/adapters/hardhat.ts +0 -1
  4. package/adapters/index.test.ts +14 -19
  5. package/adapters/smartWallet.ts +81 -73
  6. package/adapters/test-utils.ts +45 -45
  7. package/adapters/types.ts +3 -3
  8. package/chains/chains/localcofhe.ts +14 -0
  9. package/chains/chains.test.ts +2 -1
  10. package/chains/defineChain.ts +2 -2
  11. package/chains/index.ts +3 -1
  12. package/chains/types.ts +3 -3
  13. package/core/baseBuilder.ts +30 -49
  14. package/core/client.test.ts +200 -72
  15. package/core/client.ts +152 -148
  16. package/core/clientTypes.ts +114 -0
  17. package/core/config.test.ts +30 -11
  18. package/core/config.ts +26 -13
  19. package/core/consts.ts +18 -0
  20. package/core/decrypt/cofheMocksSealOutput.ts +2 -4
  21. package/core/decrypt/decryptHandleBuilder.ts +51 -45
  22. package/core/decrypt/{tnSealOutput.ts → tnSealOutputV1.ts} +1 -1
  23. package/core/decrypt/tnSealOutputV2.ts +298 -0
  24. package/core/encrypt/cofheMocksZkVerifySign.ts +15 -16
  25. package/core/encrypt/encryptInputsBuilder.test.ts +132 -116
  26. package/core/encrypt/encryptInputsBuilder.ts +159 -111
  27. package/core/encrypt/encryptUtils.ts +6 -3
  28. package/core/encrypt/zkPackProveVerify.ts +70 -8
  29. package/core/error.ts +0 -2
  30. package/core/fetchKeys.test.ts +1 -18
  31. package/core/fetchKeys.ts +0 -26
  32. package/core/index.ts +37 -17
  33. package/core/keyStore.ts +65 -38
  34. package/core/permits.test.ts +255 -4
  35. package/core/permits.ts +83 -18
  36. package/core/types.ts +198 -152
  37. package/core/utils.ts +43 -1
  38. package/dist/adapters.d.cts +38 -20
  39. package/dist/adapters.d.ts +38 -20
  40. package/dist/chains.cjs +18 -8
  41. package/dist/chains.d.cts +31 -9
  42. package/dist/chains.d.ts +31 -9
  43. package/dist/chains.js +1 -1
  44. package/dist/{chunk-KFGPTJ6X.js → chunk-I5WFEYXX.js} +1768 -1526
  45. package/dist/{chunk-LU7BMUUT.js → chunk-R3B5TMVX.js} +330 -197
  46. package/dist/{chunk-GZCQQYVI.js → chunk-TBLR7NNE.js} +18 -9
  47. package/dist/{types-PhwGgQvs.d.ts → clientTypes-RqkgkV2i.d.ts} +331 -429
  48. package/dist/{types-bB7wLj0q.d.cts → clientTypes-e4filDzK.d.cts} +331 -429
  49. package/dist/core.cjs +3000 -2625
  50. package/dist/core.d.cts +113 -7
  51. package/dist/core.d.ts +113 -7
  52. package/dist/core.js +3 -3
  53. package/dist/node.cjs +2851 -2526
  54. package/dist/node.d.cts +4 -4
  55. package/dist/node.d.ts +4 -4
  56. package/dist/node.js +4 -3
  57. package/dist/{permit-S9CnI6MF.d.cts → permit-MZ502UBl.d.cts} +54 -41
  58. package/dist/{permit-S9CnI6MF.d.ts → permit-MZ502UBl.d.ts} +54 -41
  59. package/dist/permits.cjs +328 -195
  60. package/dist/permits.d.cts +113 -825
  61. package/dist/permits.d.ts +113 -825
  62. package/dist/permits.js +1 -1
  63. package/dist/types-YiAC4gig.d.cts +33 -0
  64. package/dist/types-YiAC4gig.d.ts +33 -0
  65. package/dist/web.cjs +3067 -2527
  66. package/dist/web.d.cts +22 -6
  67. package/dist/web.d.ts +22 -6
  68. package/dist/web.js +185 -9
  69. package/dist/zkProve.worker.cjs +93 -0
  70. package/dist/zkProve.worker.d.cts +2 -0
  71. package/dist/zkProve.worker.d.ts +2 -0
  72. package/dist/zkProve.worker.js +91 -0
  73. package/node/client.test.ts +20 -25
  74. package/node/encryptInputs.test.ts +18 -38
  75. package/node/index.ts +1 -0
  76. package/package.json +15 -15
  77. package/permits/index.ts +1 -0
  78. package/permits/localstorage.test.ts +9 -14
  79. package/permits/onchain-utils.ts +221 -0
  80. package/permits/permit.test.ts +76 -27
  81. package/permits/permit.ts +58 -95
  82. package/permits/sealing.test.ts +3 -3
  83. package/permits/sealing.ts +2 -2
  84. package/permits/store.test.ts +10 -50
  85. package/permits/store.ts +9 -21
  86. package/permits/test-utils.ts +11 -3
  87. package/permits/types.ts +39 -9
  88. package/permits/utils.ts +0 -5
  89. package/permits/validation.test.ts +29 -32
  90. package/permits/validation.ts +114 -176
  91. package/web/client.web.test.ts +20 -25
  92. package/web/config.web.test.ts +0 -2
  93. package/web/encryptInputs.web.test.ts +31 -54
  94. package/web/index.ts +65 -1
  95. package/web/storage.ts +19 -5
  96. package/web/worker.builder.web.test.ts +148 -0
  97. package/web/worker.config.web.test.ts +329 -0
  98. package/web/worker.output.web.test.ts +84 -0
  99. package/web/workerManager.test.ts +80 -0
  100. package/web/workerManager.ts +214 -0
  101. package/web/workerManager.web.test.ts +114 -0
  102. package/web/zkProve.worker.ts +133 -0
  103. package/core/result.test.ts +0 -180
  104. package/core/result.ts +0 -67
  105. package/core/test-utils.ts +0 -45
  106. package/dist/types-KImPrEIe.d.cts +0 -48
  107. package/dist/types-KImPrEIe.d.ts +0 -48
@@ -1,4 +1,4 @@
1
- import { Encryptable, FheTypes, type CofhesdkClient, type Result, CofhesdkErrorCode, CofhesdkError } from '@/core';
1
+ import { Encryptable, FheTypes, type CofhesdkClient, CofhesdkErrorCode, CofhesdkError } from '@/core';
2
2
  import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
3
3
 
4
4
  import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
@@ -11,22 +11,6 @@ import { createCofhesdkClient, createCofhesdkConfig } from './index.js';
11
11
  // Real test setup - using actual node-tfhe
12
12
  const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
13
13
 
14
- const expectResultSuccess = <T>(result: Result<T>): T => {
15
- expect(result.success, `Result error: ${result.error?.toString()}`).toBe(true);
16
- return result.data!;
17
- };
18
-
19
- const expectResultError = <T>(result: Result<T>, errorCode?: CofhesdkErrorCode): void => {
20
- expect(result.success).toBe(false);
21
- expect(result.data).toBe(null);
22
- expect(result.error).not.toBe(null);
23
- const error = result.error as CofhesdkError;
24
- expect(error).toBeInstanceOf(CofhesdkError);
25
- if (errorCode) {
26
- expect(error.code, `Result error: ${result.error?.toString()}`).toBe(errorCode);
27
- }
28
- };
29
-
30
14
  describe('@cofhe/node - Encrypt Inputs', () => {
31
15
  let cofhesdkClient: CofhesdkClient;
32
16
  let publicClient: PublicClient;
@@ -59,8 +43,7 @@ describe('@cofhe/node - Encrypt Inputs', () => {
59
43
  await cofhesdkClient.connect(publicClient, walletClient);
60
44
 
61
45
  // This will trigger real TFHE initialization
62
- const result = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
63
- const encrypted = expectResultSuccess(result);
46
+ const encrypted = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
64
47
 
65
48
  // If we get here, TFHE was initialized successfully
66
49
  expect(encrypted).toBeDefined();
@@ -70,12 +53,10 @@ describe('@cofhe/node - Encrypt Inputs', () => {
70
53
  await cofhesdkClient.connect(publicClient, walletClient);
71
54
 
72
55
  // First encryption
73
- const result1 = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
74
- expectResultSuccess(result1);
56
+ await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
75
57
 
76
58
  // Second encryption should reuse initialization
77
- const result2 = await cofhesdkClient.encryptInputs([Encryptable.uint64(50n)]).encrypt();
78
- expectResultSuccess(result2);
59
+ await cofhesdkClient.encryptInputs([Encryptable.uint64(50n)]).encrypt();
79
60
  }, 120000);
80
61
  });
81
62
 
@@ -83,8 +64,7 @@ describe('@cofhe/node - Encrypt Inputs', () => {
83
64
  it('should encrypt a bool with real TFHE', async () => {
84
65
  await cofhesdkClient.connect(publicClient, walletClient);
85
66
 
86
- const result = await cofhesdkClient.encryptInputs([Encryptable.bool(true)]).encrypt();
87
- const encrypted = expectResultSuccess(result);
67
+ const encrypted = await cofhesdkClient.encryptInputs([Encryptable.bool(true)]).encrypt();
88
68
 
89
69
  expect(encrypted.length).toBe(1);
90
70
  expect(encrypted[0].utype).toBe(FheTypes.Bool);
@@ -108,8 +88,7 @@ describe('@cofhe/node - Encrypt Inputs', () => {
108
88
  Encryptable.address('0x742d35Cc6634C0532925a3b844D16faC4c175E99'),
109
89
  ];
110
90
 
111
- const result = await cofhesdkClient.encryptInputs(inputs).encrypt();
112
- const encrypted = expectResultSuccess(result);
91
+ const encrypted = await cofhesdkClient.encryptInputs(inputs).encrypt();
113
92
 
114
93
  expect(encrypted.length).toBe(7);
115
94
  // Verify each type
@@ -128,14 +107,13 @@ describe('@cofhe/node - Encrypt Inputs', () => {
128
107
  await cofhesdkClient.connect(publicClient, walletClient);
129
108
 
130
109
  const snapshot = cofhesdkClient.getSnapshot();
131
- const result = await cofhesdkClient
110
+ const encrypted = await cofhesdkClient
132
111
  .encryptInputs([Encryptable.uint128(100n)])
133
112
  .setChainId(snapshot.chainId!)
134
113
  .setAccount(snapshot.account!)
135
114
  .setSecurityZone(0)
136
115
  .encrypt();
137
116
 
138
- const encrypted = expectResultSuccess(result);
139
117
  expect(encrypted.length).toBe(1);
140
118
  expect(encrypted[0].utype).toBe(FheTypes.Uint128);
141
119
  }, 60000);
@@ -144,9 +122,12 @@ describe('@cofhe/node - Encrypt Inputs', () => {
144
122
  describe('Real Error Handling', () => {
145
123
  it('should fail gracefully when not connected', async () => {
146
124
  // Don't connect the client
147
- const result = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
148
-
149
- expectResultError(result, CofhesdkErrorCode.NotConnected);
125
+ try {
126
+ await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
127
+ } catch (error) {
128
+ expect(error).toBeInstanceOf(CofhesdkError);
129
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.NotConnected);
130
+ }
150
131
  }, 30000);
151
132
 
152
133
  it('should handle invalid CoFHE URL', async () => {
@@ -158,17 +139,16 @@ describe('@cofhe/node - Encrypt Inputs', () => {
158
139
  verifierUrl: 'http://invalid-verifier-url.local',
159
140
  },
160
141
  ],
161
- fheKeysPrefetching: 'OFF',
162
142
  });
163
143
 
164
144
  const badClient = createCofhesdkClient(badConfig);
165
145
  await badClient.connect(publicClient, walletClient);
166
146
 
167
- const result = await badClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
168
-
169
- expect(result.success).toBe(false);
170
- if (!result.success) {
171
- expect(result.error).toBeDefined();
147
+ try {
148
+ await badClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
149
+ } catch (error) {
150
+ expect(error).toBeInstanceOf(CofhesdkError);
151
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.ZkVerifyFailed);
172
152
  }
173
153
  }, 60000);
174
154
  });
package/node/index.ts CHANGED
@@ -74,6 +74,7 @@ const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: st
74
74
  */
75
75
  export function createCofhesdkConfig(config: CofhesdkInputConfig): CofhesdkConfig {
76
76
  return createCofhesdkConfigBase({
77
+ environment: 'node',
77
78
  ...config,
78
79
  fheKeyStorage: config.fheKeyStorage === null ? null : config.fheKeyStorage ?? createNodeStorage(),
79
80
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cofhe/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "SDK for Fhenix COFHE coprocessor interaction",
6
6
  "main": "./dist/core.cjs",
@@ -57,21 +57,21 @@
57
57
  "CHANGELOG.md"
58
58
  ],
59
59
  "dependencies": {
60
+ "iframe-shared-storage": "^1.0.34",
60
61
  "immer": "^10.1.1",
61
- "zustand": "^5.0.1",
62
- "zod": "^3.22.0",
63
- "viem": "^2.0.0",
64
62
  "node-tfhe": "0.11.1",
65
- "idb-keyval": "^6.2.1",
66
63
  "tfhe": "0.11.1",
67
- "tweetnacl": "^1.0.3"
64
+ "tweetnacl": "^1.0.3",
65
+ "viem": "^2.38.6",
66
+ "zod": "^4.0.0",
67
+ "zustand": "^5.0.1"
68
68
  },
69
69
  "peerDependencies": {
70
- "viem": "^2.0.0",
70
+ "@nomicfoundation/hardhat-ethers": "^3.0.0",
71
71
  "@wagmi/core": "^2.0.0",
72
72
  "ethers": "^5.0.0 || ^6.0.0",
73
73
  "hardhat": "^2.0.0",
74
- "@nomicfoundation/hardhat-ethers": "^3.0.0"
74
+ "viem": "^2.38.6"
75
75
  },
76
76
  "peerDependenciesMeta": {
77
77
  "@wagmi/core": {
@@ -88,21 +88,21 @@
88
88
  }
89
89
  },
90
90
  "devDependencies": {
91
+ "@nomicfoundation/hardhat-ethers": "^3.0.0",
91
92
  "@types/node": "^20.0.0",
92
93
  "@vitest/browser": "^3.0.0",
93
94
  "@vitest/coverage-v8": "^3.0.0",
94
95
  "eslint": "^8.57.0",
96
+ "ethers5": "npm:ethers@^5.7.2",
97
+ "ethers6": "npm:ethers@^6.13.0",
95
98
  "happy-dom": "^15.0.0",
99
+ "hardhat": "^2.19.0",
96
100
  "playwright": "^1.55.0",
97
101
  "tsup": "^8.0.2",
98
102
  "typescript": "5.5.4",
99
103
  "vitest": "^3.0.0",
100
- "ethers5": "npm:ethers@^5.7.2",
101
- "ethers6": "npm:ethers@^6.13.0",
102
- "hardhat": "^2.19.0",
103
- "@nomicfoundation/hardhat-ethers": "^3.0.0",
104
- "@cofhe/eslint-config": "0.1.1",
105
- "@cofhe/tsconfig": "0.1.0"
104
+ "@cofhe/eslint-config": "0.2.0",
105
+ "@cofhe/tsconfig": "0.1.1"
106
106
  },
107
107
  "publishConfig": {
108
108
  "access": "public"
@@ -111,7 +111,7 @@
111
111
  "build": "tsup",
112
112
  "dev": "tsup --watch",
113
113
  "lint": "eslint \"**/*.ts*\"",
114
- "type-check": "tsc --noEmit",
114
+ "check:types": "tsc --noEmit",
115
115
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
116
116
  "test": "vitest run",
117
117
  "test:watch": "vitest",
package/permits/index.ts CHANGED
@@ -53,6 +53,7 @@ export {
53
53
  setActivePermitHash,
54
54
  removeActivePermitHash,
55
55
  clearStaleStore,
56
+ PERMIT_STORE_DEFAULTS,
56
57
  } from './store.js';
57
58
 
58
59
  // Sealing utilities
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * @vitest-environment happy-dom
3
3
  */
4
- /* eslint-disable no-unused-vars */
5
4
 
6
5
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
6
  import {
@@ -40,7 +39,6 @@ describe('Permits localStorage Tests', () => {
40
39
 
41
40
  it('should persist permits to localStorage', async () => {
42
41
  const permit = await createMockPermit();
43
- const hash = PermitUtils.getHash(permit);
44
42
 
45
43
  setPermit(chainId, account, permit);
46
44
 
@@ -49,40 +47,38 @@ describe('Permits localStorage Tests', () => {
49
47
  expect(storedData).toBeDefined();
50
48
 
51
49
  const parsedData = JSON.parse(storedData!);
52
- expect(parsedData.state.permits[chainId][account][hash]).toBeDefined();
50
+ expect(parsedData.state.permits[chainId][account][permit.hash]).toBeDefined();
53
51
  });
54
52
 
55
53
  it('should persist active permit hash to localStorage', async () => {
56
54
  const permit = await createMockPermit();
57
- const hash = PermitUtils.getHash(permit);
58
55
 
59
56
  setPermit(chainId, account, permit);
60
- setActivePermitHash(chainId, account, hash);
57
+ setActivePermitHash(chainId, account, permit.hash);
61
58
 
62
59
  // Verify active permit hash is stored
63
60
  const storedData = localStorage.getItem('cofhesdk-permits');
64
61
  expect(storedData).toBeDefined();
65
62
 
66
63
  const parsedData = JSON.parse(storedData!);
67
- expect(parsedData.state.activePermitHash[chainId][account]).toBe(hash);
64
+ expect(parsedData.state.activePermitHash[chainId][account]).toBe(permit.hash);
68
65
  });
69
66
 
70
67
  it('should restore permits from localStorage', async () => {
71
68
  const permit = await createMockPermit();
72
- const hash = PermitUtils.getHash(permit);
73
69
 
74
70
  // Add permit to localStorage
75
71
  setPermit(chainId, account, permit);
76
- setActivePermitHash(chainId, account, hash);
72
+ setActivePermitHash(chainId, account, permit.hash);
77
73
  const serializedPermit = PermitUtils.serialize(permit);
78
74
 
79
75
  // Verify data is restored
80
- const retrievedPermit = getPermit(chainId, account, hash);
76
+ const retrievedPermit = getPermit(chainId, account, permit.hash);
81
77
  expect(retrievedPermit).toBeDefined();
82
78
  expect(PermitUtils.serialize(retrievedPermit!)).toEqual(serializedPermit);
83
79
 
84
80
  const activeHash = getActivePermitHash(chainId, account);
85
- expect(activeHash).toBe(hash);
81
+ expect(activeHash).toBe(permit.hash);
86
82
  });
87
83
 
88
84
  it('should handle corrupted localStorage data gracefully', () => {
@@ -97,22 +93,21 @@ describe('Permits localStorage Tests', () => {
97
93
 
98
94
  it('should clean up localStorage when permits are removed', async () => {
99
95
  const permit = await createMockPermit();
100
- const hash = PermitUtils.getHash(permit);
101
96
 
102
97
  setPermit(chainId, account, permit);
103
- setActivePermitHash(chainId, account, hash);
98
+ setActivePermitHash(chainId, account, permit.hash);
104
99
 
105
100
  // Verify data exists
106
101
  let storedData = localStorage.getItem('cofhesdk-permits');
107
102
  expect(storedData).toBeDefined();
108
103
 
109
104
  // Remove permit
110
- removePermit(chainId, account, hash, true);
105
+ removePermit(chainId, account, permit.hash);
111
106
 
112
107
  // Verify data is cleaned up
113
108
  storedData = localStorage.getItem('cofhesdk-permits');
114
109
  const parsedData = JSON.parse(storedData!);
115
- expect(parsedData.state.permits[chainId][account][hash]).toBeUndefined();
110
+ expect(parsedData.state.permits[chainId][account][permit.hash]).toBeUndefined();
116
111
  expect(parsedData.state.activePermitHash[chainId][account]).toBeUndefined();
117
112
  });
118
113
  });
@@ -0,0 +1,221 @@
1
+ import {
2
+ BaseError,
3
+ ContractFunctionRevertedError,
4
+ type Hex,
5
+ type PublicClient,
6
+ decodeErrorResult,
7
+ parseAbi,
8
+ } from 'viem';
9
+ import type { EIP712Domain, Permission } from './types';
10
+ import { TASK_MANAGER_ADDRESS } from '../core/consts.js';
11
+
12
+ export const getAclAddress = async (publicClient: PublicClient): Promise<Hex> => {
13
+ const ACL_IFACE = 'function acl() view returns (address)';
14
+
15
+ // Parse the ABI for the ACL function
16
+ const aclAbi = parseAbi([ACL_IFACE]);
17
+
18
+ // Get the ACL address
19
+ return (await publicClient.readContract({
20
+ address: TASK_MANAGER_ADDRESS as `0x${string}`,
21
+ abi: aclAbi,
22
+ functionName: 'acl',
23
+ })) as `0x${string}`;
24
+ };
25
+
26
+ export const getAclEIP712Domain = async (publicClient: PublicClient): Promise<EIP712Domain> => {
27
+ const aclAddress = await getAclAddress(publicClient);
28
+ const EIP712_DOMAIN_IFACE =
29
+ 'function eip712Domain() public view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)';
30
+
31
+ // Parse the ABI for the EIP712 domain function
32
+ const domainAbi = parseAbi([EIP712_DOMAIN_IFACE]);
33
+
34
+ // Get the EIP712 domain
35
+ const domain = await publicClient.readContract({
36
+ address: aclAddress,
37
+ abi: domainAbi,
38
+ functionName: 'eip712Domain',
39
+ });
40
+
41
+ // eslint-disable-next-line no-unused-vars
42
+ const [_fields, name, version, chainId, verifyingContract, _salt, _extensions] = domain;
43
+
44
+ return {
45
+ name,
46
+ version,
47
+ chainId: Number(chainId),
48
+ verifyingContract,
49
+ };
50
+ };
51
+
52
+ export const checkPermitValidityOnChain = async (
53
+ permission: Permission,
54
+ publicClient: PublicClient
55
+ ): Promise<boolean> => {
56
+ const aclAddress = await getAclAddress(publicClient);
57
+
58
+ // Check if the permit is valid
59
+ try {
60
+ await publicClient.simulateContract({
61
+ address: aclAddress,
62
+ abi: checkPermitValidityAbi,
63
+ functionName: 'checkPermitValidity',
64
+ args: [
65
+ {
66
+ issuer: permission.issuer,
67
+ expiration: BigInt(permission.expiration),
68
+ recipient: permission.recipient,
69
+ validatorId: BigInt(permission.validatorId),
70
+ validatorContract: permission.validatorContract,
71
+ sealingKey: permission.sealingKey,
72
+ issuerSignature: permission.issuerSignature,
73
+ recipientSignature: permission.recipientSignature,
74
+ },
75
+ ],
76
+ });
77
+ return true;
78
+ } catch (err: any) {
79
+ // Viem default handling
80
+ if (err instanceof BaseError) {
81
+ const revertError = err.walk((err: any) => err instanceof ContractFunctionRevertedError);
82
+ if (revertError instanceof ContractFunctionRevertedError) {
83
+ const errorName = revertError.data?.errorName ?? '';
84
+ throw new Error(errorName);
85
+ }
86
+ }
87
+
88
+ // Check details field for custom error names (e.g., from Hardhat test nodes)
89
+ const customErrorName = extractCustomErrorFromDetails(err, checkPermitValidityAbi);
90
+ if (customErrorName) {
91
+ throw new Error(customErrorName);
92
+ }
93
+
94
+ // Hardhat wrapped error will need to be unwrapped to get the return data
95
+ const hhDetailsData = extractReturnData(err);
96
+ if (hhDetailsData != null) {
97
+ const decoded = decodeErrorResult({
98
+ abi: checkPermitValidityAbi,
99
+ data: hhDetailsData,
100
+ });
101
+
102
+ throw new Error(decoded.errorName);
103
+ }
104
+
105
+ // Fallback throw the original error
106
+ throw err;
107
+ }
108
+ };
109
+
110
+ function extractCustomErrorFromDetails(err: unknown, abi: readonly any[]): string | undefined {
111
+ // Check details field for custom error names (e.g., from Hardhat test nodes)
112
+ const anyErr = err as any;
113
+ const details = anyErr?.details ?? anyErr?.cause?.details;
114
+
115
+ if (typeof details === 'string') {
116
+ // Match pattern: "reverted with custom error 'ErrorName()'"
117
+ const customErrorMatch = details.match(/reverted with custom error '(\w+)\(\)'/);
118
+ if (customErrorMatch) {
119
+ const errorName = customErrorMatch[1];
120
+ // Check if this error exists in our ABI
121
+ const errorExists = abi.some((item) => item.type === 'error' && item.name === errorName);
122
+ if (errorExists) {
123
+ return errorName;
124
+ }
125
+ }
126
+ }
127
+
128
+ return undefined;
129
+ }
130
+
131
+ function extractReturnData(err: unknown): `0x${string}` | undefined {
132
+ // viem BaseError has `details`, but fall back to any message-like string we can find
133
+ const anyErr = err as any;
134
+ const s = anyErr?.details ?? anyErr?.cause?.details ?? anyErr?.shortMessage ?? anyErr?.message ?? String(err);
135
+
136
+ return s.match(/return data:\s*(0x[a-fA-F0-9]+)/)?.[1] as `0x${string}` | undefined;
137
+ }
138
+
139
+ const checkPermitValidityAbi = [
140
+ {
141
+ type: 'function',
142
+ name: 'checkPermitValidity',
143
+ inputs: [
144
+ {
145
+ name: 'permission',
146
+ type: 'tuple',
147
+ internalType: 'struct Permission',
148
+ components: [
149
+ {
150
+ name: 'issuer',
151
+ type: 'address',
152
+ internalType: 'address',
153
+ },
154
+ {
155
+ name: 'expiration',
156
+ type: 'uint64',
157
+ internalType: 'uint64',
158
+ },
159
+ {
160
+ name: 'recipient',
161
+ type: 'address',
162
+ internalType: 'address',
163
+ },
164
+ {
165
+ name: 'validatorId',
166
+ type: 'uint256',
167
+ internalType: 'uint256',
168
+ },
169
+ {
170
+ name: 'validatorContract',
171
+ type: 'address',
172
+ internalType: 'address',
173
+ },
174
+ {
175
+ name: 'sealingKey',
176
+ type: 'bytes32',
177
+ internalType: 'bytes32',
178
+ },
179
+ {
180
+ name: 'issuerSignature',
181
+ type: 'bytes',
182
+ internalType: 'bytes',
183
+ },
184
+ {
185
+ name: 'recipientSignature',
186
+ type: 'bytes',
187
+ internalType: 'bytes',
188
+ },
189
+ ],
190
+ },
191
+ ],
192
+ outputs: [
193
+ {
194
+ name: '',
195
+ type: 'bool',
196
+ internalType: 'bool',
197
+ },
198
+ ],
199
+ stateMutability: 'view',
200
+ },
201
+ {
202
+ type: 'error',
203
+ name: 'PermissionInvalid_Disabled',
204
+ inputs: [],
205
+ },
206
+ {
207
+ type: 'error',
208
+ name: 'PermissionInvalid_Expired',
209
+ inputs: [],
210
+ },
211
+ {
212
+ type: 'error',
213
+ name: 'PermissionInvalid_IssuerSignature',
214
+ inputs: [],
215
+ },
216
+ {
217
+ type: 'error',
218
+ name: 'PermissionInvalid_RecipientSignature',
219
+ inputs: [],
220
+ },
221
+ ] as const;