@cofhe/sdk 0.2.0 → 0.3.0

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 (83) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/chains/defineChain.ts +2 -2
  3. package/chains/types.ts +3 -3
  4. package/core/baseBuilder.ts +18 -18
  5. package/core/client.test.ts +155 -41
  6. package/core/client.ts +72 -32
  7. package/core/clientTypes.ts +28 -18
  8. package/core/config.test.ts +40 -33
  9. package/core/config.ts +56 -51
  10. package/core/consts.ts +22 -0
  11. package/core/decrypt/{MockQueryDecrypterAbi.ts → MockThresholdNetworkAbi.ts} +71 -21
  12. package/core/decrypt/cofheMocksDecryptForTx.ts +142 -0
  13. package/core/decrypt/{cofheMocksSealOutput.ts → cofheMocksDecryptForView.ts} +12 -14
  14. package/core/decrypt/decryptForTxBuilder.ts +340 -0
  15. package/core/decrypt/{decryptHandleBuilder.ts → decryptForViewBuilder.ts} +75 -42
  16. package/core/decrypt/tnDecrypt.ts +232 -0
  17. package/core/decrypt/tnSealOutputV1.ts +5 -5
  18. package/core/decrypt/tnSealOutputV2.ts +27 -27
  19. package/core/encrypt/cofheMocksZkVerifySign.ts +19 -26
  20. package/core/encrypt/encryptInputsBuilder.test.ts +57 -61
  21. package/core/encrypt/encryptInputsBuilder.ts +65 -42
  22. package/core/encrypt/zkPackProveVerify.ts +11 -11
  23. package/core/error.ts +18 -18
  24. package/core/fetchKeys.test.ts +3 -3
  25. package/core/fetchKeys.ts +3 -3
  26. package/core/index.ts +22 -11
  27. package/core/permits.test.ts +5 -6
  28. package/core/permits.ts +5 -4
  29. package/core/utils.ts +10 -10
  30. package/dist/chains.cjs +4 -7
  31. package/dist/chains.d.cts +12 -12
  32. package/dist/chains.d.ts +12 -12
  33. package/dist/chains.js +1 -1
  34. package/dist/{chunk-WGCRJCBR.js → chunk-2TPSCOW3.js} +820 -224
  35. package/dist/{chunk-UGBVZNRT.js → chunk-NWDKXBIP.js} +309 -189
  36. package/dist/{chunk-WEAZ25JO.js → chunk-TBLR7NNE.js} +4 -7
  37. package/dist/{clientTypes-5_1nwtUe.d.cts → clientTypes-6aTZPQ_4.d.ts} +233 -173
  38. package/dist/{clientTypes-Es7fyi65.d.ts → clientTypes-Bhq7pCSA.d.cts} +233 -173
  39. package/dist/core.cjs +1138 -418
  40. package/dist/core.d.cts +37 -24
  41. package/dist/core.d.ts +37 -24
  42. package/dist/core.js +3 -3
  43. package/dist/node.cjs +1082 -370
  44. package/dist/node.d.cts +12 -12
  45. package/dist/node.d.ts +12 -12
  46. package/dist/node.js +8 -8
  47. package/dist/{permit-fUSe6KKq.d.cts → permit-MZ502UBl.d.cts} +30 -33
  48. package/dist/{permit-fUSe6KKq.d.ts → permit-MZ502UBl.d.ts} +30 -33
  49. package/dist/permits.cjs +305 -187
  50. package/dist/permits.d.cts +111 -812
  51. package/dist/permits.d.ts +111 -812
  52. package/dist/permits.js +1 -1
  53. package/dist/types-YiAC4gig.d.cts +33 -0
  54. package/dist/types-YiAC4gig.d.ts +33 -0
  55. package/dist/web.cjs +1085 -373
  56. package/dist/web.d.cts +13 -13
  57. package/dist/web.d.ts +13 -13
  58. package/dist/web.js +10 -10
  59. package/node/client.test.ts +34 -34
  60. package/node/config.test.ts +11 -11
  61. package/node/encryptInputs.test.ts +29 -29
  62. package/node/index.ts +15 -15
  63. package/package.json +3 -3
  64. package/permits/localstorage.test.ts +9 -13
  65. package/permits/onchain-utils.ts +221 -0
  66. package/permits/permit.test.ts +51 -5
  67. package/permits/permit.ts +28 -74
  68. package/permits/store.test.ts +10 -50
  69. package/permits/store.ts +4 -14
  70. package/permits/test-utils.ts +10 -2
  71. package/permits/types.ts +22 -9
  72. package/permits/utils.ts +0 -4
  73. package/permits/validation.test.ts +29 -32
  74. package/permits/validation.ts +112 -194
  75. package/web/client.web.test.ts +34 -34
  76. package/web/config.web.test.ts +11 -11
  77. package/web/encryptInputs.web.test.ts +29 -29
  78. package/web/index.ts +19 -19
  79. package/web/worker.builder.web.test.ts +28 -28
  80. package/web/worker.config.web.test.ts +47 -47
  81. package/web/worker.output.web.test.ts +10 -10
  82. package/dist/types-KImPrEIe.d.cts +0 -48
  83. package/dist/types-KImPrEIe.d.ts +0 -48
