@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,494 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
4
+ import { permitStore } from '@/permits';
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
8
+ import { arbitrumSepolia } from 'viem/chains';
9
+ import { privateKeyToAccount } from 'viem/accounts';
10
+ import { permits } from './permits.js';
11
+
12
+ // Type declarations for happy-dom environment
13
+ declare const localStorage: {
14
+ clear: () => void;
15
+ getItem: (name: string) => string | null;
16
+ setItem: (name: string, value: string) => void;
17
+ };
18
+
19
+ // Test private keys (well-known test keys from Anvil/Hardhat)
20
+ const BOB_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Bob - always issuer
21
+ const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; // Alice - always recipient
22
+
23
+ // Create real viem clients for Arbitrum Sepolia
24
+ const publicClient: PublicClient = createPublicClient({
25
+ chain: arbitrumSepolia,
26
+ transport: http(),
27
+ });
28
+
29
+ const bobWalletClient: WalletClient = createWalletClient({
30
+ chain: arbitrumSepolia,
31
+ transport: http(),
32
+ account: privateKeyToAccount(BOB_PRIVATE_KEY),
33
+ });
34
+
35
+ const aliceWalletClient: WalletClient = createWalletClient({
36
+ chain: arbitrumSepolia,
37
+ transport: http(),
38
+ account: privateKeyToAccount(ALICE_PRIVATE_KEY),
39
+ });
40
+
41
+ // Helper to get the wallet addresses
42
+ const bobAddress = bobWalletClient.account!.address;
43
+ const aliceAddress = aliceWalletClient.account!.address;
44
+ const chainId = 421614; // Arbitrum Sepolia
45
+
46
+ describe('Core Permits Tests', () => {
47
+ beforeEach(() => {
48
+ // Clear localStorage and reset stores
49
+ localStorage.clear();
50
+ permitStore.store.setState({ permits: {}, activePermitHash: {} });
51
+ });
52
+
53
+ afterEach(() => {
54
+ localStorage.clear();
55
+ permitStore.store.setState({ permits: {}, activePermitHash: {} });
56
+ });
57
+
58
+ describe('Permit Creation', () => {
59
+ it('should create and store self permit', async () => {
60
+ const permit = await permits.createSelf(
61
+ { name: 'Test Self Permit', issuer: bobAddress },
62
+ publicClient,
63
+ bobWalletClient
64
+ );
65
+
66
+ expect(permit).toBeDefined();
67
+ expect(permit.name).toBe('Test Self Permit');
68
+ expect(permit.type).toBe('self');
69
+ expect(permit.issuer).toBe(bobAddress);
70
+ expect(permit.issuerSignature).toBeDefined();
71
+ expect(permit.issuerSignature).not.toBe('0x');
72
+
73
+ // Verify localStorage
74
+ const storedData = localStorage.getItem('cofhesdk-permits');
75
+ expect(storedData).toBeDefined();
76
+ const parsedData = JSON.parse(storedData!);
77
+ expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
78
+ expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
79
+ });
80
+
81
+ it('should create and store sharing permit', async () => {
82
+ const permit = await permits.createSharing(
83
+ {
84
+ name: 'Test Sharing Permit',
85
+ issuer: bobAddress,
86
+ recipient: aliceAddress,
87
+ },
88
+ publicClient,
89
+ bobWalletClient
90
+ );
91
+
92
+ expect(permit.name).toBe('Test Sharing Permit');
93
+ expect(permit.type).toBe('sharing');
94
+ expect(permit.issuer).toBe(bobAddress);
95
+ expect(permit.recipient).toBe(aliceAddress);
96
+ expect(permit.issuerSignature).toBeDefined();
97
+ expect(permit.issuerSignature).not.toBe('0x');
98
+
99
+ // Verify localStorage
100
+ const storedData = localStorage.getItem('cofhesdk-permits');
101
+ expect(storedData).toBeDefined();
102
+ const parsedData = JSON.parse(storedData!);
103
+ expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
104
+ expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
105
+ });
106
+
107
+ it('should import shared permit from JSON string', async () => {
108
+ // First create a sharing permit to import
109
+ const sharingPermit = await permits.createSharing(
110
+ {
111
+ name: 'Original Sharing Permit',
112
+ issuer: bobAddress,
113
+ recipient: aliceAddress,
114
+ },
115
+ publicClient,
116
+ bobWalletClient
117
+ );
118
+
119
+ // Export the permit as JSON string
120
+ const permitJson = JSON.stringify({
121
+ name: sharingPermit.name,
122
+ type: sharingPermit.type,
123
+ issuer: sharingPermit.issuer,
124
+ expiration: sharingPermit.expiration,
125
+ recipient: sharingPermit.recipient,
126
+ validatorId: sharingPermit.validatorId,
127
+ validatorContract: sharingPermit.validatorContract,
128
+ issuerSignature: sharingPermit.issuerSignature,
129
+ });
130
+
131
+ // Import the permit as Alice (recipient)
132
+ const permit = await permits.importShared(permitJson, publicClient, aliceWalletClient);
133
+
134
+ expect(permit.name).toBe('Original Sharing Permit');
135
+ expect(permit.type).toBe('recipient');
136
+ expect(permit.issuer).toBe(bobAddress);
137
+ expect(permit.recipient).toBe(aliceAddress);
138
+ expect(permit.recipientSignature).toBeDefined();
139
+ expect(permit.recipientSignature).not.toBe('0x');
140
+ });
141
+ });
142
+
143
+ describe('Permit Retrieval', () => {
144
+ let createdPermit: any;
145
+ let permitHash: string;
146
+
147
+ beforeEach(async () => {
148
+ // Create a real permit for testing
149
+ createdPermit = await permits.createSelf(
150
+ { name: 'Test Permit', issuer: bobAddress },
151
+ publicClient,
152
+ bobWalletClient
153
+ );
154
+ permitHash = permits.getHash(createdPermit);
155
+ });
156
+
157
+ it('should get permit by hash', async () => {
158
+ const permit = await permits.getPermit(chainId, bobAddress, permitHash);
159
+ expect(permit?.name).toBe('Test Permit');
160
+ expect(permit?.type).toBe('self');
161
+ });
162
+
163
+ it('should get all permits', async () => {
164
+ const allPermits = await permits.getPermits(chainId, bobAddress);
165
+ expect(Object.keys(allPermits).length).toBeGreaterThan(0);
166
+ });
167
+
168
+ it('should get active permit', async () => {
169
+ const permit = await permits.getActivePermit(chainId, bobAddress);
170
+ expect(permit?.name).toBe('Test Permit');
171
+ });
172
+
173
+ it('should get active permit hash', async () => {
174
+ const hash = await permits.getActivePermitHash(chainId, bobAddress);
175
+ expect(typeof hash).toBe('string');
176
+ });
177
+ });
178
+
179
+ describe('localStorage Integration', () => {
180
+ it('should persist permits to localStorage', async () => {
181
+ const createdPermit = await permits.createSelf(
182
+ { name: 'Test Permit', issuer: bobAddress },
183
+ publicClient,
184
+ bobWalletClient
185
+ );
186
+
187
+ const storedData = localStorage.getItem('cofhesdk-permits');
188
+ expect(storedData).toBeDefined();
189
+
190
+ const parsedData = JSON.parse(storedData!);
191
+ expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
192
+ expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
193
+
194
+ // Verify the permit data structure
195
+ const permitKeys = Object.keys(parsedData.state.permits[chainId][bobAddress]);
196
+ expect(permitKeys.length).toBeGreaterThan(0);
197
+
198
+ const permitHash = permits.getHash(createdPermit);
199
+ const serializedPermit = permits.serialize(createdPermit);
200
+ expect(parsedData.state.permits[chainId][bobAddress][permitHash]).toEqual(serializedPermit);
201
+ });
202
+ });
203
+
204
+ describe('Real Network Integration', () => {
205
+ it('should create permit with real EIP712 domain from Arbitrum Sepolia', async () => {
206
+ const permit = await permits.createSelf(
207
+ { name: 'Real Network Permit', issuer: bobAddress },
208
+ publicClient,
209
+ bobWalletClient
210
+ );
211
+
212
+ expect(permit._signedDomain).toBeDefined();
213
+ expect(permit._signedDomain?.chainId).toBe(chainId);
214
+ expect(permit._signedDomain?.name).toBeDefined();
215
+ expect(permit._signedDomain?.version).toBeDefined();
216
+ expect(permit._signedDomain?.verifyingContract).toBeDefined();
217
+ });
218
+
219
+ it('should handle multiple permits on real network', async () => {
220
+ // Create multiple permits
221
+ await permits.createSelf({ name: 'Permit 1', issuer: bobAddress }, publicClient, bobWalletClient);
222
+ await permits.createSharing(
223
+ {
224
+ name: 'Permit 2',
225
+ issuer: bobAddress,
226
+ recipient: aliceAddress,
227
+ },
228
+ publicClient,
229
+ bobWalletClient
230
+ );
231
+
232
+ // Verify both permits exist
233
+ const allPermits = await permits.getPermits(chainId, bobAddress);
234
+ expect(Object.keys(allPermits).length).toBeGreaterThanOrEqual(2);
235
+
236
+ // Verify active permit is the last created one
237
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
238
+ expect(activePermit?.name).toBe('Permit 2');
239
+ });
240
+ });
241
+
242
+ describe('getOrCreateSelfPermit', () => {
243
+ it('should create a new self permit when none exists', async () => {
244
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
245
+ issuer: bobAddress,
246
+ name: 'New Self Permit',
247
+ });
248
+
249
+ expect(permit).toBeDefined();
250
+ expect(permit.name).toBe('New Self Permit');
251
+ expect(permit.type).toBe('self');
252
+ expect(permit.issuer).toBe(bobAddress);
253
+ expect(permit.issuerSignature).toBeDefined();
254
+
255
+ // Verify it was stored and set as active
256
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
257
+ expect(activePermit?.name).toBe('New Self Permit');
258
+ });
259
+
260
+ it('should return existing self permit when one exists', async () => {
261
+ // Create an initial self permit
262
+ const firstPermit = await permits.createSelf(
263
+ { name: 'First Self Permit', issuer: bobAddress },
264
+ publicClient,
265
+ bobWalletClient
266
+ );
267
+
268
+ // Call getOrCreateSelfPermit - should return existing
269
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
270
+ issuer: bobAddress,
271
+ name: 'Should Not Create This',
272
+ });
273
+
274
+ expect(permit.name).toBe('First Self Permit');
275
+ expect(permits.getHash(permit)).toBe(permits.getHash(firstPermit));
276
+
277
+ // Verify no new permit was created
278
+ const allPermits = await permits.getPermits(chainId, bobAddress);
279
+ expect(Object.keys(allPermits).length).toBe(1);
280
+ });
281
+
282
+ it('should create new self permit when active permit is sharing type', async () => {
283
+ // Create a sharing permit first
284
+ await permits.createSharing(
285
+ {
286
+ name: 'Sharing Permit',
287
+ issuer: bobAddress,
288
+ recipient: aliceAddress,
289
+ },
290
+ publicClient,
291
+ bobWalletClient
292
+ );
293
+
294
+ // Call getOrCreateSelfPermit - should create new since active is sharing type
295
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
296
+ issuer: bobAddress,
297
+ name: 'New Self Permit',
298
+ });
299
+
300
+ expect(permit.name).toBe('New Self Permit');
301
+ expect(permit.type).toBe('self');
302
+
303
+ // Verify two permits exist now
304
+ const allPermits = await permits.getPermits(chainId, bobAddress);
305
+ expect(Object.keys(allPermits).length).toBe(2);
306
+ });
307
+
308
+ it('should use default options when none provided', async () => {
309
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress);
310
+
311
+ expect(permit).toBeDefined();
312
+ expect(permit.type).toBe('self');
313
+ expect(permit.issuer).toBe(bobAddress);
314
+ expect(permit.name).toBe('Autogenerated Self Permit');
315
+ });
316
+
317
+ it('should use default chainId and account when not provided', async () => {
318
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, undefined, undefined, {
319
+ issuer: bobAddress,
320
+ name: 'Test Permit',
321
+ });
322
+
323
+ expect(permit).toBeDefined();
324
+ expect(permit.issuer).toBe(bobAddress);
325
+
326
+ // Verify it was stored with the chain's actual chainId
327
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
328
+ expect(activePermit?.name).toBe('Test Permit');
329
+ });
330
+ });
331
+
332
+ describe('getOrCreateSharingPermit', () => {
333
+ it('should create a new sharing permit when none exists', async () => {
334
+ const permit = await permits.getOrCreateSharingPermit(
335
+ publicClient,
336
+ bobWalletClient,
337
+ {
338
+ issuer: bobAddress,
339
+ recipient: aliceAddress,
340
+ name: 'New Sharing Permit',
341
+ },
342
+ chainId,
343
+ bobAddress
344
+ );
345
+
346
+ expect(permit).toBeDefined();
347
+ expect(permit.name).toBe('New Sharing Permit');
348
+ expect(permit.type).toBe('sharing');
349
+ expect(permit.issuer).toBe(bobAddress);
350
+ expect(permit.recipient).toBe(aliceAddress);
351
+ expect(permit.issuerSignature).toBeDefined();
352
+
353
+ // Verify it was stored and set as active
354
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
355
+ expect(activePermit?.name).toBe('New Sharing Permit');
356
+ });
357
+
358
+ it('should return existing sharing permit when one exists', async () => {
359
+ // Create an initial sharing permit
360
+ const firstPermit = await permits.createSharing(
361
+ {
362
+ name: 'First Sharing Permit',
363
+ issuer: bobAddress,
364
+ recipient: aliceAddress,
365
+ },
366
+ publicClient,
367
+ bobWalletClient
368
+ );
369
+
370
+ // Call getOrCreateSharingPermit - should return existing
371
+ const permit = await permits.getOrCreateSharingPermit(
372
+ publicClient,
373
+ bobWalletClient,
374
+ {
375
+ issuer: bobAddress,
376
+ recipient: aliceAddress,
377
+ name: 'Should Not Create This',
378
+ },
379
+ chainId,
380
+ bobAddress
381
+ );
382
+
383
+ expect(permit.name).toBe('First Sharing Permit');
384
+ expect(permits.getHash(permit)).toBe(permits.getHash(firstPermit));
385
+
386
+ // Verify no new permit was created
387
+ const allPermits = await permits.getPermits(chainId, bobAddress);
388
+ expect(Object.keys(allPermits).length).toBe(1);
389
+ });
390
+
391
+ it('should create new sharing permit when active permit is self type', async () => {
392
+ // Create a self permit first
393
+ await permits.createSelf({ name: 'Self Permit', issuer: bobAddress }, publicClient, bobWalletClient);
394
+
395
+ // Call getOrCreateSharingPermit - should create new since active is self type
396
+ const permit = await permits.getOrCreateSharingPermit(
397
+ publicClient,
398
+ bobWalletClient,
399
+ {
400
+ issuer: bobAddress,
401
+ recipient: aliceAddress,
402
+ name: 'New Sharing Permit',
403
+ },
404
+ chainId,
405
+ bobAddress
406
+ );
407
+
408
+ expect(permit.name).toBe('New Sharing Permit');
409
+ expect(permit.type).toBe('sharing');
410
+
411
+ // Verify two permits exist now
412
+ const allPermits = await permits.getPermits(chainId, bobAddress);
413
+ expect(Object.keys(allPermits).length).toBe(2);
414
+ });
415
+
416
+ it('should use default chainId and account when not provided', async () => {
417
+ const permit = await permits.getOrCreateSharingPermit(
418
+ publicClient,
419
+ bobWalletClient,
420
+ {
421
+ issuer: bobAddress,
422
+ recipient: aliceAddress,
423
+ name: 'Test Sharing Permit',
424
+ },
425
+ undefined,
426
+ undefined
427
+ );
428
+
429
+ expect(permit).toBeDefined();
430
+ expect(permit.issuer).toBe(bobAddress);
431
+ expect(permit.recipient).toBe(aliceAddress);
432
+
433
+ // Verify it was stored with the chain's actual chainId
434
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
435
+ expect(activePermit?.name).toBe('Test Sharing Permit');
436
+ });
437
+ });
438
+
439
+ describe('getOrCreate - Multiple Types Scenarios', () => {
440
+ it('should handle switching between self and sharing permits', async () => {
441
+ // Create self permit
442
+ const selfPermit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
443
+ issuer: bobAddress,
444
+ name: 'Self Permit',
445
+ });
446
+ expect(selfPermit.type).toBe('self');
447
+
448
+ // Create sharing permit (should create new one)
449
+ const sharingPermit = await permits.getOrCreateSharingPermit(
450
+ publicClient,
451
+ bobWalletClient,
452
+ {
453
+ issuer: bobAddress,
454
+ recipient: aliceAddress,
455
+ name: 'Sharing Permit',
456
+ },
457
+ chainId,
458
+ bobAddress
459
+ );
460
+ expect(sharingPermit.type).toBe('sharing');
461
+
462
+ // Both should exist
463
+ const allPermits = await permits.getPermits(chainId, bobAddress);
464
+ expect(Object.keys(allPermits).length).toBe(2);
465
+
466
+ // Active permit should be the sharing one
467
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
468
+ expect(activePermit?.type).toBe('sharing');
469
+ expect(activePermit?.name).toBe('Sharing Permit');
470
+ });
471
+
472
+ it('should correctly handle sequential getOrCreate calls', async () => {
473
+ // First call - creates new
474
+ const permit1 = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
475
+ issuer: bobAddress,
476
+ name: 'Permit 1',
477
+ });
478
+
479
+ // Second call - returns existing
480
+ const permit2 = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
481
+ issuer: bobAddress,
482
+ name: 'Permit 2',
483
+ });
484
+
485
+ // Should be the same permit
486
+ expect(permits.getHash(permit1)).toBe(permits.getHash(permit2));
487
+ expect(permit2.name).toBe('Permit 1'); // Original name
488
+
489
+ // Only one permit should exist
490
+ const allPermits = await permits.getPermits(chainId, bobAddress);
491
+ expect(Object.keys(allPermits).length).toBe(1);
492
+ });
493
+ });
494
+ });
@@ -0,0 +1,200 @@
1
+ import {
2
+ type ImportSharedPermitOptions,
3
+ PermitUtils,
4
+ type CreateSelfPermitOptions,
5
+ type CreateSharingPermitOptions,
6
+ type Permit,
7
+ permitStore,
8
+ type SerializedPermit,
9
+ type SelfPermit,
10
+ type RecipientPermit,
11
+ type SharingPermit,
12
+ } from '@/permits';
13
+
14
+ import { type PublicClient, type WalletClient } from 'viem';
15
+
16
+ // HELPERS
17
+
18
+ // Helper function to store permit as active permit
19
+ const storeActivePermit = async (permit: Permit, publicClient: any, walletClient: any) => {
20
+ const chainId = await publicClient.getChainId();
21
+ const account = walletClient.account!.address;
22
+
23
+ permitStore.setPermit(chainId, account, permit);
24
+ permitStore.setActivePermitHash(chainId, account, PermitUtils.getHash(permit));
25
+ };
26
+
27
+ // Generic function to handle permit creation with error handling
28
+ const createPermitWithSign = async <T, TPermit extends Permit>(
29
+ options: T,
30
+ publicClient: PublicClient,
31
+ walletClient: WalletClient,
32
+ permitMethod: (options: T, publicClient: PublicClient, walletClient: WalletClient) => Promise<TPermit>
33
+ ): Promise<TPermit> => {
34
+ const permit = await permitMethod(options, publicClient, walletClient);
35
+ await storeActivePermit(permit, publicClient, walletClient);
36
+ return permit;
37
+ };
38
+
39
+ // CREATE
40
+
41
+ /**
42
+ * Create a permit usable by the connected user
43
+ * Stores the permit and selects it as the active permit
44
+ * @param options - The options for creating a self permit
45
+ * @returns The created permit or error
46
+ */
47
+ const createSelf = async (
48
+ options: CreateSelfPermitOptions,
49
+ publicClient: PublicClient,
50
+ walletClient: WalletClient
51
+ ): Promise<SelfPermit> => {
52
+ return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSelfAndSign);
53
+ };
54
+
55
+ const createSharing = async (
56
+ options: CreateSharingPermitOptions,
57
+ publicClient: PublicClient,
58
+ walletClient: WalletClient
59
+ ): Promise<SharingPermit> => {
60
+ return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSharingAndSign);
61
+ };
62
+
63
+ const importShared = async (
64
+ options: ImportSharedPermitOptions | string,
65
+ publicClient: PublicClient,
66
+ walletClient: WalletClient
67
+ ): Promise<RecipientPermit> => {
68
+ return createPermitWithSign(options, publicClient, walletClient, PermitUtils.importSharedAndSign);
69
+ };
70
+
71
+ // PERMIT UTILS
72
+
73
+ const getHash = (permit: Permit) => {
74
+ return PermitUtils.getHash(permit);
75
+ };
76
+
77
+ const serialize = (permit: Permit) => {
78
+ return PermitUtils.serialize(permit);
79
+ };
80
+
81
+ const deserialize = (serialized: SerializedPermit) => {
82
+ return PermitUtils.deserialize(serialized);
83
+ };
84
+
85
+ // GET
86
+
87
+ const getPermit = async (chainId: number, account: string, hash: string): Promise<Permit | undefined> => {
88
+ return permitStore.getPermit(chainId, account, hash);
89
+ };
90
+
91
+ const getPermits = async (chainId: number, account: string): Promise<Record<string, Permit>> => {
92
+ return permitStore.getPermits(chainId, account);
93
+ };
94
+
95
+ const getActivePermit = async (chainId: number, account: string): Promise<Permit | undefined> => {
96
+ return permitStore.getActivePermit(chainId, account);
97
+ };
98
+
99
+ const getActivePermitHash = (chainId: number, account: string): string | undefined => {
100
+ return permitStore.getActivePermitHash(chainId, account);
101
+ };
102
+
103
+ const selectActivePermit = (chainId: number, account: string, hash: string): void => {
104
+ permitStore.setActivePermitHash(chainId, account, hash);
105
+ };
106
+
107
+ // GET OR CREATE
108
+
109
+ /**
110
+ * Get the active self permit or create a new one if it doesn't exist
111
+ * @param publicClient - The public client
112
+ * @param walletClient - The wallet client
113
+ * @param chainId - Optional chain ID (will use publicClient if not provided)
114
+ * @param account - Optional account (will use walletClient if not provided)
115
+ * @param options - The options for creating a self permit
116
+ * @returns The existing or newly created permit
117
+ */
118
+ const getOrCreateSelfPermit = async (
119
+ publicClient: PublicClient,
120
+ walletClient: WalletClient,
121
+ chainId?: number,
122
+ account?: string,
123
+ options?: CreateSelfPermitOptions
124
+ ): Promise<Permit> => {
125
+ const _chainId = chainId ?? (await publicClient.getChainId());
126
+ const _account = account ?? walletClient.account!.address;
127
+
128
+ // Try to get active permit first
129
+ const activePermit = await getActivePermit(_chainId, _account);
130
+
131
+ if (activePermit && activePermit.type === 'self') {
132
+ return activePermit;
133
+ }
134
+
135
+ // No active permit or wrong type, create new one
136
+ return createSelf(options ?? { issuer: _account, name: 'Autogenerated Self Permit' }, publicClient, walletClient);
137
+ };
138
+
139
+ /**
140
+ * Get the active sharing permit or create a new one if it doesn't exist
141
+ * @param publicClient - The public client
142
+ * @param walletClient - The wallet client
143
+ * @param options - The options for creating a sharing permit (required)
144
+ * @param chainId - Optional chain ID (will use publicClient if not provided)
145
+ * @param account - Optional account (will use walletClient if not provided)
146
+ * @returns The existing or newly created permit
147
+ */
148
+ const getOrCreateSharingPermit = async (
149
+ publicClient: PublicClient,
150
+ walletClient: WalletClient,
151
+ options: CreateSharingPermitOptions,
152
+ chainId?: number,
153
+ account?: string
154
+ ): Promise<Permit> => {
155
+ const _chainId = chainId ?? (await publicClient.getChainId());
156
+ const _account = account ?? walletClient.account!.address;
157
+
158
+ // Try to get active permit first
159
+ const activePermit = await getActivePermit(_chainId, _account);
160
+
161
+ if (activePermit && activePermit.type === 'sharing') {
162
+ return activePermit;
163
+ }
164
+
165
+ return createSharing(options, publicClient, walletClient);
166
+ };
167
+
168
+ // REMOVE
169
+
170
+ const removePermit = async (chainId: number, account: string, hash: string, force?: boolean): Promise<void> =>
171
+ permitStore.removePermit(chainId, account, hash, force);
172
+
173
+ const removeActivePermit = async (chainId: number, account: string): Promise<void> =>
174
+ permitStore.removeActivePermitHash(chainId, account);
175
+
176
+ // EXPORT
177
+
178
+ export const permits = {
179
+ getSnapshot: permitStore.store.getState,
180
+ subscribe: permitStore.store.subscribe,
181
+
182
+ createSelf,
183
+ createSharing,
184
+ importShared,
185
+
186
+ getOrCreateSelfPermit,
187
+ getOrCreateSharingPermit,
188
+
189
+ getHash,
190
+ serialize,
191
+ deserialize,
192
+
193
+ getPermit,
194
+ getPermits,
195
+ getActivePermit,
196
+ getActivePermitHash,
197
+ removePermit,
198
+ selectActivePermit,
199
+ removeActivePermit,
200
+ };