@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.
- package/CHANGELOG.md +62 -0
- package/adapters/ethers5.test.ts +174 -0
- package/adapters/ethers5.ts +36 -0
- package/adapters/ethers6.test.ts +169 -0
- package/adapters/ethers6.ts +36 -0
- package/adapters/hardhat-node.ts +167 -0
- package/adapters/hardhat.hh2.test.ts +159 -0
- package/adapters/hardhat.ts +36 -0
- package/adapters/index.test.ts +20 -0
- package/adapters/index.ts +5 -0
- package/adapters/smartWallet.ts +99 -0
- package/adapters/test-utils.ts +53 -0
- package/adapters/types.ts +6 -0
- package/adapters/wagmi.test.ts +156 -0
- package/adapters/wagmi.ts +17 -0
- package/chains/chains/arbSepolia.ts +14 -0
- package/chains/chains/baseSepolia.ts +14 -0
- package/chains/chains/hardhat.ts +15 -0
- package/chains/chains/localcofhe.ts +14 -0
- package/chains/chains/sepolia.ts +14 -0
- package/chains/chains.test.ts +50 -0
- package/chains/defineChain.ts +18 -0
- package/chains/index.ts +35 -0
- package/chains/types.ts +32 -0
- package/core/baseBuilder.ts +119 -0
- package/core/client.test.ts +315 -0
- package/core/client.ts +292 -0
- package/core/clientTypes.ts +108 -0
- package/core/config.test.ts +235 -0
- package/core/config.ts +220 -0
- package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
- package/core/decrypt/cofheMocksSealOutput.ts +57 -0
- package/core/decrypt/decryptHandleBuilder.ts +287 -0
- package/core/decrypt/decryptUtils.ts +28 -0
- package/core/decrypt/tnSealOutputV1.ts +59 -0
- package/core/decrypt/tnSealOutputV2.ts +298 -0
- package/core/encrypt/MockZkVerifierAbi.ts +106 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
- package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
- package/core/encrypt/encryptInputsBuilder.ts +560 -0
- package/core/encrypt/encryptUtils.ts +67 -0
- package/core/encrypt/zkPackProveVerify.ts +335 -0
- package/core/error.ts +168 -0
- package/core/fetchKeys.test.ts +195 -0
- package/core/fetchKeys.ts +144 -0
- package/core/index.ts +89 -0
- package/core/keyStore.test.ts +226 -0
- package/core/keyStore.ts +154 -0
- package/core/permits.test.ts +494 -0
- package/core/permits.ts +200 -0
- package/core/types.ts +398 -0
- package/core/utils.ts +130 -0
- package/dist/adapters.cjs +88 -0
- package/dist/adapters.d.cts +14576 -0
- package/dist/adapters.d.ts +14576 -0
- package/dist/adapters.js +83 -0
- package/dist/chains.cjs +114 -0
- package/dist/chains.d.cts +121 -0
- package/dist/chains.d.ts +121 -0
- package/dist/chains.js +1 -0
- package/dist/chunk-UGBVZNRT.js +818 -0
- package/dist/chunk-WEAZ25JO.js +105 -0
- package/dist/chunk-WGCRJCBR.js +2523 -0
- package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
- package/dist/clientTypes-Es7fyi65.d.ts +914 -0
- package/dist/core.cjs +3414 -0
- package/dist/core.d.cts +111 -0
- package/dist/core.d.ts +111 -0
- package/dist/core.js +3 -0
- package/dist/node.cjs +3286 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +91 -0
- package/dist/permit-fUSe6KKq.d.cts +349 -0
- package/dist/permit-fUSe6KKq.d.ts +349 -0
- package/dist/permits.cjs +871 -0
- package/dist/permits.d.cts +1045 -0
- package/dist/permits.d.ts +1045 -0
- package/dist/permits.js +1 -0
- package/dist/types-KImPrEIe.d.cts +48 -0
- package/dist/types-KImPrEIe.d.ts +48 -0
- package/dist/web.cjs +3478 -0
- package/dist/web.d.cts +38 -0
- package/dist/web.d.ts +38 -0
- package/dist/web.js +240 -0
- package/dist/zkProve.worker.cjs +93 -0
- package/dist/zkProve.worker.d.cts +2 -0
- package/dist/zkProve.worker.d.ts +2 -0
- package/dist/zkProve.worker.js +91 -0
- package/node/client.test.ts +147 -0
- package/node/config.test.ts +68 -0
- package/node/encryptInputs.test.ts +155 -0
- package/node/index.ts +97 -0
- package/node/storage.ts +51 -0
- package/package.json +27 -15
- package/permits/index.ts +68 -0
- package/permits/localstorage.test.ts +117 -0
- package/permits/permit.test.ts +477 -0
- package/permits/permit.ts +405 -0
- package/permits/sealing.test.ts +84 -0
- package/permits/sealing.ts +131 -0
- package/permits/signature.ts +79 -0
- package/permits/store.test.ts +128 -0
- package/permits/store.ts +166 -0
- package/permits/test-utils.ts +20 -0
- package/permits/types.ts +191 -0
- package/permits/utils.ts +62 -0
- package/permits/validation.test.ts +288 -0
- package/permits/validation.ts +369 -0
- package/web/client.web.test.ts +147 -0
- package/web/config.web.test.ts +69 -0
- package/web/encryptInputs.web.test.ts +172 -0
- package/web/index.ts +161 -0
- package/web/storage.ts +34 -0
- package/web/worker.builder.web.test.ts +148 -0
- package/web/worker.config.web.test.ts +329 -0
- package/web/worker.output.web.test.ts +84 -0
- package/web/workerManager.test.ts +80 -0
- package/web/workerManager.ts +214 -0
- package/web/workerManager.web.test.ts +114 -0
- 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
|
+
};
|