@cofhe/sdk 0.1.0 → 0.2.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 (121) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/adapters/ethers5.test.ts +174 -0
  3. package/adapters/ethers5.ts +36 -0
  4. package/adapters/ethers6.test.ts +169 -0
  5. package/adapters/ethers6.ts +36 -0
  6. package/adapters/hardhat-node.ts +167 -0
  7. package/adapters/hardhat.hh2.test.ts +159 -0
  8. package/adapters/hardhat.ts +36 -0
  9. package/adapters/index.test.ts +20 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +99 -0
  12. package/adapters/test-utils.ts +53 -0
  13. package/adapters/types.ts +6 -0
  14. package/adapters/wagmi.test.ts +156 -0
  15. package/adapters/wagmi.ts +17 -0
  16. package/chains/chains/arbSepolia.ts +14 -0
  17. package/chains/chains/baseSepolia.ts +14 -0
  18. package/chains/chains/hardhat.ts +15 -0
  19. package/chains/chains/localcofhe.ts +14 -0
  20. package/chains/chains/sepolia.ts +14 -0
  21. package/chains/chains.test.ts +50 -0
  22. package/chains/defineChain.ts +18 -0
  23. package/chains/index.ts +35 -0
  24. package/chains/types.ts +32 -0
  25. package/core/baseBuilder.ts +119 -0
  26. package/core/client.test.ts +315 -0
  27. package/core/client.ts +292 -0
  28. package/core/clientTypes.ts +108 -0
  29. package/core/config.test.ts +235 -0
  30. package/core/config.ts +220 -0
  31. package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
  32. package/core/decrypt/cofheMocksSealOutput.ts +57 -0
  33. package/core/decrypt/decryptHandleBuilder.ts +287 -0
  34. package/core/decrypt/decryptUtils.ts +28 -0
  35. package/core/decrypt/tnSealOutputV1.ts +59 -0
  36. package/core/decrypt/tnSealOutputV2.ts +298 -0
  37. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  38. package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
  39. package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
  40. package/core/encrypt/encryptInputsBuilder.ts +560 -0
  41. package/core/encrypt/encryptUtils.ts +67 -0
  42. package/core/encrypt/zkPackProveVerify.ts +335 -0
  43. package/core/error.ts +168 -0
  44. package/core/fetchKeys.test.ts +195 -0
  45. package/core/fetchKeys.ts +144 -0
  46. package/core/index.ts +89 -0
  47. package/core/keyStore.test.ts +226 -0
  48. package/core/keyStore.ts +154 -0
  49. package/core/permits.test.ts +494 -0
  50. package/core/permits.ts +200 -0
  51. package/core/types.ts +398 -0
  52. package/core/utils.ts +130 -0
  53. package/dist/adapters.cjs +88 -0
  54. package/dist/adapters.d.cts +14576 -0
  55. package/dist/adapters.d.ts +14576 -0
  56. package/dist/adapters.js +83 -0
  57. package/dist/chains.cjs +114 -0
  58. package/dist/chains.d.cts +121 -0
  59. package/dist/chains.d.ts +121 -0
  60. package/dist/chains.js +1 -0
  61. package/dist/chunk-UGBVZNRT.js +818 -0
  62. package/dist/chunk-WEAZ25JO.js +105 -0
  63. package/dist/chunk-WGCRJCBR.js +2523 -0
  64. package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
  65. package/dist/clientTypes-Es7fyi65.d.ts +914 -0
  66. package/dist/core.cjs +3414 -0
  67. package/dist/core.d.cts +111 -0
  68. package/dist/core.d.ts +111 -0
  69. package/dist/core.js +3 -0
  70. package/dist/node.cjs +3286 -0
  71. package/dist/node.d.cts +22 -0
  72. package/dist/node.d.ts +22 -0
  73. package/dist/node.js +91 -0
  74. package/dist/permit-fUSe6KKq.d.cts +349 -0
  75. package/dist/permit-fUSe6KKq.d.ts +349 -0
  76. package/dist/permits.cjs +871 -0
  77. package/dist/permits.d.cts +1045 -0
  78. package/dist/permits.d.ts +1045 -0
  79. package/dist/permits.js +1 -0
  80. package/dist/types-KImPrEIe.d.cts +48 -0
  81. package/dist/types-KImPrEIe.d.ts +48 -0
  82. package/dist/web.cjs +3478 -0
  83. package/dist/web.d.cts +38 -0
  84. package/dist/web.d.ts +38 -0
  85. package/dist/web.js +240 -0
  86. package/dist/zkProve.worker.cjs +93 -0
  87. package/dist/zkProve.worker.d.cts +2 -0
  88. package/dist/zkProve.worker.d.ts +2 -0
  89. package/dist/zkProve.worker.js +91 -0
  90. package/node/client.test.ts +147 -0
  91. package/node/config.test.ts +68 -0
  92. package/node/encryptInputs.test.ts +155 -0
  93. package/node/index.ts +97 -0
  94. package/node/storage.ts +51 -0
  95. package/package.json +27 -15
  96. package/permits/index.ts +68 -0
  97. package/permits/localstorage.test.ts +117 -0
  98. package/permits/permit.test.ts +477 -0
  99. package/permits/permit.ts +405 -0
  100. package/permits/sealing.test.ts +84 -0
  101. package/permits/sealing.ts +131 -0
  102. package/permits/signature.ts +79 -0
  103. package/permits/store.test.ts +128 -0
  104. package/permits/store.ts +166 -0
  105. package/permits/test-utils.ts +20 -0
  106. package/permits/types.ts +191 -0
  107. package/permits/utils.ts +62 -0
  108. package/permits/validation.test.ts +288 -0
  109. package/permits/validation.ts +369 -0
  110. package/web/client.web.test.ts +147 -0
  111. package/web/config.web.test.ts +69 -0
  112. package/web/encryptInputs.web.test.ts +172 -0
  113. package/web/index.ts +161 -0
  114. package/web/storage.ts +34 -0
  115. package/web/worker.builder.web.test.ts +148 -0
  116. package/web/worker.config.web.test.ts +329 -0
  117. package/web/worker.output.web.test.ts +84 -0
  118. package/web/workerManager.test.ts +80 -0
  119. package/web/workerManager.ts +214 -0
  120. package/web/workerManager.web.test.ts +114 -0
  121. package/web/zkProve.worker.ts +133 -0
