@cofhe/sdk 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/adapters/ethers6.ts +28 -28
  3. package/adapters/hardhat.ts +0 -1
  4. package/adapters/index.test.ts +14 -19
  5. package/adapters/smartWallet.ts +81 -73
  6. package/adapters/test-utils.ts +45 -45
  7. package/adapters/types.ts +3 -3
  8. package/chains/chains/localcofhe.ts +14 -0
  9. package/chains/chains.test.ts +2 -1
  10. package/chains/defineChain.ts +2 -2
  11. package/chains/index.ts +3 -1
  12. package/chains/types.ts +3 -3
  13. package/core/baseBuilder.ts +30 -49
  14. package/core/client.test.ts +200 -72
  15. package/core/client.ts +152 -148
  16. package/core/clientTypes.ts +114 -0
  17. package/core/config.test.ts +30 -11
  18. package/core/config.ts +26 -13
  19. package/core/consts.ts +18 -0
  20. package/core/decrypt/cofheMocksSealOutput.ts +2 -4
  21. package/core/decrypt/decryptHandleBuilder.ts +51 -45
  22. package/core/decrypt/{tnSealOutput.ts → tnSealOutputV1.ts} +1 -1
  23. package/core/decrypt/tnSealOutputV2.ts +298 -0
  24. package/core/encrypt/cofheMocksZkVerifySign.ts +15 -16
  25. package/core/encrypt/encryptInputsBuilder.test.ts +132 -116
  26. package/core/encrypt/encryptInputsBuilder.ts +159 -111
  27. package/core/encrypt/encryptUtils.ts +6 -3
  28. package/core/encrypt/zkPackProveVerify.ts +70 -8
  29. package/core/error.ts +0 -2
  30. package/core/fetchKeys.test.ts +1 -18
  31. package/core/fetchKeys.ts +0 -26
  32. package/core/index.ts +37 -17
  33. package/core/keyStore.ts +65 -38
  34. package/core/permits.test.ts +255 -4
  35. package/core/permits.ts +83 -18
  36. package/core/types.ts +198 -152
  37. package/core/utils.ts +43 -1
  38. package/dist/adapters.d.cts +38 -20
  39. package/dist/adapters.d.ts +38 -20
  40. package/dist/chains.cjs +18 -8
  41. package/dist/chains.d.cts +31 -9
  42. package/dist/chains.d.ts +31 -9
  43. package/dist/chains.js +1 -1
  44. package/dist/{chunk-KFGPTJ6X.js → chunk-I5WFEYXX.js} +1768 -1526
  45. package/dist/{chunk-LU7BMUUT.js → chunk-R3B5TMVX.js} +330 -197
  46. package/dist/{chunk-GZCQQYVI.js → chunk-TBLR7NNE.js} +18 -9
  47. package/dist/{types-PhwGgQvs.d.ts → clientTypes-RqkgkV2i.d.ts} +331 -429
  48. package/dist/{types-bB7wLj0q.d.cts → clientTypes-e4filDzK.d.cts} +331 -429
  49. package/dist/core.cjs +3000 -2625
  50. package/dist/core.d.cts +113 -7
  51. package/dist/core.d.ts +113 -7
  52. package/dist/core.js +3 -3
  53. package/dist/node.cjs +2851 -2526
  54. package/dist/node.d.cts +4 -4
  55. package/dist/node.d.ts +4 -4
  56. package/dist/node.js +4 -3
  57. package/dist/{permit-S9CnI6MF.d.cts → permit-MZ502UBl.d.cts} +54 -41
  58. package/dist/{permit-S9CnI6MF.d.ts → permit-MZ502UBl.d.ts} +54 -41
  59. package/dist/permits.cjs +328 -195
  60. package/dist/permits.d.cts +113 -825
  61. package/dist/permits.d.ts +113 -825
  62. package/dist/permits.js +1 -1
  63. package/dist/types-YiAC4gig.d.cts +33 -0
  64. package/dist/types-YiAC4gig.d.ts +33 -0
  65. package/dist/web.cjs +3067 -2527
  66. package/dist/web.d.cts +22 -6
  67. package/dist/web.d.ts +22 -6
  68. package/dist/web.js +185 -9
  69. package/dist/zkProve.worker.cjs +93 -0
  70. package/dist/zkProve.worker.d.cts +2 -0
  71. package/dist/zkProve.worker.d.ts +2 -0
  72. package/dist/zkProve.worker.js +91 -0
  73. package/node/client.test.ts +20 -25
  74. package/node/encryptInputs.test.ts +18 -38
  75. package/node/index.ts +1 -0
  76. package/package.json +15 -15
  77. package/permits/index.ts +1 -0
  78. package/permits/localstorage.test.ts +9 -14
  79. package/permits/onchain-utils.ts +221 -0
  80. package/permits/permit.test.ts +76 -27
  81. package/permits/permit.ts +58 -95
  82. package/permits/sealing.test.ts +3 -3
  83. package/permits/sealing.ts +2 -2
  84. package/permits/store.test.ts +10 -50
  85. package/permits/store.ts +9 -21
  86. package/permits/test-utils.ts +11 -3
  87. package/permits/types.ts +39 -9
  88. package/permits/utils.ts +0 -5
  89. package/permits/validation.test.ts +29 -32
  90. package/permits/validation.ts +114 -176
  91. package/web/client.web.test.ts +20 -25
  92. package/web/config.web.test.ts +0 -2
  93. package/web/encryptInputs.web.test.ts +31 -54
  94. package/web/index.ts +65 -1
  95. package/web/storage.ts +19 -5
  96. package/web/worker.builder.web.test.ts +148 -0
  97. package/web/worker.config.web.test.ts +329 -0
  98. package/web/worker.output.web.test.ts +84 -0
  99. package/web/workerManager.test.ts +80 -0
  100. package/web/workerManager.ts +214 -0
  101. package/web/workerManager.web.test.ts +114 -0
  102. package/web/zkProve.worker.ts +133 -0
  103. package/core/result.test.ts +0 -180
  104. package/core/result.ts +0 -67
  105. package/core/test-utils.ts +0 -45
  106. package/dist/types-KImPrEIe.d.cts +0 -48
  107. package/dist/types-KImPrEIe.d.ts +0 -48