@@ -39,7 +39,6 @@ describe('Permits localStorage Tests', () => {
39
39
 
40
40
  it('should persist permits to localStorage', async () => {
41
41
  const permit = await createMockPermit();
42
- const hash = PermitUtils.getHash(permit);
43
42
 
44
43
  setPermit(chainId, account, permit);
45
44
 
@@ -48,40 +47,38 @@ describe('Permits localStorage Tests', () => {
48
47
  expect(storedData).toBeDefined();
49
48
 
50
49
  const parsedData = JSON.parse(storedData!);
51
- expect(parsedData.state.permits[chainId][account][hash]).toBeDefined();
50
+ expect(parsedData.state.permits[chainId][account][permit.hash]).toBeDefined();
52
51
  });
53
52
 
54
53
  it('should persist active permit hash to localStorage', async () => {
55
54
  const permit = await createMockPermit();
56
- const hash = PermitUtils.getHash(permit);
57
55
 
58
56
  setPermit(chainId, account, permit);
59
- setActivePermitHash(chainId, account, hash);
57
+ setActivePermitHash(chainId, account, permit.hash);
60
58
 
61
59
  // Verify active permit hash is stored
62
60
  const storedData = localStorage.getItem('cofhesdk-permits');
63
61
  expect(storedData).toBeDefined();
64
62
 
65
63
  const parsedData = JSON.parse(storedData!);
66
- expect(parsedData.state.activePermitHash[chainId][account]).toBe(hash);
64
+ expect(parsedData.state.activePermitHash[chainId][account]).toBe(permit.hash);
67
65
  });
68
66
 
69
67
  it('should restore permits from localStorage', async () => {
70
68
  const permit = await createMockPermit();
71
- const hash = PermitUtils.getHash(permit);
72
69
 
73
70
  // Add permit to localStorage
74
71
  setPermit(chainId, account, permit);
75
- setActivePermitHash(chainId, account, hash);
72
+ setActivePermitHash(chainId, account, permit.hash);
76
73
  const serializedPermit = PermitUtils.serialize(permit);
77
74
 
78
75
  // Verify data is restored
79
- const retrievedPermit = getPermit(chainId, account, hash);
76
+ const retrievedPermit = getPermit(chainId, account, permit.hash);
80
77
  expect(retrievedPermit).toBeDefined();
81
78
  expect(PermitUtils.serialize(retrievedPermit!)).toEqual(serializedPermit);
82
79
 
83
80
  const activeHash = getActivePermitHash(chainId, account);
84
- expect(activeHash).toBe(hash);
81
+ expect(activeHash).toBe(permit.hash);
85
82
  });
86
83
 
87
84
  it('should handle corrupted localStorage data gracefully', () => {
@@ -96,22 +93,21 @@ describe('Permits localStorage Tests', () => {
96
93
 
97
94
  it('should clean up localStorage when permits are removed', async () => {
98
95
  const permit = await createMockPermit();
99
- const hash = PermitUtils.getHash(permit);
100
96
 
101
97
  setPermit(chainId, account, permit);
102
- setActivePermitHash(chainId, account, hash);
98
+ setActivePermitHash(chainId, account, permit.hash);
103
99
 
104
100
  // Verify data exists
105
101
  let storedData = localStorage.getItem('cofhesdk-permits');
106
102
  expect(storedData).toBeDefined();
107
103
 
108
104
  // Remove permit
109
- removePermit(chainId, account, hash, true);
105
+ removePermit(chainId, account, permit.hash);
110
106
 
111
107
  // Verify data is cleaned up
112
108
  storedData = localStorage.getItem('cofhesdk-permits');
113
109
  const parsedData = JSON.parse(storedData!);
114
- expect(parsedData.state.permits[chainId][account][hash]).toBeUndefined();
110
+ expect(parsedData.state.permits[chainId][account][permit.hash]).toBeUndefined();
115
111
  expect(parsedData.state.activePermitHash[chainId][account]).toBeUndefined();
116
112
  });
117
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;
@@ -46,6 +46,7 @@ describe('PermitUtils Tests', () => {
46
46
 
47
47
  const permit = PermitUtils.createSelf(options);
48
48
 
49
+ expect(permit.hash).toBe(PermitUtils.getHash(permit));
49
50
  expect(permit.type).toBe('self');
50
51
  expect(permit.name).toBe('Test Permit');
51
52
  expect(permit.type).toBe('self');
@@ -81,6 +82,7 @@ describe('PermitUtils Tests', () => {
81
82
 
82
83
  const permit = PermitUtils.createSharing(options);
83
84
 
85
+ expect(permit.hash).toBe(PermitUtils.getHash(permit));
84
86
  expect(permit.type).toBe('sharing');
85
87
  expect(permit.name).toBe('Test Sharing Permit');
86
88
  expect(permit.type).toBe('sharing');
@@ -111,6 +113,7 @@ describe('PermitUtils Tests', () => {
111
113
  it('should import a shared permit with valid options', async () => {
112
114
  const options: ImportSharedPermitOptions = {
113
115
  issuer: bobAddress,
116
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
114
117
  recipient: aliceAddress,
115
118
  issuerSignature: '0x1234567890abcdef',
116
119
  name: 'Test Import Permit',
@@ -118,6 +121,7 @@ describe('PermitUtils Tests', () => {
118
121
 
119
122
  const permit = PermitUtils.importShared(options);
120
123
 
124
+ expect(permit.hash).toBe(PermitUtils.getHash(permit));
121
125
  expect(permit.type).toBe('recipient');
122
126
  expect(permit.name).toBe('Test Import Permit');
123
127
  expect(permit.issuer).toBe(bobAddress);
@@ -134,6 +138,7 @@ describe('PermitUtils Tests', () => {
134
138
  it('should import a shared permit with valid options as string', async () => {
135
139
  const options: ImportSharedPermitOptions = {
136
140
  issuer: bobAddress,
141
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
137
142
  recipient: aliceAddress,
138
143
  issuerSignature: '0x1234567890abcdef',
139
144
  };
@@ -168,6 +173,7 @@ describe('PermitUtils Tests', () => {
168
173
  it('should throw error for missing issuerSignature', async () => {
169
174
  const options: ImportSharedPermitOptions = {
170
175
  issuer: bobAddress,
176
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
171
177
  recipient: aliceAddress,
172
178
  issuerSignature: '0x', // Invalid empty signature
173
179
  name: 'Test Import Permit',
@@ -175,6 +181,15 @@ describe('PermitUtils Tests', () => {
175
181
 
176
182
  expect(() => PermitUtils.importShared(options)).toThrow();
177
183
  });
184
+
185
+ it('should throw error for missing expiration', async () => {
186
+ const options = {
187
+ issuer: bobAddress,
188
+ recipient: aliceAddress,
189
+ issuerSignature: '0x1234567890abcdef',
190
+ } as unknown as ImportSharedPermitOptions;
191
+ expect(() => PermitUtils.importShared(options)).toThrow();
192
+ });
178
193
  });
179
194
 
180
195
  describe('createSelfAndSign', () => {
@@ -217,6 +232,7 @@ describe('PermitUtils Tests', () => {
217
232
  const options: ImportSharedPermitOptions = {
218
233
  issuer: bobAddress,
219
234
  recipient: aliceAddress,
235
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
220
236
  issuerSignature: '0x1234567890abcdef',
221
237
  name: 'Test Import Permit',
222
238
  };
@@ -233,6 +249,7 @@ describe('PermitUtils Tests', () => {
233
249
  const options: ImportSharedPermitOptions = {
234
250
  issuer: bobAddress,
235
251
  recipient: aliceAddress,
252
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
236
253
  issuerSignature: '0x1234567890abcdef',
237
254
  };
238
255
 
@@ -250,6 +267,7 @@ describe('PermitUtils Tests', () => {
250
267
  const options: ImportSharedPermitOptions = {
251
268
  issuer: bobAddress,
252
269
  recipient: aliceAddress,
270
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
253
271
  issuerSignature: '0x1234567890abcdef',
254
272
  };
255
273
 
@@ -283,7 +301,8 @@ describe('PermitUtils Tests', () => {
283
301
  const permit = PermitUtils.importShared({
284
302
  issuer: bobAddress,
285
303
  recipient: aliceAddress,
286
- issuerSignature: '0xexisting-signature',
304
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
305
+ issuerSignature: '0x1111111111111111111111111111111111111111111111111111111111111111',
287
306
  name: 'Test Permit',
288
307
  });
289
308
 
@@ -361,10 +380,7 @@ describe('PermitUtils Tests', () => {
361
380
  name: 'Test Permit',
362
381
  });
363
382
 
364
- const hash1 = PermitUtils.getHash(permit1);
365
- const hash2 = PermitUtils.getHash(permit2);
366
-
367
- expect(hash1).toBe(hash2);
383
+ expect(permit1.hash).toBe(permit2.hash);
368
384
  });
369
385
  });
370
386
 
@@ -473,5 +489,35 @@ describe('PermitUtils Tests', () => {
473
489
 
474
490
  expect(typeof isValid).toBe('boolean');
475
491
  }, 10000); // 10 second timeout for network call
492
+
493
+ // TODO: Uncomment when updated ACL with checkPermitValidity function is deployed
494
+
495
+ // it('should check permit validity on chain with real contract data', async () => {
496
+ // const permit = PermitUtils.createSelf({
497
+ // type: 'self',
498
+ // issuer: bobAddress,
499
+ // name: 'Test Permit',
500
+ // });
501
+
502
+ // const signedPermit = await PermitUtils.sign(permit, publicClient, bobWalletClient);
503
+
504
+ // const isValid = await PermitUtils.checkValidityOnChain(signedPermit, publicClient);
505
+
506
+ // expect(typeof isValid).toBe('boolean');
507
+ // expect(isValid).toBe(true);
508
+
509
+ // const permitInvalid = PermitUtils.createSelf({
510
+ // type: 'self',
511
+ // issuer: bobAddress,
512
+ // name: 'Test Permit',
513
+ // expiration: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
514
+ // });
515
+
516
+ // const signedPermitInvalid = await PermitUtils.sign(permitInvalid, publicClient, bobWalletClient);
517
+ // const isValidInvalid = await PermitUtils.checkValidityOnChain(signedPermitInvalid, publicClient);
518
+
519
+ // expect(typeof isValidInvalid).toBe('boolean');
520
+ // expect(isValidInvalid).toBe(false);
521
+ // });
476
522
  });
477
523
  });
package/permits/permit.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  type EIP712Domain,
12
12
  type Permission,
13
13
  type EthEncryptedData,
14
+ type PermitHashFields,
14
15
  } from './types.js';
15
16
  import {
16
17
  validateSelfPermitOptions,
@@ -21,8 +22,10 @@ import {
21
22
  validateImportPermit,
22
23
  ValidationUtils,
23
24
  } from './validation.js';
25
+ import * as z from 'zod';
24
26
  import { SignatureUtils } from './signature.js';
25
27
  import { GenerateSealingKey, SealingKey } from './sealing.js';
28
+ import { checkPermitValidityOnChain, getAclEIP712Domain } from './onchain-utils.js';
26
29
 
27
30
  /**
28
31
  * Main Permit utilities - functional approach for React compatibility
@@ -34,17 +37,12 @@ export const PermitUtils = {
34
37
  createSelf: (options: CreateSelfPermitOptions): SelfPermit => {
35
38
  const validation = validateSelfPermitOptions(options);
36
39
 
37
- if (!validation.success) {
38
- throw new Error(
39
- 'PermitUtils :: createSelf :: Parsing SelfPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
40
- );
41
- }
42
-
43
40
  // Always generate a new sealing key - users cannot provide their own
44
41
  const sealingPair = GenerateSealingKey();
45
42
 
46
43
  const permit = {
47
- ...validation.data,
44
+ hash: PermitUtils.getHash(validation),
45
+ ...validation,
48
46
  sealingPair,
49
47
  _signedDomain: undefined,
50
48
  } satisfies SelfPermit;
@@ -58,18 +56,12 @@ export const PermitUtils = {
58
56
  createSharing: (options: CreateSharingPermitOptions): SharingPermit => {
59
57
  const validation = validateSharingPermitOptions(options);
60
58
 
61
- if (!validation.success) {
62
- throw new Error(
63
- 'PermitUtils :: createSharing :: Parsing SharingPermitOptions failed ' +
64
- JSON.stringify(validation.error, null, 2)
65
- );
66
- }
67
-
68
59
  // Always generate a new sealing key - users cannot provide their own
69
60
  const sealingPair = GenerateSealingKey();
70
61
 
71
62
  const permit = {
72
- ...validation.data,
63
+ hash: PermitUtils.getHash(validation),
64
+ ...validation,
73
65
  sealingPair,
74
66
  _signedDomain: undefined,
75
67
  } satisfies SharingPermit;
@@ -89,35 +81,28 @@ export const PermitUtils = {
89
81
  try {
90
82
  parsedOptions = JSON.parse(options);
91
83
  } catch (error) {
92
- throw new Error(`PermitUtils :: importShared :: Failed to parse JSON string: ${error}`);
84
+ throw new Error(`Failed to parse JSON string: ${error}`);
93
85
  }
94
86
  } else if (typeof options === 'object' && options !== null) {
95
87
  // Handle both ImportSharedPermitOptions and any object
96
88
  parsedOptions = options;
97
89
  } else {
98
- throw new Error(
99
- 'PermitUtils :: importShared :: Invalid input type, expected ImportSharedPermitOptions, object, or string'
100
- );
90
+ throw new Error('Invalid input type, expected ImportSharedPermitOptions, object, or string');
101
91
  }
102
92
 
103
93
  // Validate type if provided
104
94
  if (parsedOptions.type != null && parsedOptions.type !== 'sharing') {
105
- throw new Error(`PermitUtils :: importShared :: Invalid permit type <${parsedOptions.type}>, must be "sharing"`);
95
+ throw new Error(`Invalid permit type <${parsedOptions.type}>, must be "sharing"`);
106
96
  }
107
97
 
108
98
  const validation = validateImportPermitOptions({ ...parsedOptions, type: 'recipient' });
109
99
 
110
- if (!validation.success) {
111
- throw new Error(
112
- 'PermitUtils :: importShared :: Parsing ImportPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
113
- );
114
- }
115
-
116
100
  // Always generate a new sealing key - users cannot provide their own
117
101
  const sealingPair = GenerateSealingKey();
118
102
 
119
103
  const permit = {
120
- ...validation.data,
104
+ hash: PermitUtils.getHash(validation),
105
+ ...validation,
121
106
  sealingPair,
122
107
  _signedDomain: undefined,
123
108
  } satisfies RecipientPermit;
@@ -131,12 +116,12 @@ export const PermitUtils = {
131
116
  sign: async <T extends Permit>(permit: T, publicClient: PublicClient, walletClient: WalletClient): Promise<T> => {
132
117
  if (walletClient == null || walletClient.account == null) {
133
118
  throw new Error(
134
- 'PermitUtils :: sign - walletClient undefined, you must pass in a `walletClient` for the connected user to create a permit signature'
119
+ 'Missing walletClient, you must pass in a `walletClient` for the connected user to create a permit signature'
135
120
  );
136
121
  }
137
122
 
138
123
  const primaryType = SignatureUtils.getPrimaryType(permit.type);
139
- const domain = await PermitUtils.fetchEIP712Domain(publicClient);
124
+ const domain = await getAclEIP712Domain(publicClient);
140
125
  const { types, message } = SignatureUtils.getSignatureParams(PermitUtils.getPermission(permit, true), primaryType);
141
126
 
142
127
  const signature = await walletClient.signTypedData({
@@ -216,6 +201,7 @@ export const PermitUtils = {
216
201
  */
217
202
  serialize: (permit: Permit): SerializedPermit => {
218
203
  return {
204
+ hash: permit.hash,
219
205
  name: permit.name,
220
206
  type: permit.type,
221
207
  issuer: permit.issuer,
@@ -241,7 +227,7 @@ export const PermitUtils = {
241
227
  } else if (permit.type === 'recipient') {
242
228
  return validateImportPermit(permit);
243
229
  } else {
244
- throw new Error('PermitUtils :: validate :: Invalid permit type');
230
+ throw new Error('Invalid permit type');
245
231
  }
246
232
  },
247
233
 
@@ -250,13 +236,7 @@ export const PermitUtils = {
250
236
  */
251
237
  getPermission: (permit: Permit, skipValidation = false): Permission => {
252
238
  if (!skipValidation) {
253
- const validationResult = PermitUtils.validate(permit);
254
-
255
- if (!validationResult.success) {
256
- throw new Error(
257
- `PermitUtils :: getPermission :: permit validation failed - ${JSON.stringify(validationResult.error, null, 2)} ${JSON.stringify(permit, null, 2)}`
258
- );
259
- }
239
+ PermitUtils.validate(permit);
260
240
  }
261
241
 
262
242
  return {
@@ -274,7 +254,7 @@ export const PermitUtils = {
274
254
  /**
275
255
  * Get a stable hash for the permit (used as key in storage)
276
256
  */
277
- getHash: (permit: Permit): string => {
257
+ getHash: (permit: PermitHashFields): string => {
278
258
  const data = JSON.stringify({
279
259
  type: permit.type,
280
260
  issuer: permit.issuer,
@@ -345,41 +325,7 @@ export const PermitUtils = {
345
325
  * Fetch EIP712 domain from the blockchain
346
326
  */
347
327
  fetchEIP712Domain: async (publicClient: PublicClient): Promise<EIP712Domain> => {
348
- // Hardcoded constants from the original implementation
349
- const TASK_MANAGER_ADDRESS = '0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9';
350
- const ACL_IFACE = 'function acl() view returns (address)';
351
- const EIP712_DOMAIN_IFACE =
352
- 'function eip712Domain() public view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)';
353
-
354
- // Parse the ABI for the ACL function
355
- const aclAbi = parseAbi([ACL_IFACE]);
356
-
357
- // Get the ACL address
358
- const aclAddress = (await publicClient.readContract({
359
- address: TASK_MANAGER_ADDRESS as `0x${string}`,
360
- abi: aclAbi,
361
- functionName: 'acl',
362
- })) as `0x${string}`;
363
-
364
- // Parse the ABI for the EIP712 domain function
365
- const domainAbi = parseAbi([EIP712_DOMAIN_IFACE]);
366
-
367
- // Get the EIP712 domain
368
- const domain = await publicClient.readContract({
369
- address: aclAddress,
370
- abi: domainAbi,
371
- functionName: 'eip712Domain',
372
- });
373
-
374
- // eslint-disable-next-line no-unused-vars
375
- const [_fields, name, version, chainId, verifyingContract, _salt, _extensions] = domain;
376
-
377
- return {
378
- name,
379
- version,
380
- chainId: Number(chainId),
381
- verifyingContract,
382
- };
328
+ return getAclEIP712Domain(publicClient);
383
329
  },
384
330
 
385
331
  /**
@@ -399,7 +345,15 @@ export const PermitUtils = {
399
345
  */
400
346
  checkSignedDomainValid: async (permit: Permit, publicClient: PublicClient): Promise<boolean> => {
401
347
  if (permit._signedDomain == null) return false;
402
- const domain = await PermitUtils.fetchEIP712Domain(publicClient);
348
+ const domain = await getAclEIP712Domain(publicClient);
403
349
  return PermitUtils.matchesDomain(permit, domain);
404
350
  },
351
+
352
+ /**
353
+ * Check if permit passes the on-chain validation
354
+ */
355
+ checkValidityOnChain: async (permit: Permit, publicClient: PublicClient): Promise<boolean> => {
356
+ const permission = PermitUtils.getPermission(permit);
357
+ return checkPermitValidityOnChain(permission, publicClient);
358
+ },
405
359
  };