@@ -0,0 +1,288 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ ValidationUtils,
4
+ validateSelfPermitOptions,
5
+ validateSharingPermitOptions,
6
+ validateImportPermitOptions,
7
+ validateSelfPermit,
8
+ validateSharingPermit,
9
+ validateImportPermit,
10
+ type Permit,
11
+ type CreateSelfPermitOptions,
12
+ type CreateSharingPermitOptions,
13
+ type ImportSharedPermitOptions,
14
+ } from './index.js';
15
+ import { createMockPermit } from './test-utils.js';
16
+
17
+ describe('Validation Tests', () => {
18
+ describe('validateSelfPermitOptions', () => {
19
+ it('should validate valid self permit options', () => {
20
+ const options: CreateSelfPermitOptions = {
21
+ type: 'self',
22
+ issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
23
+ name: 'Test Permit',
24
+ };
25
+
26
+ const result = validateSelfPermitOptions(options);
27
+ expect(result.success).toBe(true);
28
+ expect(result.data).toBeDefined();
29
+ });
30
+
31
+ it('should reject invalid address', () => {
32
+ const options: CreateSelfPermitOptions = {
33
+ type: 'self',
34
+ issuer: 'invalid-address',
35
+ name: 'Test Permit',
36
+ };
37
+
38
+ const result = validateSelfPermitOptions(options);
39
+ expect(result.success).toBe(false);
40
+ expect(result.error).toBeDefined();
41
+ });
42
+
43
+ it('should reject zero address', () => {
44
+ const options: CreateSelfPermitOptions = {
45
+ type: 'self',
46
+ issuer: '0x0000000000000000000000000000000000000000',
47
+ name: 'Test Permit',
48
+ };
49
+
50
+ const result = validateSelfPermitOptions(options);
51
+ expect(result.success).toBe(false);
52
+ expect(result.error).toBeDefined();
53
+ });
54
+ });
55
+
56
+ describe('validateSharingPermitOptions', () => {
57
+ it('should validate valid sharing permit options', () => {
58
+ const options: CreateSharingPermitOptions = {
59
+ type: 'sharing',
60
+ issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
61
+ recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
62
+ name: 'Sharing Permit',
63
+ };
64
+
65
+ const result = validateSharingPermitOptions(options);
66
+ expect(result.success).toBe(true);
67
+ });
68
+
69
+ it('should reject sharing permit with zero recipient', () => {
70
+ const options: CreateSharingPermitOptions = {
71
+ type: 'sharing',
72
+ issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
73
+ recipient: '0x0000000000000000000000000000000000000000',
74
+ name: 'Sharing Permit',
75
+ };
76
+
77
+ const result = validateSharingPermitOptions(options);
78
+ expect(result.success).toBe(false);
79
+ });
80
+
81
+ it('should reject sharing permit with invalid recipient', () => {
82
+ const options: CreateSharingPermitOptions = {
83
+ type: 'sharing',
84
+ issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
85
+ recipient: 'invalid-address',
86
+ name: 'Sharing Permit',
87
+ };
88
+
89
+ const result = validateSharingPermitOptions(options);
90
+ expect(result.success).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe('validateImportPermitOptions', () => {
95
+ it('should validate valid import permit options', () => {
96
+ const options: ImportSharedPermitOptions = {
97
+ issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
98
+ recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
99
+ issuerSignature: '0x1234567890abcdef',
100
+ name: 'Import Permit',
101
+ };
102
+
103
+ const result = validateImportPermitOptions(options);
104
+ expect(result.success).toBe(true);
105
+ });
106
+
107
+ it('should reject import permit with empty signature', () => {
108
+ const options: ImportSharedPermitOptions = {
109
+ issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
110
+ recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
111
+ issuerSignature: '0x',
112
+ name: 'Import Permit',
113
+ };
114
+
115
+ const result = validateImportPermitOptions(options);
116
+ expect(result.success).toBe(false);
117
+ });
118
+
119
+ it('should reject import permit with invalid signature', () => {
120
+ const options: ImportSharedPermitOptions = {
121
+ issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
122
+ recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
123
+ issuerSignature: '0x',
124
+ name: 'Import Permit',
125
+ };
126
+
127
+ const result = validateImportPermitOptions(options);
128
+ expect(result.success).toBe(false);
129
+ });
130
+ });
131
+
132
+ describe('validateSelfPermit', () => {
133
+ it('should validate valid self permit', async () => {
134
+ const permit = await createMockPermit();
135
+ permit.type = 'self';
136
+ permit.issuerSignature = '0x1234567890abcdef';
137
+
138
+ const result = validateSelfPermit(permit);
139
+ expect(result.success).toBe(true);
140
+ });
141
+
142
+ it('should reject self permit with missing sealing pair', async () => {
143
+ const permit = { ...(await createMockPermit()), sealingPair: undefined };
144
+ permit.type = 'self';
145
+ const result = validateSelfPermit(permit as unknown as Permit);
146
+ expect(result.success).toBe(false);
147
+ });
148
+ });
149
+
150
+ describe('validateSharingPermit', () => {
151
+ it('should validate valid sharing permit', async () => {
152
+ const permit = await createMockPermit();
153
+ permit.type = 'sharing';
154
+ permit.issuerSignature = '0x1234567890abcdef';
155
+ permit.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
156
+
157
+ const result = validateSharingPermit(permit);
158
+ expect(result.success).toBe(true);
159
+ });
160
+
161
+ it('should reject sharing permit with zero recipient', async () => {
162
+ const permit = await createMockPermit();
163
+ permit.type = 'sharing';
164
+ permit.issuerSignature = '0x1234567890abcdef';
165
+ permit.recipient = '0x0000000000000000000000000000000000000000';
166
+
167
+ const result = validateSharingPermit(permit);
168
+ expect(result.success).toBe(false);
169
+ });
170
+ });
171
+
172
+ describe('validateImportPermit', () => {
173
+ it('should validate valid import permit', async () => {
174
+ const permit = await createMockPermit();
175
+ permit.type = 'recipient';
176
+ permit.issuerSignature = '0x1234567890abcdef';
177
+ permit.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
178
+ permit.recipientSignature = '0xabcdef1234567890';
179
+
180
+ const result = validateImportPermit(permit);
181
+ expect(result.success).toBe(true);
182
+ });
183
+
184
+ it('should reject import permit with empty recipient signature', async () => {
185
+ const permit = await createMockPermit();
186
+ permit.type = 'recipient';
187
+ permit.issuerSignature = '0x1234567890abcdef';
188
+ permit.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
189
+ permit.recipientSignature = '0x';
190
+
191
+ const result = validateImportPermit(permit);
192
+ expect(result.success).toBe(false);
193
+ });
194
+ });
195
+
196
+ describe('ValidationUtils', () => {
197
+ describe('isExpired', () => {
198
+ it('should return true for expired permit', async () => {
199
+ const permit = {
200
+ ...(await createMockPermit()),
201
+ expiration: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
202
+ };
203
+ expect(ValidationUtils.isExpired(permit)).toBe(true);
204
+ });
205
+
206
+ it('should return false for non-expired permit', async () => {
207
+ const permit = {
208
+ ...(await createMockPermit()),
209
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
210
+ };
211
+ expect(ValidationUtils.isExpired(permit)).toBe(false);
212
+ });
213
+ });
214
+
215
+ describe('isSigned', () => {
216
+ it('should return true for signed self permit', async () => {
217
+ const permit = {
218
+ ...(await createMockPermit()),
219
+ type: 'self' as const,
220
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
221
+ };
222
+ expect(ValidationUtils.isSigned(permit)).toBe(true);
223
+ });
224
+
225
+ it('should return false for unsigned self permit', async () => {
226
+ const permit = {
227
+ ...(await createMockPermit()),
228
+ type: 'self' as const,
229
+ issuerSignature: '0x' as `0x${string}`,
230
+ };
231
+ expect(ValidationUtils.isSigned(permit)).toBe(false);
232
+ });
233
+
234
+ it('should return true for signed recipient permit', async () => {
235
+ const permit = {
236
+ ...(await createMockPermit()),
237
+ type: 'recipient' as const,
238
+ recipientSignature: '0x1234567890abcdef' as `0x${string}`,
239
+ };
240
+ expect(ValidationUtils.isSigned(permit)).toBe(true);
241
+ });
242
+
243
+ it('should return false for unsigned recipient permit', async () => {
244
+ const permit = {
245
+ ...(await createMockPermit()),
246
+ type: 'recipient' as const,
247
+ recipientSignature: '0x' as `0x${string}`,
248
+ };
249
+ expect(ValidationUtils.isSigned(permit)).toBe(false);
250
+ });
251
+ });
252
+
253
+ describe('isValid', () => {
254
+ it('should return valid for valid permit', async () => {
255
+ const permit = {
256
+ ...(await createMockPermit()),
257
+ expiration: Math.floor(Date.now() / 1000) + 3600,
258
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
259
+ };
260
+ const result = ValidationUtils.isValid(permit);
261
+ expect(result.valid).toBe(true);
262
+ expect(result.error).toBeNull();
263
+ });
264
+
265
+ it('should return invalid for expired permit', async () => {
266
+ const permit = {
267
+ ...(await createMockPermit()),
268
+ expiration: Math.floor(Date.now() / 1000) - 3600,
269
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
270
+ };
271
+ const result = ValidationUtils.isValid(permit);
272
+ expect(result.valid).toBe(false);
273
+ expect(result.error).toBe('expired');
274
+ });
275
+
276
+ it('should return invalid for unsigned permit', async () => {
277
+ const permit = {
278
+ ...(await createMockPermit()),
279
+ expiration: Math.floor(Date.now() / 1000) + 3600,
280
+ issuerSignature: '0x' as `0x${string}`,
281
+ };
282
+ const result = ValidationUtils.isValid(permit);
283
+ expect(result.valid).toBe(false);
284
+ expect(result.error).toBe('not-signed');
285
+ });
286
+ });
287
+ });
288
+ });
@@ -0,0 +1,369 @@
1
+ import { z } from 'zod';
2
+ import { isAddress, zeroAddress } from 'viem';
3
+ import { type Permit, type ValidationResult } from './types.js';
4
+ import { is0xPrefixed } from './utils.js';
5
+
6
+ const SerializedSealingPair = z.object({
7
+ privateKey: z.string(),
8
+ publicKey: z.string(),
9
+ });
10
+
11
+ const DEFAULT_EXPIRATION_FN = () => Math.round(Date.now() / 1000) + 7 * 24 * 60 * 60; // 7 days from now
12
+
13
+ const zPermitWithDefaults = z.object({
14
+ name: z.string().optional().default('Unnamed Permit'),
15
+ type: z.enum(['self', 'sharing', 'recipient']),
16
+ issuer: z
17
+ .string()
18
+ .refine((val) => isAddress(val), {
19
+ message: 'Permit issuer :: invalid address',
20
+ })
21
+ .refine((val) => val !== zeroAddress, {
22
+ message: 'Permit issuer :: must not be zeroAddress',
23
+ }),
24
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
25
+ recipient: z
26
+ .string()
27
+ .optional()
28
+ .default(zeroAddress)
29
+ .refine((val) => isAddress(val), {
30
+ message: 'Permit recipient :: invalid address',
31
+ }),
32
+ validatorId: z.number().optional().default(0),
33
+ validatorContract: z
34
+ .string()
35
+ .optional()
36
+ .default(zeroAddress)
37
+ .refine((val) => isAddress(val), {
38
+ message: 'Permit validatorContract :: invalid address',
39
+ }),
40
+ issuerSignature: z.string().optional().default('0x'),
41
+ recipientSignature: z.string().optional().default('0x'),
42
+ });
43
+
44
+ const zPermitWithSealingPair = zPermitWithDefaults.extend({
45
+ sealingPair: SerializedSealingPair.optional(),
46
+ });
47
+
48
+ type zPermitType = z.infer<typeof zPermitWithDefaults>;
49
+
50
+ /**
51
+ * Permits allow a hook into an optional external validator contract,
52
+ * this check ensures that IF an external validator is applied, that both `validatorId` and `validatorContract` are populated,
53
+ * ELSE ensures that both `validatorId` and `validatorContract` are empty
54
+ */
55
+ const ValidatorContractRefinement = [
56
+ (data: zPermitType) =>
57
+ (data.validatorId !== 0 && data.validatorContract !== zeroAddress) ||
58
+ (data.validatorId === 0 && data.validatorContract === zeroAddress),
59
+ {
60
+ message: 'Permit external validator :: validatorId and validatorContract must either both be set or both be unset.',
61
+ path: ['validatorId', 'validatorContract'] as string[],
62
+ },
63
+ ] as const;
64
+
65
+ // ============================================================================
66
+ // SELF PERMIT VALIDATORS
67
+ // ============================================================================
68
+
69
+ /**
70
+ * Validator for self permit creation options
71
+ */
72
+ export const SelfPermitOptionsValidator = z
73
+ .object({
74
+ type: z.literal('self').optional().default('self'),
75
+ issuer: z
76
+ .string()
77
+ .refine((val) => isAddress(val), {
78
+ message: 'Self permit issuer :: invalid address',
79
+ })
80
+ .refine((val) => is0xPrefixed(val), {
81
+ message: 'Self permit issuer :: must be 0x prefixed',
82
+ })
83
+ .refine((val) => val !== zeroAddress, {
84
+ message: 'Self permit issuer :: must not be zeroAddress',
85
+ }),
86
+ name: z.string().optional().default('Unnamed Permit'),
87
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
88
+ recipient: z
89
+ .string()
90
+ .optional()
91
+ .default(zeroAddress)
92
+ .refine((val) => isAddress(val), {
93
+ message: 'Self permit recipient :: invalid address',
94
+ })
95
+ .refine((val) => is0xPrefixed(val), {
96
+ message: 'Self permit recipient :: must be 0x prefixed',
97
+ })
98
+ .refine((val) => val === zeroAddress, {
99
+ message: 'Self permit recipient :: must be zeroAddress',
100
+ }),
101
+ validatorId: z.number().optional().default(0),
102
+ validatorContract: z
103
+ .string()
104
+ .optional()
105
+ .default(zeroAddress)
106
+ .refine((val) => isAddress(val), {
107
+ message: 'Self permit validatorContract :: invalid address',
108
+ }),
109
+ issuerSignature: z
110
+ .string()
111
+ .optional()
112
+ .default('0x')
113
+ .refine((val) => is0xPrefixed(val), {
114
+ message: 'Self permit issuerSignature :: must be 0x prefixed',
115
+ }),
116
+ recipientSignature: z
117
+ .string()
118
+ .optional()
119
+ .default('0x')
120
+ .refine((val) => is0xPrefixed(val), {
121
+ message: 'Self permit recipientSignature :: must be 0x prefixed',
122
+ }),
123
+ })
124
+ .refine(...ValidatorContractRefinement);
125
+
126
+ /**
127
+ * Validator for fully formed self permits
128
+ */
129
+ export const SelfPermitValidator = zPermitWithSealingPair
130
+ .refine((data) => data.type === 'self', {
131
+ message: "Self permit :: type must be 'self'",
132
+ })
133
+ .refine((data) => data.recipient === zeroAddress, {
134
+ message: 'Self permit :: recipient must be zeroAddress',
135
+ })
136
+ .refine((data) => data.issuerSignature !== '0x', {
137
+ message: 'Self permit :: issuerSignature must be populated',
138
+ })
139
+ .refine((data) => data.recipientSignature === '0x', {
140
+ message: 'Self permit :: recipientSignature must be empty',
141
+ })
142
+ .refine(...ValidatorContractRefinement);
143
+
144
+ // ============================================================================
145
+ // SHARING PERMIT VALIDATORS
146
+ // ============================================================================
147
+
148
+ /**
149
+ * Validator for sharing permit creation options
150
+ */
151
+ export const SharingPermitOptionsValidator = z
152
+ .object({
153
+ type: z.literal('sharing').optional().default('sharing'),
154
+ issuer: z
155
+ .string()
156
+ .refine((val) => isAddress(val), {
157
+ message: 'Sharing permit issuer :: invalid address',
158
+ })
159
+ .refine((val) => is0xPrefixed(val), {
160
+ message: 'Sharing permit issuer :: must be 0x prefixed',
161
+ })
162
+ .refine((val) => val !== zeroAddress, {
163
+ message: 'Sharing permit issuer :: must not be zeroAddress',
164
+ }),
165
+ recipient: z
166
+ .string()
167
+ .refine((val) => isAddress(val), {
168
+ message: 'Sharing permit recipient :: invalid address',
169
+ })
170
+ .refine((val) => is0xPrefixed(val), {
171
+ message: 'Sharing permit recipient :: must be 0x prefixed',
172
+ })
173
+ .refine((val) => val !== zeroAddress, {
174
+ message: 'Sharing permit recipient :: must not be zeroAddress',
175
+ }),
176
+ name: z.string().optional().default('Unnamed Permit'),
177
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
178
+ validatorId: z.number().optional().default(0),
179
+ validatorContract: z
180
+ .string()
181
+ .optional()
182
+ .default(zeroAddress)
183
+ .refine((val) => isAddress(val), {
184
+ message: 'Sharing permit validatorContract :: invalid address',
185
+ }),
186
+ issuerSignature: z
187
+ .string()
188
+ .optional()
189
+ .default('0x')
190
+ .refine((val) => is0xPrefixed(val), {
191
+ message: 'Sharing permit issuerSignature :: must be 0x prefixed',
192
+ }),
193
+ recipientSignature: z
194
+ .string()
195
+ .optional()
196
+ .default('0x')
197
+ .refine((val) => is0xPrefixed(val), {
198
+ message: 'Sharing permit recipientSignature :: must be 0x prefixed',
199
+ }),
200
+ })
201
+ .refine(...ValidatorContractRefinement);
202
+
203
+ /**
204
+ * Validator for fully formed sharing permits
205
+ */
206
+ export const SharingPermitValidator = zPermitWithSealingPair
207
+ .refine((data) => data.type === 'sharing', {
208
+ message: "Sharing permit :: type must be 'sharing'",
209
+ })
210
+ .refine((data) => data.recipient !== zeroAddress, {
211
+ message: 'Sharing permit :: recipient must not be zeroAddress',
212
+ })
213
+ .refine((data) => data.issuerSignature !== '0x', {
214
+ message: 'Sharing permit :: issuerSignature must be populated',
215
+ })
216
+ .refine((data) => data.recipientSignature === '0x', {
217
+ message: 'Sharing permit :: recipientSignature must be empty',
218
+ })
219
+ .refine(...ValidatorContractRefinement);
220
+
221
+ // ============================================================================
222
+ // IMPORT/RECIPIENT PERMIT VALIDATORS
223
+ // ============================================================================
224
+
225
+ /**
226
+ * Validator for import permit creation options (recipient receiving shared permit)
227
+ */
228
+ export const ImportPermitOptionsValidator = z
229
+ .object({
230
+ type: z.literal('recipient').optional().default('recipient'),
231
+ issuer: z
232
+ .string()
233
+ .refine((val) => isAddress(val), {
234
+ message: 'Import permit issuer :: invalid address',
235
+ })
236
+ .refine((val) => is0xPrefixed(val), {
237
+ message: 'Import permit issuer :: must be 0x prefixed',
238
+ })
239
+ .refine((val) => val !== zeroAddress, {
240
+ message: 'Import permit issuer :: must not be zeroAddress',
241
+ }),
242
+ recipient: z
243
+ .string()
244
+ .refine((val) => isAddress(val), {
245
+ message: 'Import permit recipient :: invalid address',
246
+ })
247
+ .refine((val) => is0xPrefixed(val), {
248
+ message: 'Import permit recipient :: must be 0x prefixed',
249
+ })
250
+ .refine((val) => val !== zeroAddress, {
251
+ message: 'Import permit recipient :: must not be zeroAddress',
252
+ }),
253
+ issuerSignature: z
254
+ .string()
255
+ .refine((val) => is0xPrefixed(val), {
256
+ message: 'Import permit issuerSignature :: must be 0x prefixed',
257
+ })
258
+ .refine((val) => val !== '0x', {
259
+ message: 'Import permit :: issuerSignature must be provided',
260
+ }),
261
+ name: z.string().optional().default('Unnamed Permit'),
262
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
263
+ validatorId: z.number().optional().default(0),
264
+ validatorContract: z
265
+ .string()
266
+ .optional()
267
+ .default(zeroAddress)
268
+ .refine((val) => isAddress(val), {
269
+ message: 'Import permit validatorContract :: invalid address',
270
+ }),
271
+ recipientSignature: z
272
+ .string()
273
+ .optional()
274
+ .default('0x')
275
+ .refine((val) => is0xPrefixed(val), {
276
+ message: 'Import permit recipientSignature :: must be 0x prefixed',
277
+ }),
278
+ })
279
+ .refine(...ValidatorContractRefinement);
280
+
281
+ /**
282
+ * Validator for fully formed import/recipient permits
283
+ */
284
+ export const ImportPermitValidator = zPermitWithSealingPair
285
+ .refine((data) => data.type === 'recipient', {
286
+ message: "Import permit :: type must be 'recipient'",
287
+ })
288
+ .refine((data) => data.recipient !== zeroAddress, {
289
+ message: 'Import permit :: recipient must not be zeroAddress',
290
+ })
291
+ .refine((data) => data.issuerSignature !== '0x', {
292
+ message: 'Import permit :: issuerSignature must be populated',
293
+ })
294
+ .refine((data) => data.recipientSignature !== '0x', {
295
+ message: 'Import permit :: recipientSignature must be populated',
296
+ })
297
+ .refine(...ValidatorContractRefinement);
298
+
299
+ // ============================================================================
300
+ // VALIDATION FUNCTIONS
301
+ // ============================================================================
302
+
303
+ /**
304
+ * Validates self permit creation options
305
+ */
306
+ export const validateSelfPermitOptions = (options: any) => SelfPermitOptionsValidator.safeParse(options);
307
+
308
+ /**
309
+ * Validates sharing permit creation options
310
+ */
311
+ export const validateSharingPermitOptions = (options: any) => SharingPermitOptionsValidator.safeParse(options);
312
+
313
+ /**
314
+ * Validates import permit creation options
315
+ */
316
+ export const validateImportPermitOptions = (options: any) => ImportPermitOptionsValidator.safeParse(options);
317
+
318
+ /**
319
+ * Validates a fully formed self permit
320
+ */
321
+ export const validateSelfPermit = (permit: any) => SelfPermitValidator.safeParse(permit);
322
+
323
+ /**
324
+ * Validates a fully formed sharing permit
325
+ */
326
+ export const validateSharingPermit = (permit: any) => SharingPermitValidator.safeParse(permit);
327
+
328
+ /**
329
+ * Validates a fully formed import/recipient permit
330
+ */
331
+ export const validateImportPermit = (permit: any) => ImportPermitValidator.safeParse(permit);
332
+
333
+ /**
334
+ * Simple validation functions for common checks
335
+ */
336
+ export const ValidationUtils = {
337
+ /**
338
+ * Check if permit is expired
339
+ */
340
+ isExpired: (permit: Permit): boolean => {
341
+ return permit.expiration < Math.floor(Date.now() / 1000);
342
+ },
343
+
344
+ /**
345
+ * Check if permit is signed by the active party
346
+ */
347
+ isSigned: (permit: Permit): boolean => {
348
+ if (permit.type === 'self' || permit.type === 'sharing') {
349
+ return permit.issuerSignature !== '0x';
350
+ }
351
+ if (permit.type === 'recipient') {
352
+ return permit.recipientSignature !== '0x';
353
+ }
354
+ return false;
355
+ },
356
+
357
+ /**
358
+ * Overall validity checker of a permit
359
+ */
360
+ isValid: (permit: Permit): ValidationResult => {
361
+ if (ValidationUtils.isExpired(permit)) {
362
+ return { valid: false, error: 'expired' };
363
+ }
364
+ if (!ValidationUtils.isSigned(permit)) {
365
+ return { valid: false, error: 'not-signed' };
366
+ }
367
+ return { valid: true, error: null };
368
+ },
369
+ };