@cofhe/sdk 0.6.0 → 0.7.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 (114) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/acps/acp.ts +411 -0
  3. package/acps/index.ts +70 -0
  4. package/{permits → acps}/onchain-utils.ts +49 -24
  5. package/acps/sealing.ts +90 -0
  6. package/acps/signature.ts +89 -0
  7. package/acps/store.ts +172 -0
  8. package/acps/test/acp.test.ts +615 -0
  9. package/acps/test/localstorage.test.ts +105 -0
  10. package/acps/test/sealing.test.ts +77 -0
  11. package/acps/test/store.test.ts +88 -0
  12. package/acps/test/validation.test.ts +361 -0
  13. package/acps/test-utils.ts +32 -0
  14. package/acps/types.ts +252 -0
  15. package/acps/validation.ts +392 -0
  16. package/adapters/test/ethers5.test.ts +4 -1
  17. package/adapters/test/ethers6.test.ts +4 -1
  18. package/adapters/test/wagmi.test.ts +5 -2
  19. package/chains/chains/stagingCofhe.ts +21 -0
  20. package/chains/index.ts +3 -1
  21. package/chains/test/chains.test.ts +2 -1
  22. package/core/acps.ts +625 -0
  23. package/core/client.ts +136 -39
  24. package/core/clientTypes.ts +52 -41
  25. package/core/config.ts +66 -5
  26. package/core/debug.ts +72 -0
  27. package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
  28. package/core/decrypt/apiError.ts +104 -0
  29. package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
  30. package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
  31. package/core/decrypt/decryptForTxBuilder.ts +102 -102
  32. package/core/decrypt/decryptForViewBuilder.ts +90 -90
  33. package/core/decrypt/submitRetry.ts +38 -30
  34. package/core/decrypt/tnDecryptV1.ts +5 -5
  35. package/core/decrypt/tnDecryptV2.ts +45 -34
  36. package/core/decrypt/tnSealOutputV1.ts +4 -3
  37. package/core/decrypt/tnSealOutputV2.ts +44 -31
  38. package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
  39. package/core/encrypt/encryptInputsBuilder.ts +86 -52
  40. package/core/encrypt/zkPackProveVerify.ts +25 -18
  41. package/core/error.ts +34 -6
  42. package/core/index.ts +14 -14
  43. package/core/test/acpDefaults.test.ts +52 -0
  44. package/core/test/acps.test.ts +596 -0
  45. package/core/test/apiError.test.ts +130 -0
  46. package/core/test/client.test.ts +22 -19
  47. package/core/test/config.test.ts +25 -5
  48. package/core/test/decrypt.test.ts +40 -35
  49. package/core/test/decryptBuilders.test.ts +68 -68
  50. package/core/test/decryptErrorCodes.test.ts +217 -0
  51. package/core/test/encryptInputsBuilder.test.ts +72 -41
  52. package/core/test/pollCallbacks.test.ts +89 -18
  53. package/core/test/stagingRedirect.ts +18 -0
  54. package/core/test/submitRetry.test.ts +182 -0
  55. package/core/types.ts +9 -69
  56. package/dist/acp-Wi6isVQI.d.cts +407 -0
  57. package/dist/acp-Wi6isVQI.d.ts +407 -0
  58. package/dist/acps.cjs +1081 -0
  59. package/dist/acps.d.cts +480 -0
  60. package/dist/acps.d.ts +480 -0
  61. package/dist/acps.js +2 -0
  62. package/dist/chains.cjs +14 -1
  63. package/dist/chains.d.cts +30 -1
  64. package/dist/chains.d.ts +30 -1
  65. package/dist/chains.js +1 -1
  66. package/dist/{chunk-PE5V5CCV.js → chunk-43USWPEH.js} +1000 -604
  67. package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
  68. package/dist/chunk-Q7CBWGQX.js +1029 -0
  69. package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
  70. package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
  71. package/dist/core.cjs +1431 -1026
  72. package/dist/core.d.cts +55 -10
  73. package/dist/core.d.ts +55 -10
  74. package/dist/core.js +3 -3
  75. package/dist/node.cjs +1356 -976
  76. package/dist/node.d.cts +2 -2
  77. package/dist/node.d.ts +2 -2
  78. package/dist/node.js +3 -3
  79. package/dist/web.cjs +1356 -976
  80. package/dist/web.d.cts +2 -2
  81. package/dist/web.d.ts +2 -2
  82. package/dist/web.js +3 -3
  83. package/node/test/inherited.test.ts +75 -65
  84. package/node/test/tfheinit.test.ts +23 -8
  85. package/package.json +6 -6
  86. package/web/test/client.web.test.ts +5 -1
  87. package/web/test/inherited.web.test.ts +75 -65
  88. package/web/test/tfheinit.web.test.ts +14 -5
  89. package/web/test/worker.config.web.test.ts +38 -23
  90. package/web/test/worker.output.web.test.ts +25 -24
  91. package/core/encrypt/encryptUtils.ts +0 -67
  92. package/core/permits.ts +0 -206
  93. package/core/test/permits.test.ts +0 -534
  94. package/dist/chunk-VB62WYPL.js +0 -978
  95. package/dist/permit-DnVMDT5h.d.cts +0 -376
  96. package/dist/permit-DnVMDT5h.d.ts +0 -376
  97. package/dist/permits.cjs +0 -1026
  98. package/dist/permits.d.cts +0 -353
  99. package/dist/permits.d.ts +0 -353
  100. package/dist/permits.js +0 -2
  101. package/permits/index.ts +0 -68
  102. package/permits/permit.ts +0 -385
  103. package/permits/sealing.ts +0 -131
  104. package/permits/signature.ts +0 -79
  105. package/permits/store.ts +0 -157
  106. package/permits/test/localstorage.test.ts +0 -113
  107. package/permits/test/permit.test.ts +0 -557
  108. package/permits/test/sealing.test.ts +0 -84
  109. package/permits/test/store.test.ts +0 -88
  110. package/permits/test/validation.test.ts +0 -361
  111. package/permits/test-utils.ts +0 -28
  112. package/permits/types.ts +0 -204
  113. package/permits/validation.ts +0 -327
  114. /package/{permits → acps}/utils.ts +0 -0