package/core/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Client (base implementations)
2
- export { createCofhesdkClientBase } from './client.js';
2
+ export { createCofhesdkClientBase, InitialConnectStore as CONNECT_STORE_DEFAULTS } from './client.js';
3
3
 
4
4
  // Configuration (base implementations)
5
5
  export { createCofhesdkConfigBase, getCofhesdkConfigItem } from './config.js';
@@ -12,6 +12,9 @@ export type {
12
12
  CofhesdkClientParams,
13
13
  CofhesdkClientConnectionState,
14
14
  CofhesdkClientPermits,
15
+ } from './clientTypes.js';
16
+
17
+ export type {
15
18
  IStorage,
16
19
  // Primitive types
17
20
  Primitive,
@@ -34,35 +37,33 @@ export type {
34
37
  EncryptedUint32Input,
35
38
  EncryptedUint64Input,
36
39
  EncryptedUint128Input,
37
- EncryptedUint256Input,
38
40
  EncryptedAddressInput,
39
41
  EncryptedItemInputs,
40
42
  EncryptableToEncryptedItemInputMap,
43
+ FheTypeValue,
41
44
  // Decryption types
42
45
  UnsealedItem,
43
46
  // Util types
44
47
  EncryptStepCallbackFunction as EncryptSetStateFn,
48
+ EncryptStepCallbackContext,
49
+ } from './types.js';
50
+ export {
51
+ FheTypes,
52
+ FheUintUTypes,
53
+ FheAllUTypes,
54
+ Encryptable,
55
+ isEncryptableItem,
56
+ EncryptStep,
57
+ isLastEncryptionStep,
58
+ assertCorrectEncryptedItemInput,
45
59
  } from './types.js';
46
- export { FheTypes, FheUintUTypes, FheAllUTypes, Encryptable, isEncryptableItem, EncryptStep } from './types.js';
47
60
 
48
61
  // Error handling
49
62
  export { CofhesdkError, CofhesdkErrorCode, isCofhesdkError } from './error.js';
50
63
  export type { CofhesdkErrorParams } from './error.js';
51
64
 
52
- // Result types
53
- export {
54
- ResultErr,
55
- ResultOk,
56
- ResultErrOrInternal,
57
- ResultHttpError,
58
- ResultValidationError,
59
- resultWrapper,
60
- resultWrapperSync,
61
- } from './result.js';
62
- export type { Result } from './result.js';
63
-
64
65
  // Key fetching
65
- export { fetchKeys, fetchMultichainKeys } from './fetchKeys.js';
66
+ export { fetchKeys } from './fetchKeys.js';
66
67
  export type { FheKeyDeserializer } from './fetchKeys.js';
67
68
 
68
69
  // Key storage
@@ -74,4 +75,23 @@ export { EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
74
75
  export { DecryptHandlesBuilder } from './decrypt/decryptHandleBuilder.js';
75
76
 
76
77
  // ZK utilities
77
- export type { ZkBuilderAndCrsGenerator } from './encrypt/zkPackProveVerify.js';
78
+ export type {
79
+ ZkBuilderAndCrsGenerator,
80
+ ZkProveWorkerFunction,
81
+ ZkProveWorkerRequest,
82
+ ZkProveWorkerResponse,
83
+ } from './encrypt/zkPackProveVerify.js';
84
+ export { zkProveWithWorker } from './encrypt/zkPackProveVerify.js';
85
+
86
+ // Contract addresses
87
+ export {
88
+ TASK_MANAGER_ADDRESS,
89
+ MOCKS_ZK_VERIFIER_ADDRESS,
90
+ MOCKS_ZK_VERIFIER_SIGNER_ADDRESS,
91
+ MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
92
+ MOCKS_QUERY_DECRYPTER_ADDRESS,
93
+ TEST_BED_ADDRESS,
94
+ } from './consts.js';
95
+
96
+ // Utils
97
+ export { fheTypeToString } from './utils.js';
package/core/keyStore.ts CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-unused-vars */
2
1
  import { createStore, type StoreApi } from 'zustand/vanilla';
3
2
  import { persist, createJSONStorage } from 'zustand/middleware';
4
3
  import { produce } from 'immer';
@@ -24,6 +23,30 @@ export type KeysStorage = {
24
23
  rehydrateKeysStore: () => Promise<void>;
25
24
  };
26
25
 
26
+ function isValidPersistedState(state: unknown): state is KeysStore {
27
+ if (state && typeof state === 'object') {
28
+ if ('fhe' in state && 'crs' in state) {
29
+ return true;
30
+ } else {
31
+ throw new Error(
32
+ "Invalid persisted state structure for KeysStore. Is object but doesn't contain required fields 'fhe' and 'crs'."
33
+ );
34
+ }
35
+ }
36
+
37
+ return false;
38
+ }
39
+
40
+ const DEFAULT_KEYS_STORE: KeysStore = {
41
+ fhe: {},
42
+ crs: {},
43
+ };
44
+
45
+ type StoreWithPersist = ReturnType<typeof createStoreWithPersit>;
46
+
47
+ function isStoreWithPersist(store: StoreApi<KeysStore> | StoreWithPersist): store is StoreWithPersist {
48
+ return 'persist' in store;
49
+ }
27
50
  /**
28
51
  * Creates a keys storage instance using the provided storage implementation
29
52
  * @param storage - The storage implementation to use (IStorage interface), or null for non-persisted store
@@ -32,39 +55,7 @@ export type KeysStorage = {
32
55
  export function createKeysStore(storage: IStorage | null): KeysStorage {
33
56
  // Conditionally create store with or without persist wrapper
34
57
  const keysStore = storage
35
- ? createStore<KeysStore>()(
36
- persist(
37
- () => ({
38
- fhe: {},
39
- crs: {},
40
- }),
41
- {
42
- name: 'cofhesdk-keys',
43
- storage: createJSONStorage(() => storage),
44
- merge: (persistedState, currentState) => {
45
- const persisted = persistedState as KeysStore;
46
- const current = currentState as KeysStore;
47
-
48
- // Deep merge for fhe
49
- const mergedFhe: KeysStore['fhe'] = { ...persisted.fhe };
50
- const allChainIds = new Set([...Object.keys(current.fhe), ...Object.keys(persisted.fhe)]);
51
- for (const chainId of allChainIds) {
52
- const persistedZones = persisted.fhe[chainId] || {};
53
- const currentZones = current.fhe[chainId] || {};
54
- mergedFhe[chainId] = { ...persistedZones, ...currentZones };
55
- }
56
-
57
- // Deep merge for crs
58
- const mergedCrs: KeysStore['crs'] = { ...persisted.crs, ...current.crs };
59
-
60
- return {
61
- fhe: mergedFhe,
62
- crs: mergedCrs,
63
- };
64
- },
65
- }
66
- )
67
- )
58
+ ? createStoreWithPersit(storage)
68
59
  : createStore<KeysStore>()(() => ({
69
60
  fhe: {},
70
61
  crs: {},
@@ -109,10 +100,9 @@ export function createKeysStore(storage: IStorage | null): KeysStorage {
109
100
  };
110
101
 
111
102
  const rehydrateKeysStore = async () => {
112
- if ('persist' in keysStore) {
113
- if ((keysStore.persist as any).hasHydrated()) return;
114
- await (keysStore.persist as any).rehydrate();
115
- }
103
+ if (!isStoreWithPersist(keysStore)) return;
104
+ if (keysStore.persist.hasHydrated()) return;
105
+ await keysStore.persist.rehydrate();
116
106
  };
117
107
 
118
108
  return {
@@ -125,3 +115,40 @@ export function createKeysStore(storage: IStorage | null): KeysStorage {
125
115
  rehydrateKeysStore,
126
116
  };
127
117
  }
118
+
119
+ function createStoreWithPersit(storage: IStorage) {
120
+ const result = createStore<KeysStore>()(
121
+ persist(() => DEFAULT_KEYS_STORE, {
122
+ // because earleir tests were written with on-init hydration skipped (due to the error suppression in zustand), returning this flag to fix test (i.e. KeyStore > Storage Utilities > should rehydrate keys store)
123
+ skipHydration: true,
124
+ // if onRehydrateStorage is not passed here, the errors thrown by storage layer are swallowed by zustand here: https://github.com/pmndrs/zustand/blob/39a391b6c1ff9aa89b81694d9bdb21da37dd4ac6/src/middleware/persist.ts#L321
125
+ onRehydrateStorage: () => (_state?, _error?) => {
126
+ if (_error) throw new Error(`onRehydrateStorage: Error rehydrating keys store: ${_error}`);
127
+ },
128
+ name: 'cofhesdk-keys',
129
+ storage: createJSONStorage(() => storage),
130
+ merge: (persistedState, currentState) => {
131
+ const persisted = isValidPersistedState(persistedState) ? persistedState : DEFAULT_KEYS_STORE;
132
+ const current = currentState as KeysStore;
133
+
134
+ // Deep merge for fhe
135
+ const mergedFhe: KeysStore['fhe'] = { ...persisted.fhe };
136
+ const allChainIds = new Set([...Object.keys(current.fhe), ...Object.keys(persisted.fhe)]);
137
+ for (const chainId of allChainIds) {
138
+ const persistedZones = persisted.fhe[chainId] || {};
139
+ const currentZones = current.fhe[chainId] || {};
140
+ mergedFhe[chainId] = { ...persistedZones, ...currentZones };
141
+ }
142
+
143
+ // Deep merge for crs
144
+ const mergedCrs: KeysStore['crs'] = { ...persisted.crs, ...current.crs };
145
+
146
+ return {
147
+ fhe: mergedFhe,
148
+ crs: mergedCrs,
149
+ };
150
+ },
151
+ })
152
+ );
153
+ return result;
154
+ }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * @vitest-environment happy-dom
3
3
  */
4
- /* eslint-disable no-unused-vars */
5
4
  import { permitStore } from '@/permits';
6
5
 
7
6
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
@@ -152,7 +151,7 @@ describe('Core Permits Tests', () => {
152
151
  publicClient,
153
152
  bobWalletClient
154
153
  );
155
- permitHash = permits.getHash(createdPermit);
154
+ permitHash = createdPermit.hash;
156
155
  });
157
156
 
158
157
  it('should get permit by hash', async () => {
@@ -196,9 +195,8 @@ describe('Core Permits Tests', () => {
196
195
  const permitKeys = Object.keys(parsedData.state.permits[chainId][bobAddress]);
197
196
  expect(permitKeys.length).toBeGreaterThan(0);
198
197
 
199
- const permitHash = permits.getHash(createdPermit);
200
198
  const serializedPermit = permits.serialize(createdPermit);
201
- expect(parsedData.state.permits[chainId][bobAddress][permitHash]).toEqual(serializedPermit);
199
+ expect(parsedData.state.permits[chainId][bobAddress][createdPermit.hash]).toEqual(serializedPermit);
202
200
  });
203
201
  });
204
202
 
@@ -239,4 +237,257 @@ describe('Core Permits Tests', () => {
239
237
  expect(activePermit?.name).toBe('Permit 2');
240
238
  });
241
239
  });
