@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @cofhe/sdk Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fb87d91: **ACP-era chains only.** The SDK signs and sends ACP (Permit V3) exclusively. Chains whose ACL still serves the pre-upgrade V2 `Permission` protocol are rejected with a clear error at permit-signing time (the ACL's `eip712Domain` version is probed and must be `"2"`). Decryption request bodies carry the ACP object under the `acp` key, and backend error handling recognizes the `acp_*` codes exclusively — seven map 1:1 onto the former `permit_*` codes, while revocation now surfaces as `acp_denied` (see the [0.7.0 migration guide](https://cofhesdk.fhenix.io/migrating-to-0-7-0) for the full table).
8
+ - fb87d91: **Breaking: Permit (V2) → ACP (Access Control Permission).** Permits become scoped, revocable ACPs; old names are removed rather than deprecated. Highlights (full list in the [0.7.0 migration guide](https://cofhesdk.fhenix.io/migrating-to-0-7-0)):
9
+
10
+ - `Permit`/`Permission`/`PermitUtils`/`client.permits` → `ACP`/`ACPPublic`/`ACPUtils`/`client.acp`; `getPermission()` → `getPublic()`
11
+ - `ACPPrivate` & `ACPPublic` are top-level types, `ACP` is the union; the sealing keypair is flattened to `sealingPrivateKey`/`sealingKey` (the `SealingKey` class is removed)
12
+ - New scope fields (`scope`, `contracts`, `handles: bytes32[]`) and revocation fields renamed `validatorId`/`validatorContract` → `revokerData`/`revokerContract`; default revoker `ACPTimestampRevoker` with `revokeACP`/`revokeAllACPs`/`isACPRevoked` on the client
13
+ - EIP-712 domain bumped to `("ACL", "2")` with new `ACPIssuerSelf`/`ACPIssuerShared`/`ACPRecipient` types — previously signed permits no longer verify; the permit store migrates by wiping retired-format permits
14
+ - `ACPUtils.export()` produces a fixed `SharedACP` shape and only accepts signed sharing ACPs
15
+
16
+ - fb87d91: **On-chain ACP sharing.** New `ACPShareRegistry` contract (deployed with the mocks) lets an issuer post a sharing ACP on-chain for its recipient to discover and import — replacing the JSON copy-paste hand-off.
17
+
18
+ - `client.acp.shareOnChain(acp)` posts a signed sharing ACP (issuer-only, same guards as `export()`); `cancelShare(shareId)` retracts it
19
+ - `client.acp.getIncomingShares()` lists importable shares addressed to the connected account (unexpired, not revoked — the registry checks the share's own revoker)
20
+ - `client.acp.importFromChain(share)` imports like the JSON flow (recipient sealing key + signature); `dismissShare(shareId)` cleans up the entry
21
+ - config: `acp.sharingRegistry: Record<chainId, address>`
22
+ - registry exposes `isShareValid(shareId)` as an on-chain verification hook for contracts
23
+
24
+ - fb87d91: Migrate `cofheClient.encryptInputs` from one-signature-per-ciphertext to the new batch verification scheme (one signature per batch, per `FhenixProtocol/cofhe-contracts#78`).
25
+
26
+ **Breaking:** `EncryptInputsBuilder.execute()` now always returns `[...hashes, signature]` (`HashPlusProofResult<T>`) instead of an array of per-item `EncryptedItemInput` structs. `EncryptedItemInput` and its per-type aliases (`EncryptedBoolInput`, `EncryptedUint8Input`, etc.) are removed, along with `EncryptInputsBuilder.asHashPlusProof()` (no longer needed - it's the only shape now). `@cofhe/abi`'s `extractEncryptableValues`/`insertEncryptedValues` now detect `external*` ABI types instead of `struct InEuintXX`, with a new calling convention: any function with encrypted inputs must end with a plain `bytes` parameter to receive the shared batch signature. `@cofhe/foundry-plugin`'s `CofheClient.createIn*` helpers are renamed to `createExternal*` (`createInEuint32` → `createExternalEuint32`, etc.) and now return an `external*` handle plus a batch signature rather than an `InEuintXX` struct; the `createIn*_asHashPlusProof` variants are removed as redundant. See the [0.7.0 migration guide](https://cofhesdk.fhenix.io/migrating-to-0-7-0) for the full list of changes and what contract authors need to update.
27
+
28
+ - fb87d91: Bind encrypted inputs to a consuming contract (`FhenixProtocol/cofhe-contracts#77`). The verifier-signed digest now includes the contract that will consume the ciphertext, closing a replay path where a signed input packet observed on-chain could be reused against a different contract than the one it was signed for.
29
+
30
+ **Breaking:** `EncryptInputsBuilder.setConsumingContract(address)` must be called before `.execute()` - it throws `ConsumingContractUninitialized` otherwise. `@cofhe/mock-contracts`'s `MockTaskManager` signature digest changed to include the consuming contract (external ABI unchanged). `@cofhe/foundry-plugin`'s `CofheClient.createExternal*`/`createEuint32sBatch` helpers now take a required `address consumingContract` as their last parameter (no global setter). `@cofhe/react`'s `useCofheEncryptAndWriteContract` defaults `consumingContract` to the write's target address automatically; `useCofheEncrypt` accepts it as an explicit option. See the [0.7.0 migration guide](https://cofhesdk.fhenix.io/migrating-to-0-7-0) for full details.
31
+
32
+ - fb87d91: **Permit is now ACP everywhere.** All Permit-named API surface is renamed to ACP to avoid confusion with classic DeFi permits: types (`SelfACP`, `SharingACP`, `RecipientACP`, ...), client methods (`getOrCreateSelfACP`, `withACP`, ...), react hooks and components (`useCofheACPs`, `ACPCard`, ...), error codes (`ACP_DENIED`, ...), the `@cofhe/sdk/permits` entrypoint (now `@cofhe/sdk/acps`), and all documentation. English prose words (permitted/permitting) and protocol contract interfaces are unchanged. The persisted store key also changed; previously stored ACPs are not migrated and are transparently re-created on next use.
33
+
34
+ ### Patch Changes
35
+
36
+ - d4d662f: fix(sdk): don't activate delegated (sharing) permits on creation
37
+
38
+ `createPermitWithSign` always stored the newly created permit as the issuer's active permit, so creating a delegated/sharing permit hijacked the issuer's own active permit. A delegated permit is for the recipient and must not change the issuer's active permit.
39
+
40
+ `createSharing` now defaults to store-only; `getOrCreateSharingACP` (which genuinely wants an active sharing ACP) opts in via `activate: true`. Self ACPs are unchanged.
41
+
42
+ - f01cac7: Decrypt/sealoutput failures now map the threshold network's stable `error` codes to dedicated `CofheErrorCode` values (e.g. `ACPDenied`, `CtNotFound`, `UnsupportedType`) instead of a generic `DecryptFailed`/`SealOutputFailed`, and `CofheError` gains an `apiErrorCode` field with the raw backend string.
43
+
44
+ Also fixes submit-time `404` retries: a `404` is only retried while the backend reports `ct_not_found` (still indexing); any other error code now fails immediately instead of being blindly retried for up to `set404RetryTimeout`.
45
+
3
46
  ## 0.6.1
4
47
 
5
48
  ### Patch Changes
package/acps/acp.ts ADDED
@@ -0,0 +1,411 @@
1
+ import { keccak256, toHex, zeroAddress, parseAbi, type Hex, type PublicClient, type WalletClient } from 'viem';
2
+ import {
3
+ type ACP,
4
+ type SelfACP,
5
+ type SharingACP,
6
+ type RecipientACP,
7
+ type CreateSelfACPOptions,
8
+ type CreateSharingACPOptions,
9
+ type ImportSharedACPOptions,
10
+ type SharedACP,
11
+ type SerializedACP,
12
+ type EIP712Domain,
13
+ type ACPPublic,
14
+ type EthEncryptedData,
15
+ type ACPHashFields,
16
+ } from './types.js';
17
+ import {
18
+ validateSelfACPOptions,
19
+ validateSharingACPOptions,
20
+ validateImportACPOptions,
21
+ validateSelfACP,
22
+ validateSharingACP,
23
+ validateImportACP,
24
+ ValidationUtils,
25
+ } from './validation.js';
26
+ import { SignatureUtils } from './signature.js';
27
+ import { GenerateSealingKey, unsealWithPrivateKey } from './sealing.js';
28
+ import { checkACPValidityOnChain, getAclEIP712Domain } from './onchain-utils.js';
29
+
30
+ /**
31
+ * Main ACP utilities - functional approach for React compatibility
32
+ */
33
+ export const ACPUtils = {
34
+ /**
35
+ * Create a self acp for personal use
36
+ */
37
+ createSelf: (options: CreateSelfACPOptions): SelfACP => {
38
+ const validation = validateSelfACPOptions(options);
39
+
40
+ // Always generate a new sealing key - users cannot provide their own
41
+ const sealingPair = GenerateSealingKey();
42
+
43
+ const acp = {
44
+ hash: ACPUtils.getHash(validation),
45
+ ...validation,
46
+ sealingPrivateKey: sealingPair.privateKey,
47
+ sealingKey: sealingPair.publicKey,
48
+ _signedDomain: undefined,
49
+ } satisfies SelfACP;
50
+
51
+ return acp;
52
+ },
53
+
54
+ /**
55
+ * Create a sharing acp to be shared with another user
56
+ */
57
+ createSharing: (options: CreateSharingACPOptions): SharingACP => {
58
+ const validation = validateSharingACPOptions(options);
59
+
60
+ // Always generate a new sealing key - users cannot provide their own
61
+ const sealingPair = GenerateSealingKey();
62
+
63
+ const acp = {
64
+ hash: ACPUtils.getHash(validation),
65
+ ...validation,
66
+ sealingPrivateKey: sealingPair.privateKey,
67
+ sealingKey: sealingPair.publicKey,
68
+ _signedDomain: undefined,
69
+ } satisfies SharingACP;
70
+
71
+ return acp;
72
+ },
73
+
74
+ /**
75
+ * Import a shared acp from various input formats
76
+ */
77
+ importShared: (options: ImportSharedACPOptions | string): RecipientACP => {
78
+ let parsedOptions: ImportSharedACPOptions;
79
+
80
+ // Handle different input types
81
+ if (typeof options === 'string') {
82
+ // Parse JSON string
83
+ try {
84
+ parsedOptions = JSON.parse(options);
85
+ } catch (error) {
86
+ throw new Error(`Failed to parse JSON string: ${error}`);
87
+ }
88
+ } else if (typeof options === 'object' && options !== null) {
89
+ // Handle both ImportSharedACPOptions and any object
90
+ parsedOptions = options;
91
+ } else {
92
+ throw new Error('Invalid input type, expected ImportSharedACPOptions, object, or string');
93
+ }
94
+
95
+ // Validate type if provided
96
+ if (parsedOptions.type != null && parsedOptions.type !== 'sharing') {
97
+ throw new Error(`Invalid acp type <${parsedOptions.type}>, must be "sharing"`);
98
+ }
99
+
100
+ const validation = validateImportACPOptions({ ...parsedOptions, type: 'recipient' });
101
+
102
+ // Always generate a new sealing key - users cannot provide their own
103
+ const sealingPair = GenerateSealingKey();
104
+
105
+ const acp = {
106
+ hash: ACPUtils.getHash(validation),
107
+ ...validation,
108
+ sealingPrivateKey: sealingPair.privateKey,
109
+ sealingKey: sealingPair.publicKey,
110
+ _signedDomain: undefined,
111
+ } satisfies RecipientACP;
112
+
113
+ return acp;
114
+ },
115
+
116
+ /**
117
+ * Sign an ACP with the provided wallet client
118
+ */
119
+ sign: async <T extends ACP>(acp: T, publicClient: PublicClient, walletClient: WalletClient): Promise<T> => {
120
+ if (walletClient == null || walletClient.account == null) {
121
+ throw new Error(
122
+ 'Missing walletClient, you must pass in a `walletClient` for the connected user to create an ACP signature'
123
+ );
124
+ }
125
+
126
+ const primaryType = SignatureUtils.getPrimaryType(acp.type);
127
+ const domain = await getAclEIP712Domain(publicClient);
128
+ const { types, message } = SignatureUtils.getSignatureParams(ACPUtils.getPublic(acp, true), primaryType);
129
+
130
+ const signature = await walletClient.signTypedData({
131
+ domain,
132
+ types,
133
+ primaryType,
134
+ message,
135
+ account: walletClient.account,
136
+ });
137
+
138
+ let updatedACP: ACP;
139
+ if (acp.type === 'self' || acp.type === 'sharing') {
140
+ updatedACP = {
141
+ ...acp,
142
+ issuerSignature: signature,
143
+ _signedDomain: domain,
144
+ };
145
+ } else {
146
+ updatedACP = {
147
+ ...acp,
148
+ recipientSignature: signature,
149
+ _signedDomain: domain,
150
+ };
151
+ }
152
+
153
+ return updatedACP as T;
154
+ },
155
+
156
+ /**
157
+ * Create and sign a self acp in one operation
158
+ */
159
+ createSelfAndSign: async (
160
+ options: CreateSelfACPOptions,
161
+ publicClient: PublicClient,
162
+ walletClient: WalletClient
163
+ ): Promise<SelfACP> => {
164
+ const acp = ACPUtils.createSelf(options);
165
+ return ACPUtils.sign(acp, publicClient, walletClient);
166
+ },
167
+
168
+ /**
169
+ * Create and sign a sharing acp in one operation
170
+ */
171
+ createSharingAndSign: async (
172
+ options: CreateSharingACPOptions,
173
+ publicClient: PublicClient,
174
+ walletClient: WalletClient
175
+ ): Promise<SharingACP> => {
176
+ const acp = ACPUtils.createSharing(options);
177
+ return ACPUtils.sign(acp, publicClient, walletClient);
178
+ },
179
+
180
+ /**
181
+ * Import and sign a shared acp in one operation from various input formats
182
+ */
183
+ importSharedAndSign: async (
184
+ options: ImportSharedACPOptions | string,
185
+ publicClient: PublicClient,
186
+ walletClient: WalletClient
187
+ ): Promise<RecipientACP> => {
188
+ const acp = ACPUtils.importShared(options);
189
+ return ACPUtils.sign(acp, publicClient, walletClient);
190
+ },
191
+
192
+ /**
193
+ * Deserialize an ACP from serialized data
194
+ */
195
+ deserialize: (data: SerializedACP): ACP => {
196
+ return { ...data };
197
+ },
198
+
199
+ /**
200
+ * Serialize an ACP for storage
201
+ */
202
+ serialize: (acp: ACP): SerializedACP => {
203
+ return {
204
+ hash: acp.hash,
205
+ name: acp.name,
206
+ type: acp.type,
207
+ issuer: acp.issuer,
208
+ expiration: acp.expiration,
209
+ recipient: acp.recipient,
210
+ revokerData: acp.revokerData,
211
+ revokerContract: acp.revokerContract,
212
+ scope: acp.scope,
213
+ contracts: acp.contracts,
214
+ handles: acp.handles,
215
+ sealingKey: acp.sealingKey,
216
+ issuerSignature: acp.issuerSignature,
217
+ recipientSignature: acp.recipientSignature,
218
+ _signedDomain: acp._signedDomain,
219
+ sealingPrivateKey: acp.sealingPrivateKey,
220
+ };
221
+ },
222
+
223
+ /**
224
+ * Validate an ACP (schema-level validation)
225
+ */
226
+ validateSchema: (acp: ACP) => {
227
+ if (acp.type === 'self') {
228
+ return validateSelfACP(acp);
229
+ } else if (acp.type === 'sharing') {
230
+ return validateSharingACP(acp);
231
+ } else if (acp.type === 'recipient') {
232
+ return validateImportACP(acp);
233
+ } else {
234
+ throw new Error('Invalid ACP type');
235
+ }
236
+ },
237
+
238
+ /**
239
+ * Validate an ACP (holistic validation).
240
+ *
241
+ * This validates:
242
+ * - ACP schema (shape + invariants)
243
+ * - ACP is signed
244
+ * - ACP is not expired
245
+ *
246
+ * For schema-only validation, use `validateSchema(acp)`.
247
+ */
248
+ validate: (acp: ACP) => {
249
+ const validated = ACPUtils.validateSchema(acp);
250
+ ValidationUtils.assertSignedAndNotExpired(validated as ACP);
251
+ return validated;
252
+ },
253
+
254
+ /**
255
+ * Get the public component of an ACP — the signed struct sent on-chain / to the decryption backend.
256
+ * Strips the private component (hash, name, type, sealing pair).
257
+ */
258
+ getPublic: (acp: ACP, skipValidation = false): ACPPublic => {
259
+ if (!skipValidation) {
260
+ ACPUtils.validateSchema(acp);
261
+ }
262
+
263
+ return {
264
+ issuer: acp.issuer,
265
+ expiration: acp.expiration,
266
+ recipient: acp.recipient,
267
+ revokerData: acp.revokerData,
268
+ revokerContract: acp.revokerContract,
269
+ scope: acp.scope,
270
+ contracts: acp.contracts,
271
+ handles: acp.handles,
272
+ sealingKey: acp.sealingKey,
273
+ issuerSignature: acp.issuerSignature,
274
+ recipientSignature: acp.recipientSignature,
275
+ };
276
+ },
277
+
278
+ /**
279
+ * Get a stable hash for the acp (used as key in storage)
280
+ */
281
+ getHash: (acp: ACPHashFields): string => {
282
+ const data = JSON.stringify({
283
+ type: acp.type,
284
+ issuer: acp.issuer,
285
+ expiration: acp.expiration,
286
+ recipient: acp.recipient,
287
+ revokerData: acp.revokerData,
288
+ revokerContract: acp.revokerContract,
289
+ scope: acp.scope,
290
+ contracts: acp.contracts,
291
+ handles: acp.handles,
292
+ });
293
+ return keccak256(toHex(data));
294
+ },
295
+
296
+ /**
297
+ * Export acp data for sharing (strips the private component).
298
+ * Fixed `SharedACP` shape — every field always present, aligned with
299
+ * `ACPPublic` and the on-chain sharing payload.
300
+ */
301
+ export: (acp: ACP): string => {
302
+ if (acp.type !== 'sharing') {
303
+ throw new Error(
304
+ `Cannot export a '${acp.type}' ACP — only 'sharing' ACPs are exportable. The export includes the issuer signature.`
305
+ );
306
+ }
307
+ if (acp.issuerSignature === '0x') {
308
+ throw new Error(
309
+ 'Cannot export an unsigned sharing ACP — sign it first (the recipient needs the issuer signature).'
310
+ );
311
+ }
312
+
313
+ const shared: SharedACP = {
314
+ name: acp.name,
315
+ type: acp.type,
316
+ issuer: acp.issuer,
317
+ expiration: acp.expiration,
318
+ recipient: acp.recipient,
319
+ revokerData: acp.revokerData,
320
+ revokerContract: acp.revokerContract,
321
+ scope: acp.scope,
322
+ contracts: acp.contracts,
323
+ handles: acp.handles,
324
+ issuerSignature: acp.issuerSignature,
325
+ };
326
+
327
+ return JSON.stringify(shared, undefined, 2);
328
+ },
329
+
330
+ /**
331
+ * Unseal encrypted data using the acp's sealing key
332
+ */
333
+ unseal: (acp: ACP, ciphertext: EthEncryptedData): bigint => {
334
+ return unsealWithPrivateKey(acp.sealingPrivateKey, ciphertext);
335
+ },
336
+
337
+ /**
338
+ * Check if acp is expired
339
+ */
340
+ isExpired: (acp: ACP): boolean => {
341
+ return ValidationUtils.isExpired(acp);
342
+ },
343
+
344
+ /**
345
+ * Check if acp is signed
346
+ */
347
+ isSigned: (acp: ACP): boolean => {
348
+ return ValidationUtils.isSigned(acp);
349
+ },
350
+
351
+ /**
352
+ * Check if acp is signed and not expired
353
+ */
354
+ isSignedAndNotExpired: (acp: ACP) => {
355
+ return ValidationUtils.isSignedAndNotExpired(acp);
356
+ },
357
+
358
+ /**
359
+ * Assert that acp is signed and not expired
360
+ */
361
+ assertSignedAndNotExpired: (acp: ACP): void => {
362
+ return ValidationUtils.assertSignedAndNotExpired(acp);
363
+ },
364
+
365
+ isValid: (acp: ACP) => {
366
+ return ValidationUtils.isValid(acp);
367
+ },
368
+
369
+ /**
370
+ * Update acp name (returns new acp instance)
371
+ */
372
+ updateName: (acp: ACP, name: string): ACP => {
373
+ return { ...acp, name };
374
+ },
375
+
376
+ /**
377
+ * Fetch EIP712 domain from the blockchain
378
+ */
379
+ fetchEIP712Domain: async (publicClient: PublicClient): Promise<EIP712Domain> => {
380
+ return getAclEIP712Domain(publicClient);
381
+ },
382
+
383
+ /**
384
+ * Check if acp's signed domain matches the provided domain
385
+ */
386
+ matchesDomain: (acp: ACP, domain: EIP712Domain): boolean => {
387
+ return (
388
+ acp._signedDomain?.name === domain.name &&
389
+ acp._signedDomain?.version === domain.version &&
390
+ acp._signedDomain?.verifyingContract === domain.verifyingContract &&
391
+ acp._signedDomain?.chainId === domain.chainId
392
+ );
393
+ },
394
+
395
+ /**
396
+ * Check if acp's signed domain is valid for the current chain
397
+ */
398
+ checkSignedDomainValid: async (acp: ACP, publicClient: PublicClient): Promise<boolean> => {
399
+ if (acp._signedDomain == null) return false;
400
+ const domain = await getAclEIP712Domain(publicClient);
401
+ return ACPUtils.matchesDomain(acp, domain);
402
+ },
403
+
404
+ /**
405
+ * Check if acp passes the on-chain validation
406
+ */
407
+ checkValidityOnChain: async (acp: ACP, publicClient: PublicClient): Promise<boolean> => {
408
+ const publicAcp = ACPUtils.getPublic(acp);
409
+ return checkACPValidityOnChain(publicAcp, publicClient);
410
+ },
411
+ };
package/acps/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ // Core types
2
+ export type {
3
+ ACP,
4
+ CreateSelfACPOptions as SelfACPOptions,
5
+ CreateSharingACPOptions as SharingACPOptions,
6
+ ImportSharedACPOptions as ImportACPOptions,
7
+ SerializedACP,
8
+ ACPMetadata,
9
+ ACPPublic,
10
+ SharedACP,
11
+ IncomingShare,
12
+ EIP712Domain,
13
+ EIP712Types,
14
+ EIP712Message,
15
+ ValidationResult,
16
+ ACPSignaturePrimaryType,
17
+ } from './types.js';
18
+
19
+ // Main utilities
20
+ export { ACPUtils } from './acp.js';
21
+
22
+ // Validation utilities
23
+ export {
24
+ // Self ACP validators
25
+ SelfACPOptionsValidator,
26
+ SelfACPValidator,
27
+ validateSelfACPOptions,
28
+ validateSelfACP,
29
+ // Sharing ACP validators
30
+ SharingACPOptionsValidator,
31
+ SharingACPValidator,
32
+ validateSharingACPOptions,
33
+ validateSharingACP,
34
+ // Import ACP validators
35
+ ImportACPOptionsValidator,
36
+ ImportACPValidator,
37
+ validateImportACPOptions,
38
+ validateImportACP,
39
+ // Common utilities
40
+ ValidationUtils,
41
+ } from './validation.js';
42
+
43
+ // Signature utilities
44
+ export { SignatureUtils, getSignatureTypesAndMessage, SignatureTypes } from './signature.js';
45
+
46
+ // Storage utilities
47
+ export {
48
+ acpStore,
49
+ getACP,
50
+ getActiveACP,
51
+ getACPs,
52
+ setACP,
53
+ removeACP,
54
+ getActiveACPHash,
55
+ setActiveACPHash,
56
+ removeActiveACPHash,
57
+ clearStaleStore,
58
+ ACP_STORE_DEFAULTS,
59
+ } from './store.js';
60
+
61
+ // Sealing utilities
62
+ export { GenerateSealingKey, seal, unsealWithPrivateKey, type SealingKeyPair } from './sealing.js';
63
+ export type { EthEncryptedData } from './sealing.js';
64
+
65
+ // Re-export everything for convenience
66
+ export * from './types.js';
67
+ export * from './acp.js';
68
+ export * from './validation.js';
69
+ export * from './signature.js';
70
+ export * from './store.js';
@@ -6,7 +6,7 @@ import {
6
6
  decodeErrorResult,
7
7
  parseAbi,
8
8
  } from 'viem';
9
- import type { EIP712Domain, Permission } from './types';
9
+ import type { EIP712Domain, ACPPublic } from './types';
10
10
  import { TASK_MANAGER_ADDRESS } from '../core/consts.js';
11
11
 
12
12
  export const getAclAddress = async (publicClient: PublicClient): Promise<Hex> => {
@@ -23,6 +23,10 @@ export const getAclAddress = async (publicClient: PublicClient): Promise<Hex> =>
23
23
  })) as `0x${string}`;
24
24
  };
25
25
 
26
+ /**
27
+ * ACP (ACP V3) signing domain — fetched from the ACL contract, which since V3
28
+ * carries the ACP verification logic and signs as name "ACL", version "2".
29
+ */
26
30
  export const getAclEIP712Domain = async (publicClient: PublicClient): Promise<EIP712Domain> => {
27
31
  const aclAddress = await getAclAddress(publicClient);
28
32
  const EIP712_DOMAIN_IFACE =
@@ -41,6 +45,12 @@ export const getAclEIP712Domain = async (publicClient: PublicClient): Promise<EI
41
45
  // eslint-disable-next-line no-unused-vars
42
46
  const [_fields, name, version, chainId, verifyingContract, _salt, _extensions] = domain;
43
47
 
48
+ if (version !== '2') {
49
+ throw new Error(
50
+ `Chain ${chainId}'s ACL serves EIP-712 domain version "${version}" — this SDK requires the upgraded (ACP-era) ACL, which signs as version "2". Pre-upgrade (V2 Permission) chains are not supported.`
51
+ );
52
+ }
53
+
44
54
  return {
45
55
  name,
46
56
  version,
@@ -49,28 +59,28 @@ export const getAclEIP712Domain = async (publicClient: PublicClient): Promise<EI
49
59
  };
50
60
  };
51
61
 
52
- export const checkPermitValidityOnChain = async (
53
- permission: Permission,
54
- publicClient: PublicClient
55
- ): Promise<boolean> => {
62
+ export const checkACPValidityOnChain = async (acp: ACPPublic, publicClient: PublicClient): Promise<boolean> => {
56
63
  const aclAddress = await getAclAddress(publicClient);
57
64
 
58
- // Check if the permit is valid
65
+ // Check if the acp is valid (structure: expiration / signatures / revocation)
59
66
  try {
60
67
  await publicClient.simulateContract({
61
68
  address: aclAddress,
62
- abi: checkPermitValidityAbi,
63
- functionName: 'checkPermitValidity',
69
+ abi: checkACPValidityAbi,
70
+ functionName: 'checkPermissionValidity',
64
71
  args: [
65
72
  {
66
- issuer: permission.issuer,
67
- expiration: BigInt(permission.expiration),
68
- recipient: permission.recipient,
69
- validatorId: BigInt(permission.validatorId),
70
- validatorContract: permission.validatorContract,
71
- sealingKey: permission.sealingKey,
72
- issuerSignature: permission.issuerSignature,
73
- recipientSignature: permission.recipientSignature,
73
+ issuer: acp.issuer,
74
+ expiration: BigInt(acp.expiration),
75
+ recipient: acp.recipient,
76
+ revokerData: BigInt(acp.revokerData),
77
+ revokerContract: acp.revokerContract,
78
+ scope: acp.scope,
79
+ contracts: acp.contracts,
80
+ handles: acp.handles,
81
+ sealingKey: acp.sealingKey,
82
+ issuerSignature: acp.issuerSignature,
83
+ recipientSignature: acp.recipientSignature,
74
84
  },
75
85
  ],
76
86
  });
@@ -86,7 +96,7 @@ export const checkPermitValidityOnChain = async (
86
96
  }
87
97
 
88
98
  // Check details field for custom error names (e.g., from Hardhat test nodes)
89
- const customErrorName = extractCustomErrorFromDetails(err, checkPermitValidityAbi);
99
+ const customErrorName = extractCustomErrorFromDetails(err, checkACPValidityAbi);
90
100
  if (customErrorName) {
91
101
  throw new Error(customErrorName);
92
102
  }
@@ -95,7 +105,7 @@ export const checkPermitValidityOnChain = async (
95
105
  const hhDetailsData = extractReturnData(err);
96
106
  if (hhDetailsData != null) {
97
107
  const decoded = decodeErrorResult({
98
- abi: checkPermitValidityAbi,
108
+ abi: checkACPValidityAbi,
99
109
  data: hhDetailsData,
100
110
  });
101
111
 
@@ -136,15 +146,15 @@ function extractReturnData(err: unknown): `0x${string}` | undefined {
136
146
  return s.match(/return data:\s*(0x[a-fA-F0-9]+)/)?.[1] as `0x${string}` | undefined;
137
147
  }
138
148
 
139
- const checkPermitValidityAbi = [
149
+ const checkACPValidityAbi = [
140
150
  {
141
151
  type: 'function',
142
- name: 'checkPermitValidity',
152
+ name: 'checkPermissionValidity',
143
153
  inputs: [
144
154
  {
145
- name: 'permission',
155
+ name: 'acp',
146
156
  type: 'tuple',
147
- internalType: 'struct Permission',
157
+ internalType: 'struct ACP',
148
158
  components: [
149
159
  {
150
160
  name: 'issuer',
@@ -162,15 +172,30 @@ const checkPermitValidityAbi = [
162
172
  internalType: 'address',
163
173
  },
164
174
  {
165
- name: 'validatorId',
175
+ name: 'revokerData',
166
176
  type: 'uint256',
167
177
  internalType: 'uint256',
168
178
  },
169
179
  {
170
- name: 'validatorContract',
180
+ name: 'revokerContract',
171
181
  type: 'address',
172
182
  internalType: 'address',
173
183
  },
184
+ {
185
+ name: 'scope',
186
+ type: 'uint8',
187
+ internalType: 'uint8',
188
+ },
189
+ {
190
+ name: 'contracts',
191
+ type: 'address[]',
192
+ internalType: 'address[]',
193
+ },
194
+ {
195
+ name: 'handles',
196
+ type: 'bytes32[]',
197
+ internalType: 'bytes32[]',
198
+ },
174
199
  {
175
200
  name: 'sealingKey',
176
201
  type: 'bytes32',