@@ -0,0 +1,615 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import {
3
+ ACPUtils,
4
+ type CreateSelfACPOptions,
5
+ type CreateSharingACPOptions,
6
+ type ImportSharedACPOptions,
7
+ } from '../index.js';
8
+
9
+ // ACP domain resolution requires an upgraded on-chain ACL (domain ("ACL","2") served by the ACL itself).
10
+ // Public testnets still run the V2 contracts, so the domain fetch is stubbed here —
11
+ // these tests cover the signing flow, not on-chain domain resolution (which is
12
+ // exercised e2e against mocks in test/hardhat-plugin-test).
13
+ vi.mock('../onchain-utils.js', async (importOriginal) => ({
14
+ ...(await importOriginal<typeof import('../onchain-utils.js')>()),
15
+ getAclEIP712Domain: async () => ({
16
+ name: 'ACL',
17
+ version: '2',
18
+ chainId: 421614,
19
+ // arbitrary non-zero address — signatures are never verified on-chain in these tests
20
+ verifyingContract: '0x1111111111111111111111111111111111111111' as `0x${string}`,
21
+ }),
22
+ }));
23
+ import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
24
+ import { arbitrumSepolia } from 'viem/chains';
25
+ import { privateKeyToAccount } from 'viem/accounts';
26
+
27
+ // Test private keys (well-known test keys from Anvil/Hardhat)
28
+ const BOB_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Bob - always issuer
29
+ const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; // Alice - always recipient
30
+
31
+ // Create real viem clients for Arbitrum Sepolia
32
+ const publicClient: PublicClient = createPublicClient({
33
+ chain: arbitrumSepolia,
34
+ transport: http(),
35
+ });
36
+
37
+ const bobWalletClient: WalletClient = createWalletClient({
38
+ chain: arbitrumSepolia,
39
+ transport: http(),
40
+ account: privateKeyToAccount(BOB_PRIVATE_KEY),
41
+ });
42
+
43
+ const aliceWalletClient: WalletClient = createWalletClient({
44
+ chain: arbitrumSepolia,
45
+ transport: http(),
46
+ account: privateKeyToAccount(ALICE_PRIVATE_KEY),
47
+ });
48
+
49
+ // Helper to get the wallet addresses
50
+ const bobAddress = bobWalletClient.account!.address;
51
+ const aliceAddress = aliceWalletClient.account!.address;
52
+
53
+ describe('ACPUtils Tests', () => {
54
+ describe('createSelf', () => {
55
+ it('should create a self acp with valid options', async () => {
56
+ const options: CreateSelfACPOptions = {
57
+ type: 'self',
58
+ issuer: bobAddress,
59
+ name: 'Test ACP',
60
+ };
61
+
62
+ const acp = ACPUtils.createSelf(options);
63
+
64
+ expect(acp.hash).toBe(ACPUtils.getHash(acp));
65
+ expect(acp.type).toBe('self');
66
+ expect(acp.name).toBe('Test ACP');
67
+ expect(acp.type).toBe('self');
68
+ expect(acp.issuer).toBe(bobAddress);
69
+ expect(acp.sealingPrivateKey).toBeDefined();
70
+ expect(acp.sealingKey).toBeDefined();
71
+
72
+ // Should not be signed yet
73
+ expect(acp.issuerSignature).toBe('0x');
74
+ expect(acp.recipientSignature).toBe('0x');
75
+ });
76
+
77
+ it('should throw error for invalid options', async () => {
78
+ const options: CreateSelfACPOptions = {
79
+ type: 'self',
80
+ issuer: 'invalid-address',
81
+ name: 'Test ACP',
82
+ };
83
+
84
+ expect(() => ACPUtils.createSelf(options)).toThrowError();
85
+ });
86
+ });
87
+
88
+ describe('createSharing', () => {
89
+ it('should create a sharing acp with valid options', async () => {
90
+ const options: CreateSharingACPOptions = {
91
+ type: 'sharing',
92
+ issuer: bobAddress,
93
+ recipient: aliceAddress,
94
+ name: 'Test Sharing ACP',
95
+ };
96
+
97
+ const acp = ACPUtils.createSharing(options);
98
+
99
+ expect(acp.hash).toBe(ACPUtils.getHash(acp));
100
+ expect(acp.type).toBe('sharing');
101
+ expect(acp.name).toBe('Test Sharing ACP');
102
+ expect(acp.type).toBe('sharing');
103
+ expect(acp.issuer).toBe(bobAddress);
104
+ expect(acp.recipient).toBe(aliceAddress);
105
+ expect(acp.sealingPrivateKey).toBeDefined();
106
+ expect(acp.sealingKey).toBeDefined();
107
+
108
+ // Should not be signed yet
109
+ expect(acp.issuerSignature).toBe('0x');
110
+ expect(acp.recipientSignature).toBe('0x');
111
+ });
112
+
113
+ it('should throw error for invalid recipient', async () => {
114
+ const options: CreateSharingACPOptions = {
115
+ type: 'sharing',
116
+ issuer: bobAddress,
117
+ recipient: 'invalid-address',
118
+ name: 'Test Sharing ACP',
119
+ };
120
+
121
+ expect(() => ACPUtils.createSharing(options)).toThrow();
122
+ });
123
+ });
124
+
125
+ describe('importShared', () => {
126
+ it('should import a shared acp with valid options', async () => {
127
+ const options: ImportSharedACPOptions = {
128
+ issuer: bobAddress,
129
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
130
+ recipient: aliceAddress,
131
+ issuerSignature: '0x1234567890abcdef',
132
+ name: 'Test Import ACP',
133
+ };
134
+
135
+ const acp = ACPUtils.importShared(options);
136
+
137
+ expect(acp.hash).toBe(ACPUtils.getHash(acp));
138
+ expect(acp.type).toBe('recipient');
139
+ expect(acp.name).toBe('Test Import ACP');
140
+ expect(acp.issuer).toBe(bobAddress);
141
+ expect(acp.recipient).toBe(aliceAddress);
142
+ expect(acp.issuerSignature).toBe('0x1234567890abcdef');
143
+ expect(acp.sealingPrivateKey).toBeDefined();
144
+ expect(acp.sealingKey).toBeDefined();
145
+
146
+ // Should not be signed yet
147
+ expect(acp.recipientSignature).toBe('0x');
148
+ });
149
+
150
+ it('should import a shared acp with valid options as string', async () => {
151
+ const options: ImportSharedACPOptions = {
152
+ issuer: bobAddress,
153
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
154
+ recipient: aliceAddress,
155
+ issuerSignature: '0x1234567890abcdef',
156
+ };
157
+
158
+ const stringOptions = JSON.stringify(options);
159
+
160
+ const acp = ACPUtils.importShared(stringOptions);
161
+
162
+ expect(acp.type).toBe('recipient');
163
+ });
164
+
165
+ it('should throw error for invalid acp type', async () => {
166
+ const options = {
167
+ type: 'self',
168
+ issuer: bobAddress,
169
+ recipient: aliceAddress,
170
+ issuerSignature: '0x1234567890abcdef',
171
+ } as unknown as ImportSharedACPOptions;
172
+
173
+ expect(() => ACPUtils.importShared(options)).toThrow();
174
+
175
+ const options2 = {
176
+ type: 'recipient',
177
+ issuer: bobAddress,
178
+ recipient: aliceAddress,
179
+ issuerSignature: '0x1234567890abcdef',
180
+ } as unknown as ImportSharedACPOptions;
181
+
182
+ expect(() => ACPUtils.importShared(options2)).toThrow();
183
+ });
184
+
185
+ it('should throw error for missing issuerSignature', async () => {
186
+ const options: ImportSharedACPOptions = {
187
+ issuer: bobAddress,
188
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
189
+ recipient: aliceAddress,
190
+ issuerSignature: '0x', // Invalid empty signature
191
+ name: 'Test Import ACP',
192
+ };
193
+
194
+ expect(() => ACPUtils.importShared(options)).toThrow();
195
+ });
196
+
197
+ it('should throw error for missing expiration', async () => {
198
+ const options = {
199
+ issuer: bobAddress,
200
+ recipient: aliceAddress,
201
+ issuerSignature: '0x1234567890abcdef',
202
+ } as unknown as ImportSharedACPOptions;
203
+ expect(() => ACPUtils.importShared(options)).toThrow();
204
+ });
205
+ });
206
+
207
+ describe('createSelfAndSign', () => {
208
+ it('should create and sign a self acp', async () => {
209
+ const options: CreateSelfACPOptions = {
210
+ issuer: bobAddress,
211
+ name: 'Test ACP',
212
+ };
213
+
214
+ const acp = await ACPUtils.createSelfAndSign(options, publicClient, bobWalletClient);
215
+
216
+ expect(acp.type).toBe('self');
217
+ expect(acp.issuerSignature).toBeDefined();
218
+ expect(acp.issuerSignature).not.toBe('0x');
219
+ expect(acp.recipientSignature).toBe('0x');
220
+ expect(acp._signedDomain).toBeDefined();
221
+ });
222
+ });
223
+
224
+ describe('createSharingAndSign', () => {
225
+ it('should create and sign a sharing acp', async () => {
226
+ const options: CreateSharingACPOptions = {
227
+ issuer: bobAddress,
228
+ recipient: aliceAddress,
229
+ name: 'Test Sharing ACP',
230
+ };
231
+
232
+ const acp = await ACPUtils.createSharingAndSign(options, publicClient, bobWalletClient);
233
+
234
+ expect(acp.type).toBe('sharing');
235
+ expect(acp.issuerSignature).toBeDefined();
236
+ expect(acp.issuerSignature).not.toBe('0x');
237
+ expect(acp.recipientSignature).toBe('0x');
238
+ expect(acp._signedDomain).toBeDefined();
239
+ });
240
+ });
241
+
242
+ describe('importSharedAndSign', () => {
243
+ it('should import and sign a shared acp', async () => {
244
+ const options: ImportSharedACPOptions = {
245
+ issuer: bobAddress,
246
+ recipient: aliceAddress,
247
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
248
+ issuerSignature: '0x1234567890abcdef',
249
+ name: 'Test Import ACP',
250
+ };
251
+
252
+ const acp = await ACPUtils.importSharedAndSign(options, publicClient, aliceWalletClient);
253
+
254
+ expect(acp.type).toBe('recipient');
255
+ expect(acp.recipientSignature).toBeDefined();
256
+ expect(acp.recipientSignature).not.toBe('0x');
257
+ expect(acp._signedDomain).toBeDefined();
258
+ });
259
+
260
+ it('should import and sign a shared acp string', async () => {
261
+ const options: ImportSharedACPOptions = {
262
+ issuer: bobAddress,
263
+ recipient: aliceAddress,
264
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
265
+ issuerSignature: '0x1234567890abcdef',
266
+ };
267
+
268
+ const stringOptions = JSON.stringify(options);
269
+
270
+ const acp = await ACPUtils.importSharedAndSign(stringOptions, publicClient, aliceWalletClient);
271
+
272
+ expect(acp.type).toBe('recipient');
273
+ expect(acp.recipientSignature).toBeDefined();
274
+ expect(acp.recipientSignature).not.toBe('0x');
275
+ expect(acp._signedDomain).toBeDefined();
276
+ });
277
+
278
+ it('should import and sign a shared acp json object', async () => {
279
+ const options: ImportSharedACPOptions = {
280
+ issuer: bobAddress,
281
+ recipient: aliceAddress,
282
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
283
+ issuerSignature: '0x1234567890abcdef',
284
+ };
285
+
286
+ const jsonOptions = JSON.parse(JSON.stringify(options));
287
+
288
+ const acp = await ACPUtils.importSharedAndSign(jsonOptions, publicClient, aliceWalletClient);
289
+
290
+ expect(acp.type).toBe('recipient');
291
+ expect(acp.recipientSignature).toBeDefined();
292
+ expect(acp.recipientSignature).not.toBe('0x');
293
+ expect(acp._signedDomain).toBeDefined();
294
+ });
295
+ });
296
+
297
+ describe('sign', () => {
298
+ it('should sign a self acp', async () => {
299
+ const acp = ACPUtils.createSelf({
300
+ issuer: bobAddress,
301
+ name: 'Test ACP',
302
+ });
303
+
304
+ const signedACP = await ACPUtils.sign(acp, publicClient, bobWalletClient);
305
+
306
+ expect(signedACP.type).toBe('self');
307
+ expect(signedACP.issuerSignature).toBeDefined();
308
+ expect(signedACP.issuerSignature).not.toBe('0x');
309
+ expect(signedACP._signedDomain).toBeDefined();
310
+ });
311
+
312
+ it('should sign a recipient acp', async () => {
313
+ const acp = ACPUtils.importShared({
314
+ issuer: bobAddress,
315
+ recipient: aliceAddress,
316
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
317
+ issuerSignature: '0x1111111111111111111111111111111111111111111111111111111111111111',
318
+ name: 'Test ACP',
319
+ });
320
+
321
+ const signedACP = await ACPUtils.sign(acp, publicClient, aliceWalletClient);
322
+
323
+ expect(signedACP.recipientSignature).toBeDefined();
324
+ expect(signedACP.recipientSignature).not.toBe('0x');
325
+ expect(signedACP._signedDomain).toBeDefined();
326
+ });
327
+
328
+ it('should throw error for undefined signer', async () => {
329
+ const acp = ACPUtils.createSelf({
330
+ issuer: bobAddress,
331
+ name: 'Test ACP',
332
+ });
333
+
334
+ await expect(
335
+ // @ts-expect-error - undefined signer
336
+ ACPUtils.sign(acp, publicClient, undefined)
337
+ ).rejects.toThrow();
338
+ });
339
+ });
340
+
341
+ describe('serialize/deserialize', () => {
342
+ it('should serialize and deserialize an ACP', async () => {
343
+ const originalACP = ACPUtils.createSelf({
344
+ issuer: bobAddress,
345
+ name: 'Test ACP',
346
+ });
347
+
348
+ const serialized = ACPUtils.serialize(originalACP);
349
+ const deserialized = ACPUtils.deserialize(serialized);
350
+
351
+ expect(deserialized.type).toBe('self');
352
+ expect(deserialized.name).toBe(originalACP.name);
353
+ expect(deserialized.type).toBe(originalACP.type);
354
+ expect(deserialized.issuer).toBe(originalACP.issuer);
355
+ expect(deserialized.sealingPrivateKey).toBe(originalACP.sealingPrivateKey);
356
+ expect(deserialized.sealingKey).toBe(originalACP.sealingKey);
357
+ });
358
+ });
359
+
360
+ describe('getPermission', () => {
361
+ it('should extract permission from acp', async () => {
362
+ const acp = await ACPUtils.createSelfAndSign(
363
+ {
364
+ issuer: bobAddress,
365
+ name: 'Test ACP',
366
+ },
367
+ publicClient,
368
+ bobWalletClient
369
+ );
370
+
371
+ const permission = ACPUtils.getPublic(acp);
372
+
373
+ expect(permission.issuer).toBe(acp.issuer);
374
+ expect(permission.sealingKey).toBe(acp.sealingKey);
375
+ expect(permission).not.toHaveProperty('name');
376
+ expect(permission).not.toHaveProperty('type');
377
+ });
378
+ });
379
+
380
+ describe('getHash', () => {
381
+ it('should generate consistent hash for same acp data', async () => {
382
+ const expiration = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
383
+ const acp1 = ACPUtils.createSelf({
384
+ expiration,
385
+ issuer: bobAddress,
386
+ name: 'Test ACP',
387
+ });
388
+
389
+ const acp2 = ACPUtils.createSelf({
390
+ expiration,
391
+ issuer: bobAddress,
392
+ name: 'Test ACP',
393
+ });
394
+
395
+ expect(acp1.hash).toBe(acp2.hash);
396
+ });
397
+ });
398
+
399
+ describe('export', () => {
400
+ it('throws when exporting a non-sharing acp', async () => {
401
+ const acp = ACPUtils.createSelf({
402
+ issuer: bobAddress,
403
+ name: 'Test ACP',
404
+ });
405
+
406
+ // export includes the issuer signature — only sharing acps are exportable
407
+ expect(() => ACPUtils.export(acp)).toThrow(/only 'sharing' ACPs are exportable/);
408
+ });
409
+
410
+ it('throws when exporting an unsigned sharing acp', async () => {
411
+ const acp = ACPUtils.createSharing({
412
+ issuer: bobAddress,
413
+ recipient: aliceAddress,
414
+ name: 'Test Sharing ACP',
415
+ });
416
+
417
+ expect(() => ACPUtils.export(acp)).toThrow(/sign it first/);
418
+ });
419
+
420
+ it('should export sharing acp data with recipient and issuerSignature', async () => {
421
+ const acp = await ACPUtils.createSharingAndSign(
422
+ {
423
+ issuer: bobAddress,
424
+ recipient: aliceAddress,
425
+ name: 'Test Sharing ACP',
426
+ },
427
+ publicClient,
428
+ bobWalletClient
429
+ );
430
+
431
+ const exported = ACPUtils.export(acp);
432
+ const parsed = JSON.parse(exported);
433
+
434
+ expect(parsed.name).toBe('Test Sharing ACP');
435
+ expect(parsed.type).toBe('sharing');
436
+ expect(parsed.issuer).toBe(bobAddress);
437
+ expect(parsed.recipient).toBe(aliceAddress);
438
+ expect(parsed.issuerSignature).toBeDefined();
439
+ expect(parsed.issuerSignature).not.toBe('0x');
440
+ expect(parsed).not.toHaveProperty('sealingPrivateKey');
441
+ });
442
+
443
+ it('exports the fixed SharedACP shape — empty fields present, not omitted', async () => {
444
+ // every zero-value field must still appear in the JSON (signature set manually
445
+ // to pass the unsigned-export guard without touching the other defaults)
446
+ const acp = {
447
+ ...ACPUtils.createSharing({
448
+ issuer: bobAddress,
449
+ recipient: aliceAddress,
450
+ name: 'Test Sharing ACP',
451
+ }),
452
+ issuerSignature: '0xsig' as `0x${string}`,
453
+ };
454
+
455
+ const parsed = JSON.parse(ACPUtils.export(acp));
456
+
457
+ expect(Object.keys(parsed).sort()).toEqual(
458
+ [
459
+ 'name',
460
+ 'type',
461
+ 'issuer',
462
+ 'expiration',
463
+ 'recipient',
464
+ 'revokerData',
465
+ 'revokerContract',
466
+ 'scope',
467
+ 'contracts',
468
+ 'handles',
469
+ 'issuerSignature',
470
+ ].sort()
471
+ );
472
+ expect(parsed.issuerSignature).toBe('0xsig');
473
+ expect(parsed.scope).toBe(0);
474
+ expect(parsed.contracts).toEqual([]);
475
+ expect(parsed.handles).toEqual([]);
476
+ expect(parsed.revokerData).toBe(0);
477
+ // private component never leaves the client
478
+ expect(parsed).not.toHaveProperty('sealingPrivateKey');
479
+ expect(parsed).not.toHaveProperty('sealingKey');
480
+ expect(parsed).not.toHaveProperty('hash');
481
+ expect(parsed).not.toHaveProperty('recipientSignature');
482
+ });
483
+ });
484
+
485
+ describe('updateName', () => {
486
+ it('should update acp name immutably', async () => {
487
+ const acp = ACPUtils.createSelf({
488
+ issuer: bobAddress,
489
+ name: 'Original Name',
490
+ });
491
+
492
+ const updatedACP = ACPUtils.updateName(acp, 'New Name');
493
+
494
+ expect(updatedACP.name).toBe('New Name');
495
+ expect(acp.name).toBe('Original Name'); // Original should be unchanged
496
+ expect(updatedACP).not.toBe(acp); // Should be a new object
497
+ });
498
+ });
499
+
500
+ describe('validation helpers', () => {
501
+ it('should check if acp is expired', async () => {
502
+ const expiredACP = ACPUtils.createSelf({
503
+ issuer: bobAddress,
504
+ name: 'Test ACP',
505
+ expiration: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
506
+ });
507
+
508
+ const validACP = ACPUtils.createSelf({
509
+ issuer: bobAddress,
510
+ name: 'Test ACP',
511
+ expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
512
+ });
513
+
514
+ expect(ACPUtils.isExpired(expiredACP)).toBe(true);
515
+ expect(ACPUtils.isExpired(validACP)).toBe(false);
516
+ });
517
+
518
+ it('should check if acp is signed', async () => {
519
+ const unsignedACP = ACPUtils.createSelf({
520
+ issuer: bobAddress,
521
+ name: 'Test ACP',
522
+ });
523
+
524
+ const signedACP = await ACPUtils.sign(unsignedACP, publicClient, bobWalletClient);
525
+
526
+ expect(ACPUtils.isSigned(unsignedACP)).toBe(false);
527
+ expect(ACPUtils.isSigned(signedACP)).toBe(true);
528
+ });
529
+
530
+ it('should check overall validity', async () => {
531
+ const validACP = ACPUtils.createSelf({
532
+ issuer: bobAddress,
533
+ name: 'Test ACP',
534
+ expiration: Math.floor(Date.now() / 1000) + 3600,
535
+ });
536
+
537
+ const signedACP = await ACPUtils.sign(validACP, publicClient, bobWalletClient);
538
+
539
+ const validation = ACPUtils.isValid(signedACP);
540
+ expect(validation.valid).toBe(true);
541
+ expect(validation.error).toBeNull();
542
+ });
543
+
544
+ it('should throw on validate() for expired signed acp', async () => {
545
+ const expiredACP = ACPUtils.createSelf({
546
+ issuer: bobAddress,
547
+ name: 'Expired ACP',
548
+ expiration: Math.floor(Date.now() / 1000) - 3600,
549
+ });
550
+
551
+ const signedExpiredACP = await ACPUtils.sign(expiredACP, publicClient, bobWalletClient);
552
+ expect(() => ACPUtils.validate(signedExpiredACP)).toThrow('ACP is expired');
553
+ });
554
+ });
555
+
556
+ describe('real contract interactions', () => {
557
+ it('should fetch EIP712 domain from real Arbitrum Sepolia contract', async () => {
558
+ // This test uses the real public client to fetch actual contract data
559
+ const domain = await ACPUtils.fetchEIP712Domain(publicClient);
560
+
561
+ expect(domain).toBeDefined();
562
+ expect(domain.name).toBeDefined();
563
+ expect(domain.version).toBeDefined();
564
+ expect(domain.chainId).toBeDefined();
565
+ expect(domain.verifyingContract).toBeDefined();
566
+ expect(domain.verifyingContract).toMatch(/^0x[a-fA-F0-9]{40}$/); // Valid Ethereum address
567
+ }, 10000); // 10 second timeout for network call
568
+
569
+ it('should check signed domain validity with real contract data', async () => {
570
+ const acp = ACPUtils.createSelf({
571
+ type: 'self',
572
+ issuer: bobAddress,
573
+ name: 'Test ACP',
574
+ });
575
+
576
+ // Sign the acp to get a domain
577
+ const signedACP = await ACPUtils.sign(acp, publicClient, bobWalletClient);
578
+
579
+ // Check if the signed domain is valid against the real contract
580
+ const isValid = await ACPUtils.checkSignedDomainValid(signedACP, publicClient);
581
+
582
+ expect(typeof isValid).toBe('boolean');
583
+ }, 10000); // 10 second timeout for network call
584
+
585
+ // TODO: Uncomment when updated ACL with checkACPValidity function is deployed
586
+
587
+ // it('should check acp validity on chain with real contract data', async () => {
588
+ // const acp = ACPUtils.createSelf({
589
+ // type: 'self',
590
+ // issuer: bobAddress,
591
+ // name: 'Test ACP',
592
+ // });
593
+
594
+ // const signedACP = await ACPUtils.sign(acp, publicClient, bobWalletClient);
595
+
596
+ // const isValid = await ACPUtils.checkValidityOnChain(signedACP, publicClient);
597
+
598
+ // expect(typeof isValid).toBe('boolean');
599
+ // expect(isValid).toBe(true);
600
+
601
+ // const acpInvalid = ACPUtils.createSelf({
602
+ // type: 'self',
603
+ // issuer: bobAddress,
604
+ // name: 'Test ACP',
605
+ // expiration: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
606
+ // });
607
+
608
+ // const signedACPInvalid = await ACPUtils.sign(acpInvalid, publicClient, bobWalletClient);
609
+ // const isValidInvalid = await ACPUtils.checkValidityOnChain(signedACPInvalid, publicClient);
610
+
611
+ // expect(typeof isValidInvalid).toBe('boolean');
612
+ // expect(isValidInvalid).toBe(false);
613
+ // });
614
+ });
615
+ });