240
+
241
+ describe('getOrCreateSelfPermit', () => {
242
+ it('should create a new self permit when none exists', async () => {
243
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
244
+ issuer: bobAddress,
245
+ name: 'New Self Permit',
246
+ });
247
+
248
+ expect(permit).toBeDefined();
249
+ expect(permit.name).toBe('New Self Permit');
250
+ expect(permit.type).toBe('self');
251
+ expect(permit.issuer).toBe(bobAddress);
252
+ expect(permit.issuerSignature).toBeDefined();
253
+
254
+ // Verify it was stored and set as active
255
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
256
+ expect(activePermit?.name).toBe('New Self Permit');
257
+ });
258
+
259
+ it('should return existing self permit when one exists', async () => {
260
+ // Create an initial self permit
261
+ const firstPermit = await permits.createSelf(
262
+ { name: 'First Self Permit', issuer: bobAddress },
263
+ publicClient,
264
+ bobWalletClient
265
+ );
266
+
267
+ // Call getOrCreateSelfPermit - should return existing
268
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
269
+ issuer: bobAddress,
270
+ name: 'Should Not Create This',
271
+ });
272
+
273
+ expect(permit.name).toBe('First Self Permit');
274
+ expect(permit.hash).toBe(firstPermit.hash);
275
+
276
+ // Verify no new permit was created
277
+ const allPermits = await permits.getPermits(chainId, bobAddress);
278
+ expect(Object.keys(allPermits).length).toBe(1);
279
+ });
280
+
281
+ it('should create new self permit when active permit is sharing type', async () => {
282
+ // Create a sharing permit first
283
+ await permits.createSharing(
284
+ {
285
+ name: 'Sharing Permit',
286
+ issuer: bobAddress,
287
+ recipient: aliceAddress,
288
+ },
289
+ publicClient,
290
+ bobWalletClient
291
+ );
292
+
293
+ // Call getOrCreateSelfPermit - should create new since active is sharing type
294
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
295
+ issuer: bobAddress,
296
+ name: 'New Self Permit',
297
+ });
298
+
299
+ expect(permit.name).toBe('New Self Permit');
300
+ expect(permit.type).toBe('self');
301
+
302
+ // Verify two permits exist now
303
+ const allPermits = await permits.getPermits(chainId, bobAddress);
304
+ expect(Object.keys(allPermits).length).toBe(2);
305
+ });
306
+
307
+ it('should use default options when none provided', async () => {
308
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress);
309
+
310
+ expect(permit).toBeDefined();
311
+ expect(permit.type).toBe('self');
312
+ expect(permit.issuer).toBe(bobAddress);
313
+ expect(permit.name).toBe('Autogenerated Self Permit');
314
+ });
315
+
316
+ it('should use default chainId and account when not provided', async () => {
317
+ const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, undefined, undefined, {
318
+ issuer: bobAddress,
319
+ name: 'Test Permit',
320
+ });
321
+
322
+ expect(permit).toBeDefined();
323
+ expect(permit.issuer).toBe(bobAddress);
324
+
325
+ // Verify it was stored with the chain's actual chainId
326
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
327
+ expect(activePermit?.name).toBe('Test Permit');
328
+ });
329
+ });
330
+
331
+ describe('getOrCreateSharingPermit', () => {
332
+ it('should create a new sharing permit when none exists', async () => {
333
+ const permit = await permits.getOrCreateSharingPermit(
334
+ publicClient,
335
+ bobWalletClient,
336
+ {
337
+ issuer: bobAddress,
338
+ recipient: aliceAddress,
339
+ name: 'New Sharing Permit',
340
+ },
341
+ chainId,
342
+ bobAddress
343
+ );
344
+
345
+ expect(permit).toBeDefined();
346
+ expect(permit.name).toBe('New Sharing Permit');
347
+ expect(permit.type).toBe('sharing');
348
+ expect(permit.issuer).toBe(bobAddress);
349
+ expect(permit.recipient).toBe(aliceAddress);
350
+ expect(permit.issuerSignature).toBeDefined();
351
+
352
+ // Verify it was stored and set as active
353
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
354
+ expect(activePermit?.name).toBe('New Sharing Permit');
355
+ });
356
+
357
+ it('should return existing sharing permit when one exists', async () => {
358
+ // Create an initial sharing permit
359
+ const firstPermit = await permits.createSharing(
360
+ {
361
+ name: 'First Sharing Permit',
362
+ issuer: bobAddress,
363
+ recipient: aliceAddress,
364
+ },
365
+ publicClient,
366
+ bobWalletClient
367
+ );
368
+
369
+ // Call getOrCreateSharingPermit - should return existing
370
+ const permit = await permits.getOrCreateSharingPermit(
371
+ publicClient,
372
+ bobWalletClient,
373
+ {
374
+ issuer: bobAddress,
375
+ recipient: aliceAddress,
376
+ name: 'Should Not Create This',
377
+ },
378
+ chainId,
379
+ bobAddress
380
+ );
381
+
382
+ expect(permit.name).toBe('First Sharing Permit');
383
+ expect(permit.hash).toBe(firstPermit.hash);
384
+
385
+ // Verify no new permit was created
386
+ const allPermits = await permits.getPermits(chainId, bobAddress);
387
+ expect(Object.keys(allPermits).length).toBe(1);
388
+ });
389
+
390
+ it('should create new sharing permit when active permit is self type', async () => {
391
+ // Create a self permit first
392
+ await permits.createSelf({ name: 'Self Permit', issuer: bobAddress }, publicClient, bobWalletClient);
393
+
394
+ // Call getOrCreateSharingPermit - should create new since active is self type
395
+ const permit = await permits.getOrCreateSharingPermit(
396
+ publicClient,
397
+ bobWalletClient,
398
+ {
399
+ issuer: bobAddress,
400
+ recipient: aliceAddress,
401
+ name: 'New Sharing Permit',
402
+ },
403
+ chainId,
404
+ bobAddress
405
+ );
406
+
407
+ expect(permit.name).toBe('New Sharing Permit');
408
+ expect(permit.type).toBe('sharing');
409
+
410
+ // Verify two permits exist now
411
+ const allPermits = await permits.getPermits(chainId, bobAddress);
412
+ expect(Object.keys(allPermits).length).toBe(2);
413
+ });
414
+
415
+ it('should use default chainId and account when not provided', async () => {
416
+ const permit = await permits.getOrCreateSharingPermit(
417
+ publicClient,
418
+ bobWalletClient,
419
+ {
420
+ issuer: bobAddress,
421
+ recipient: aliceAddress,
422
+ name: 'Test Sharing Permit',
423
+ },
424
+ undefined,
425
+ undefined
426
+ );
427
+
428
+ expect(permit).toBeDefined();
429
+ expect(permit.issuer).toBe(bobAddress);
430
+ expect(permit.recipient).toBe(aliceAddress);
431
+
432
+ // Verify it was stored with the chain's actual chainId
433
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
434
+ expect(activePermit?.name).toBe('Test Sharing Permit');
435
+ });
436
+ });
437
+
438
+ describe('getOrCreate - Multiple Types Scenarios', () => {
439
+ it('should handle switching between self and sharing permits', async () => {
440
+ // Create self permit
441
+ const selfPermit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
442
+ issuer: bobAddress,
443
+ name: 'Self Permit',
444
+ });
445
+ expect(selfPermit.type).toBe('self');
446
+
447
+ // Create sharing permit (should create new one)
448
+ const sharingPermit = await permits.getOrCreateSharingPermit(
449
+ publicClient,
450
+ bobWalletClient,
451
+ {
452
+ issuer: bobAddress,
453
+ recipient: aliceAddress,
454
+ name: 'Sharing Permit',
455
+ },
456
+ chainId,
457
+ bobAddress
458
+ );
459
+ expect(sharingPermit.type).toBe('sharing');
460
+
461
+ // Both should exist
462
+ const allPermits = await permits.getPermits(chainId, bobAddress);
463
+ expect(Object.keys(allPermits).length).toBe(2);
464
+
465
+ // Active permit should be the sharing one
466
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
467
+ expect(activePermit?.type).toBe('sharing');
468
+ expect(activePermit?.name).toBe('Sharing Permit');
469
+ });
470
+
471
+ it('should correctly handle sequential getOrCreate calls', async () => {
472
+ // First call - creates new
473
+ const permit1 = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
474
+ issuer: bobAddress,
475
+ name: 'Permit 1',
476
+ });
477
+
478
+ // Second call - returns existing
479
+ const permit2 = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
480
+ issuer: bobAddress,
481
+ name: 'Permit 2',
482
+ });
483
+
484
+ // Should be the same permit
485
+ expect(permit1.hash).toBe(permit2.hash);
486
+ expect(permit2.name).toBe('Permit 1'); // Original name
487
+
488
+ // Only one permit should exist
489
+ const allPermits = await permits.getPermits(chainId, bobAddress);
490
+ expect(Object.keys(allPermits).length).toBe(1);
491
+ });
492
+ });
242
493
  });
