@cofhe/sdk 0.6.1 → 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 (113) hide show
  1. package/CHANGELOG.md +43 -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/decrypt/MockThresholdNetworkAbi.ts +20 -11
  27. package/core/decrypt/apiError.ts +104 -0
  28. package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
  29. package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
  30. package/core/decrypt/decryptForTxBuilder.ts +102 -102
  31. package/core/decrypt/decryptForViewBuilder.ts +90 -90
  32. package/core/decrypt/submitRetry.ts +38 -30
  33. package/core/decrypt/tnDecryptV1.ts +5 -5
  34. package/core/decrypt/tnDecryptV2.ts +25 -21
  35. package/core/decrypt/tnSealOutputV1.ts +4 -3
  36. package/core/decrypt/tnSealOutputV2.ts +24 -18
  37. package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
  38. package/core/encrypt/encryptInputsBuilder.ts +86 -52
  39. package/core/encrypt/zkPackProveVerify.ts +25 -18
  40. package/core/error.ts +34 -6
  41. package/core/index.ts +4 -14
  42. package/core/test/acpDefaults.test.ts +52 -0
  43. package/core/test/acps.test.ts +596 -0
  44. package/core/test/apiError.test.ts +130 -0
  45. package/core/test/client.test.ts +22 -19
  46. package/core/test/config.test.ts +25 -5
  47. package/core/test/decrypt.test.ts +40 -35
  48. package/core/test/decryptBuilders.test.ts +68 -68
  49. package/core/test/decryptErrorCodes.test.ts +217 -0
  50. package/core/test/encryptInputsBuilder.test.ts +72 -41
  51. package/core/test/pollCallbacks.test.ts +89 -18
  52. package/core/test/stagingRedirect.ts +18 -0
  53. package/core/test/submitRetry.test.ts +182 -0
  54. package/core/types.ts +9 -69
  55. package/dist/acp-Wi6isVQI.d.cts +407 -0
  56. package/dist/acp-Wi6isVQI.d.ts +407 -0
  57. package/dist/acps.cjs +1081 -0
  58. package/dist/acps.d.cts +480 -0
  59. package/dist/acps.d.ts +480 -0
  60. package/dist/acps.js +2 -0
  61. package/dist/chains.cjs +14 -1
  62. package/dist/chains.d.cts +30 -1
  63. package/dist/chains.d.ts +30 -1
  64. package/dist/chains.js +1 -1
  65. package/dist/{chunk-NOC3PYB7.js → chunk-43USWPEH.js} +930 -580
  66. package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
  67. package/dist/chunk-Q7CBWGQX.js +1029 -0
  68. package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
  69. package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
  70. package/dist/core.cjs +1358 -1002
  71. package/dist/core.d.cts +31 -9
  72. package/dist/core.d.ts +31 -9
  73. package/dist/core.js +3 -3
  74. package/dist/node.cjs +1293 -952
  75. package/dist/node.d.cts +2 -2
  76. package/dist/node.d.ts +2 -2
  77. package/dist/node.js +3 -3
  78. package/dist/web.cjs +1293 -952
  79. package/dist/web.d.cts +2 -2
  80. package/dist/web.d.ts +2 -2
  81. package/dist/web.js +3 -3
  82. package/node/test/inherited.test.ts +75 -65
  83. package/node/test/tfheinit.test.ts +23 -8
  84. package/package.json +6 -6
  85. package/web/test/client.web.test.ts +5 -1
  86. package/web/test/inherited.web.test.ts +75 -65
  87. package/web/test/tfheinit.web.test.ts +14 -5
  88. package/web/test/worker.config.web.test.ts +38 -23
  89. package/web/test/worker.output.web.test.ts +25 -24
  90. package/core/encrypt/encryptUtils.ts +0 -67
  91. package/core/permits.ts +0 -216
  92. package/core/test/permits.test.ts +0 -596
  93. package/dist/chunk-VB62WYPL.js +0 -978
  94. package/dist/permit-DnVMDT5h.d.cts +0 -376
  95. package/dist/permit-DnVMDT5h.d.ts +0 -376
  96. package/dist/permits.cjs +0 -1026
  97. package/dist/permits.d.cts +0 -353
  98. package/dist/permits.d.ts +0 -353
  99. package/dist/permits.js +0 -2
  100. package/permits/index.ts +0 -68
  101. package/permits/permit.ts +0 -385
  102. package/permits/sealing.ts +0 -131
  103. package/permits/signature.ts +0 -79
  104. package/permits/store.ts +0 -157
  105. package/permits/test/localstorage.test.ts +0 -113
  106. package/permits/test/permit.test.ts +0 -557
  107. package/permits/test/sealing.test.ts +0 -84
  108. package/permits/test/store.test.ts +0 -88
  109. package/permits/test/validation.test.ts +0 -361
  110. package/permits/test-utils.ts +0 -28
  111. package/permits/types.ts +0 -204
  112. package/permits/validation.ts +0 -327
  113. /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
+ });