package/core/permits.ts CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-unused-vars */
2
1
  import {
3
2
  type ImportSharedPermitOptions,
4
3
  PermitUtils,
@@ -7,6 +6,10 @@ import {
7
6
  type Permit,
8
7
  permitStore,
9
8
  type SerializedPermit,
9
+ type SelfPermit,
10
+ type RecipientPermit,
11
+ type SharingPermit,
12
+ type PermitHashFields,
10
13
  } from '@/permits';
11
14
 
12
15
  import { type PublicClient, type WalletClient } from 'viem';
@@ -19,16 +22,16 @@ const storeActivePermit = async (permit: Permit, publicClient: any, walletClient
19
22
  const account = walletClient.account!.address;
20
23
 
21
24
  permitStore.setPermit(chainId, account, permit);
22
- permitStore.setActivePermitHash(chainId, account, PermitUtils.getHash(permit));
25
+ permitStore.setActivePermitHash(chainId, account, permit.hash);
23
26
  };
24
27
 
25
28
  // Generic function to handle permit creation with error handling
26
- const createPermitWithSign = async <T>(
29
+ const createPermitWithSign = async <T, TPermit extends Permit>(
27
30
  options: T,
28
31
  publicClient: PublicClient,
29
32
  walletClient: WalletClient,
30
- permitMethod: (options: T, publicClient: PublicClient, walletClient: WalletClient) => Promise<Permit>
31
- ): Promise<Permit> => {
33
+ permitMethod: (options: T, publicClient: PublicClient, walletClient: WalletClient) => Promise<TPermit>
34
+ ): Promise<TPermit> => {
32
35
  const permit = await permitMethod(options, publicClient, walletClient);
33
36
  await storeActivePermit(permit, publicClient, walletClient);
34
37
  return permit;
@@ -46,7 +49,7 @@ const createSelf = async (
46
49
  options: CreateSelfPermitOptions,
47
50
  publicClient: PublicClient,
48
51
  walletClient: WalletClient
49
- ): Promise<Permit> => {
52
+ ): Promise<SelfPermit> => {
50
53
  return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSelfAndSign);
51
54
  };
52
55
 
@@ -54,21 +57,21 @@ const createSharing = async (
54
57
  options: CreateSharingPermitOptions,
55
58
  publicClient: PublicClient,
56
59
  walletClient: WalletClient
57
- ): Promise<Permit> => {
60
+ ): Promise<SharingPermit> => {
58
61
  return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSharingAndSign);
59
62
  };
60
63
 
61
64
  const importShared = async (
62
- options: ImportSharedPermitOptions | any | string,
65
+ options: ImportSharedPermitOptions | string,
63
66
  publicClient: PublicClient,
64
67
  walletClient: WalletClient
65
- ): Promise<Permit> => {
68
+ ): Promise<RecipientPermit> => {
66
69
  return createPermitWithSign(options, publicClient, walletClient, PermitUtils.importSharedAndSign);
67
70
  };
68
71
 
69
72
  // PERMIT UTILS
70
73
 
71
- const getHash = (permit: Permit) => {
74
+ const getHash = (permit: PermitHashFields) => {
72
75
  return PermitUtils.getHash(permit);
73
76
  };
74
77
 
@@ -94,24 +97,83 @@ const getActivePermit = async (chainId: number, account: string): Promise<Permit
94
97
  return permitStore.getActivePermit(chainId, account);
95
98
  };
96
99
 
97
- const getActivePermitHash = async (chainId: number, account: string): Promise<string | undefined> => {
100
+ const getActivePermitHash = (chainId: number, account: string): string | undefined => {
98
101
  return permitStore.getActivePermitHash(chainId, account);
99
102
  };
100
103
 
101
- const selectActivePermit = async (chainId: number, account: string, hash: string): Promise<void> => {
102
- await permitStore.setActivePermitHash(chainId, account, hash);
104
+ const selectActivePermit = (chainId: number, account: string, hash: string): void => {
105
+ permitStore.setActivePermitHash(chainId, account, hash);
103
106
  };
104
107
 
105
- // REMOVE
108
+ // GET OR CREATE
109
+
110
+ /**
111
+ * Get the active self permit or create a new one if it doesn't exist
112
+ * @param publicClient - The public client
113
+ * @param walletClient - The wallet client
114
+ * @param chainId - Optional chain ID (will use publicClient if not provided)
115
+ * @param account - Optional account (will use walletClient if not provided)
116
+ * @param options - The options for creating a self permit
117
+ * @returns The existing or newly created permit
118
+ */
119
+ const getOrCreateSelfPermit = async (
120
+ publicClient: PublicClient,
121
+ walletClient: WalletClient,
122
+ chainId?: number,
123
+ account?: string,
124
+ options?: CreateSelfPermitOptions
125
+ ): Promise<Permit> => {
126
+ const _chainId = chainId ?? (await publicClient.getChainId());
127
+ const _account = account ?? walletClient.account!.address;
128
+
129
+ // Try to get active permit first
130
+ const activePermit = await getActivePermit(_chainId, _account);
131
+
132
+ if (activePermit && activePermit.type === 'self') {
133
+ return activePermit;
134
+ }
106
135
 
107
- const removePermit = async (chainId: number, account: string, hash: string): Promise<void> => {
108
- await permitStore.removePermit(chainId, account, hash);
136
+ // No active permit or wrong type, create new one
137
+ return createSelf(options ?? { issuer: _account, name: 'Autogenerated Self Permit' }, publicClient, walletClient);
109
138
  };
110
139
 
111
- const removeActivePermit = async (chainId: number, account: string): Promise<void> => {
112
- await permitStore.removeActivePermitHash(chainId, account);
140
+ /**
141
+ * Get the active sharing permit or create a new one if it doesn't exist
142
+ * @param publicClient - The public client
143
+ * @param walletClient - The wallet client
144
+ * @param options - The options for creating a sharing permit (required)
145
+ * @param chainId - Optional chain ID (will use publicClient if not provided)
146
+ * @param account - Optional account (will use walletClient if not provided)
147
+ * @returns The existing or newly created permit
148
+ */
149
+ const getOrCreateSharingPermit = async (
150
+ publicClient: PublicClient,
151
+ walletClient: WalletClient,
152
+ options: CreateSharingPermitOptions,
153
+ chainId?: number,
154
+ account?: string
155
+ ): Promise<Permit> => {
156
+ const _chainId = chainId ?? (await publicClient.getChainId());
157
+ const _account = account ?? walletClient.account!.address;
158
+
159
+ // Try to get active permit first
160
+ const activePermit = await getActivePermit(_chainId, _account);
161
+
162
+ if (activePermit && activePermit.type === 'sharing') {
163
+ return activePermit;
164
+ }
165
+
166
+ return createSharing(options, publicClient, walletClient);
113
167
  };
114
168
 
169
+ // REMOVE
170
+
171
+ const removePermit = async (chainId: number, account: string, hash: string): Promise<void> =>
172
+ permitStore.removePermit(chainId, account, hash);
173
+
174
+ const removeActivePermit = async (chainId: number, account: string): Promise<void> =>
175
+ permitStore.removeActivePermitHash(chainId, account);
176
+
115
177
  // EXPORT
116
178
 
117
179
  export const permits = {
@@ -122,6 +184,9 @@ export const permits = {
122
184
  createSharing,
123
185
  importShared,
124
186
 
187
+ getOrCreateSelfPermit,
188
+ getOrCreateSharingPermit,
189
+
125
190
  getHash,
126
191
  serialize,
127
192
  deserialize,