@cofhe/sdk 0.0.0-beta-20251027110729

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 (110) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/adapters/ethers5.test.ts +174 -0
  3. package/adapters/ethers5.ts +36 -0
  4. package/adapters/ethers6.test.ts +169 -0
  5. package/adapters/ethers6.ts +36 -0
  6. package/adapters/hardhat-node.ts +167 -0
  7. package/adapters/hardhat.hh2.test.ts +159 -0
  8. package/adapters/hardhat.ts +37 -0
  9. package/adapters/index.test.ts +25 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +91 -0
  12. package/adapters/test-utils.ts +53 -0
  13. package/adapters/types.ts +6 -0
  14. package/adapters/wagmi.test.ts +156 -0
  15. package/adapters/wagmi.ts +17 -0
  16. package/chains/chains/arbSepolia.ts +14 -0
  17. package/chains/chains/baseSepolia.ts +14 -0
  18. package/chains/chains/hardhat.ts +15 -0
  19. package/chains/chains/sepolia.ts +14 -0
  20. package/chains/chains.test.ts +49 -0
  21. package/chains/defineChain.ts +18 -0
  22. package/chains/index.ts +33 -0
  23. package/chains/types.ts +32 -0
  24. package/core/baseBuilder.ts +138 -0
  25. package/core/client.test.ts +298 -0
  26. package/core/client.ts +308 -0
  27. package/core/config.test.ts +224 -0
  28. package/core/config.ts +213 -0
  29. package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
  30. package/core/decrypt/cofheMocksSealOutput.ts +57 -0
  31. package/core/decrypt/decryptHandleBuilder.ts +281 -0
  32. package/core/decrypt/decryptUtils.ts +28 -0
  33. package/core/decrypt/tnSealOutput.ts +59 -0
  34. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  35. package/core/encrypt/cofheMocksZkVerifySign.ts +278 -0
  36. package/core/encrypt/encryptInputsBuilder.test.ts +735 -0
  37. package/core/encrypt/encryptInputsBuilder.ts +512 -0
  38. package/core/encrypt/encryptUtils.ts +64 -0
  39. package/core/encrypt/zkPackProveVerify.ts +273 -0
  40. package/core/error.ts +170 -0
  41. package/core/fetchKeys.test.ts +212 -0
  42. package/core/fetchKeys.ts +170 -0
  43. package/core/index.ts +77 -0
  44. package/core/keyStore.test.ts +226 -0
  45. package/core/keyStore.ts +127 -0
  46. package/core/permits.test.ts +242 -0
  47. package/core/permits.ts +136 -0
  48. package/core/result.test.ts +180 -0
  49. package/core/result.ts +67 -0
  50. package/core/test-utils.ts +45 -0
  51. package/core/types.ts +352 -0
  52. package/core/utils.ts +88 -0
  53. package/dist/adapters.cjs +88 -0
  54. package/dist/adapters.d.cts +14558 -0
  55. package/dist/adapters.d.ts +14558 -0
  56. package/dist/adapters.js +83 -0
  57. package/dist/chains.cjs +101 -0
  58. package/dist/chains.d.cts +99 -0
  59. package/dist/chains.d.ts +99 -0
  60. package/dist/chains.js +1 -0
  61. package/dist/chunk-GZCQQYVI.js +93 -0
  62. package/dist/chunk-KFGPTJ6X.js +2295 -0
  63. package/dist/chunk-LU7BMUUT.js +804 -0
  64. package/dist/core.cjs +3174 -0
  65. package/dist/core.d.cts +16 -0
  66. package/dist/core.d.ts +16 -0
  67. package/dist/core.js +3 -0
  68. package/dist/node.cjs +3090 -0
  69. package/dist/node.d.cts +22 -0
  70. package/dist/node.d.ts +22 -0
  71. package/dist/node.js +90 -0
  72. package/dist/permit-S9CnI6MF.d.cts +333 -0
  73. package/dist/permit-S9CnI6MF.d.ts +333 -0
  74. package/dist/permits.cjs +856 -0
  75. package/dist/permits.d.cts +1056 -0
  76. package/dist/permits.d.ts +1056 -0
  77. package/dist/permits.js +1 -0
  78. package/dist/types-KImPrEIe.d.cts +48 -0
  79. package/dist/types-KImPrEIe.d.ts +48 -0
  80. package/dist/types-PhwGgQvs.d.ts +953 -0
  81. package/dist/types-bB7wLj0q.d.cts +953 -0
  82. package/dist/web.cjs +3067 -0
  83. package/dist/web.d.cts +22 -0
  84. package/dist/web.d.ts +22 -0
  85. package/dist/web.js +64 -0
  86. package/node/client.test.ts +152 -0
  87. package/node/config.test.ts +68 -0
  88. package/node/encryptInputs.test.ts +175 -0
  89. package/node/index.ts +96 -0
  90. package/node/storage.ts +51 -0
  91. package/package.json +120 -0
  92. package/permits/index.ts +67 -0
  93. package/permits/localstorage.test.ts +118 -0
  94. package/permits/permit.test.ts +474 -0
  95. package/permits/permit.ts +396 -0
  96. package/permits/sealing.test.ts +84 -0
  97. package/permits/sealing.ts +131 -0
  98. package/permits/signature.ts +79 -0
  99. package/permits/store.test.ts +128 -0
  100. package/permits/store.ts +168 -0
  101. package/permits/test-utils.ts +20 -0
  102. package/permits/types.ts +174 -0
  103. package/permits/utils.ts +63 -0
  104. package/permits/validation.test.ts +288 -0
  105. package/permits/validation.ts +349 -0
  106. package/web/client.web.test.ts +152 -0
  107. package/web/config.web.test.ts +71 -0
  108. package/web/encryptInputs.web.test.ts +195 -0
  109. package/web/index.ts +97 -0
  110. package/web/storage.ts +20 -0
package/core/config.ts ADDED
@@ -0,0 +1,213 @@
1
+ import { type CofheChain } from '@/chains';
2
+
3
+ import { z } from 'zod';
4
+ import { type WalletClient } from 'viem';
5
+ import { CofhesdkError, CofhesdkErrorCode } from './error.js';
6
+ import { type IStorage } from './types.js';
7
+
8
+ /**
9
+ * Usable config type inferred from the schema
10
+ */
11
+ export type CofhesdkConfig = {
12
+ supportedChains: CofheChain[];
13
+ /**
14
+ * Strategy for fetching FHE keys
15
+ * - CONNECTED_CHAIN: Fetch keys for the connected chain (provided by the publicClient)
16
+ * - SUPPORTED_CHAINS: Fetch keys for all supported chains (provided by the supportedChains config)
17
+ * - OFF: Do not fetch keys (fetching occurs during encryptInputs)
18
+ * */
19
+ fheKeysPrefetching: 'CONNECTED_CHAIN' | 'SUPPORTED_CHAINS' | 'OFF';
20
+ /**
21
+ * How permits are generated
22
+ * - ON_CONNECT: Generate a permit when client.connect() is called
23
+ * - ON_DECRYPT_HANDLES: Generate a permit when client.decryptHandles() is called
24
+ * - MANUAL: Generate a permit manually using client.generatePermit()
25
+ */
26
+ permitGeneration: 'ON_CONNECT' | 'ON_DECRYPT_HANDLES' | 'MANUAL';
27
+ /** Default permit expiration in seconds, default is 30 days */
28
+ defaultPermitExpiration: number;
29
+ /**
30
+ * Storage scheme for the fetched fhe keys
31
+ * FHE keys are large, and caching prevents re-fetching them on each encryptInputs call
32
+ * (defaults to indexedDB on web, filesystem on node)
33
+ */
34
+ fheKeyStorage: IStorage | null;
35
+ /** Mocks configs */
36
+ mocks: {
37
+ /**
38
+ * Length of the simulated seal output delay in milliseconds
39
+ * Default 1000ms on web
40
+ * Default 0ms on hardhat (will be called during tests no need for fake delay)
41
+ */
42
+ sealOutputDelay: number;
43
+ };
44
+ _internal?: CofhesdkInternalConfig;
45
+ };
46
+
47
+ export type CofhesdkInternalConfig = {
48
+ zkvWalletClient?: WalletClient;
49
+ };
50
+
51
+ /**
52
+ * Zod schema for configuration validation
53
+ */
54
+ export const CofhesdkConfigSchema = z.object({
55
+ /** List of supported chain configurations */
56
+ supportedChains: z.array(z.custom<CofheChain>()),
57
+ /** Strategy for fetching FHE keys */
58
+ fheKeysPrefetching: z.enum(['CONNECTED_CHAIN', 'SUPPORTED_CHAINS', 'OFF']).optional().default('OFF'),
59
+ /** How permits are generated */
60
+ permitGeneration: z.enum(['ON_CONNECT', 'ON_DECRYPT_HANDLES', 'MANUAL']).optional().default('ON_CONNECT'),
61
+ /** Default permit expiration in seconds, default is 30 days */
62
+ defaultPermitExpiration: z
63
+ .number()
64
+ .optional()
65
+ .default(60 * 60 * 24 * 30),
66
+ /** Storage method for fhe keys (defaults to indexedDB on web, filesystem on node) */
67
+ fheKeyStorage: z
68
+ .object({
69
+ getItem: z.function().args(z.string()).returns(z.promise(z.any())),
70
+ setItem: z.function().args(z.string(), z.any()).returns(z.promise(z.void())),
71
+ removeItem: z.function().args(z.string()).returns(z.promise(z.void())),
72
+ })
73
+ .or(z.null())
74
+ .default(null),
75
+ /** Mocks configs */
76
+ mocks: z
77
+ .object({
78
+ sealOutputDelay: z.number().optional().default(0),
79
+ })
80
+ .optional()
81
+ .default({ sealOutputDelay: 0 }),
82
+ /** Internal configuration */
83
+ _internal: z
84
+ .object({
85
+ zkvWalletClient: z.any().optional(),
86
+ })
87
+ .optional(),
88
+ });
89
+
90
+ /**
91
+ * Input config type inferred from the schema
92
+ */
93
+ export type CofhesdkInputConfig = z.input<typeof CofhesdkConfigSchema>;
94
+ /**
95
+ * Creates and validates a cofhesdk configuration (base implementation)
96
+ * @param config - The configuration object to validate
97
+ * @returns The validated configuration
98
+ * @throws {Error} If the configuration is invalid
99
+ */
100
+ export function createCofhesdkConfigBase(config: CofhesdkInputConfig): CofhesdkConfig {
101
+ const result = CofhesdkConfigSchema.safeParse(config);
102
+
103
+ if (!result.success) {
104
+ throw new Error(`Invalid cofhesdk configuration: ${result.error.message}`);
105
+ }
106
+
107
+ return result.data;
108
+ }
109
+
110
+ /**
111
+ * Access the CofhesdkConfig object directly by providing the key.
112
+ * This is powerful when you use OnchainKit utilities outside of the React context.
113
+ */
114
+ export const getCofhesdkConfigItem = <K extends keyof CofhesdkConfig>(
115
+ config: CofhesdkConfig,
116
+ key: K
117
+ ): CofhesdkConfig[K] => {
118
+ return config[key];
119
+ };
120
+
121
+ /**
122
+ * Gets a supported chain from config by chainId, throws if not found
123
+ * @param config - The cofhesdk configuration
124
+ * @param chainId - The chain ID to look up
125
+ * @returns The supported chain configuration
126
+ * @throws {CofhesdkError} If the chain is not found in the config
127
+ */
128
+ export function getSupportedChainOrThrow(config: CofhesdkConfig, chainId: number): CofheChain {
129
+ const supportedChain = config.supportedChains.find((chain) => chain.id === chainId);
130
+
131
+ if (!supportedChain) {
132
+ throw new CofhesdkError({
133
+ code: CofhesdkErrorCode.UnsupportedChain,
134
+ message: `Config does not support chain <${chainId}>`,
135
+ hint: 'Ensure config passed to client has been created with this chain in the config.supportedChains array.',
136
+ context: {
137
+ chainId,
138
+ supportedChainIds: config.supportedChains.map((c) => c.id),
139
+ },
140
+ });
141
+ }
142
+
143
+ return supportedChain;
144
+ }
145
+
146
+ /**
147
+ * Gets the CoFHE URL for a chain, throws if not found
148
+ * @param config - The cofhesdk configuration
149
+ * @param chainId - The chain ID to look up
150
+ * @returns The CoFHE URL for the chain
151
+ * @throws {CofhesdkError} If the chain or URL is not found
152
+ */
153
+ export function getCoFheUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
154
+ const supportedChain = getSupportedChainOrThrow(config, chainId);
155
+ const url = supportedChain.coFheUrl;
156
+
157
+ if (!url) {
158
+ throw new CofhesdkError({
159
+ code: CofhesdkErrorCode.MissingConfig,
160
+ message: `CoFHE URL is not configured for chain <${chainId}>`,
161
+ hint: 'Ensure this chain config includes a coFheUrl property.',
162
+ context: { chainId },
163
+ });
164
+ }
165
+
166
+ return url;
167
+ }
168
+
169
+ /**
170
+ * Gets the ZK verifier URL for a chain, throws if not found
171
+ * @param config - The cofhesdk configuration
172
+ * @param chainId - The chain ID to look up
173
+ * @returns The ZK verifier URL for the chain
174
+ * @throws {CofhesdkError} If the chain or URL is not found
175
+ */
176
+ export function getZkVerifierUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
177
+ const supportedChain = getSupportedChainOrThrow(config, chainId);
178
+ const url = supportedChain.verifierUrl;
179
+
180
+ if (!url) {
181
+ throw new CofhesdkError({
182
+ code: CofhesdkErrorCode.ZkVerifierUrlUninitialized,
183
+ message: `ZK verifier URL is not configured for chain <${chainId}>`,
184
+ hint: 'Ensure this chain config includes a verifierUrl property.',
185
+ context: { chainId },
186
+ });
187
+ }
188
+
189
+ return url;
190
+ }
191
+
192
+ /**
193
+ * Gets the threshold network URL for a chain, throws if not found
194
+ * @param config - The cofhesdk configuration
195
+ * @param chainId - The chain ID to look up
196
+ * @returns The threshold network URL for the chain
197
+ * @throws {CofhesdkError} If the chain or URL is not found
198
+ */
199
+ export function getThresholdNetworkUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
200
+ const supportedChain = getSupportedChainOrThrow(config, chainId);
201
+ const url = supportedChain.thresholdNetworkUrl;
202
+
203
+ if (!url) {
204
+ throw new CofhesdkError({
205
+ code: CofhesdkErrorCode.ThresholdNetworkUrlUninitialized,
206
+ message: `Threshold network URL is not configured for chain <${chainId}>`,
207
+ hint: 'Ensure this chain config includes a thresholdNetworkUrl property.',
208
+ context: { chainId },
209
+ });
210
+ }
211
+
212
+ return url;
213
+ }
@@ -0,0 +1,129 @@
1
+ export const MockQueryDecrypterAbi = [
2
+ {
3
+ type: 'function',
4
+ name: 'acl',
5
+ inputs: [],
6
+ outputs: [{ name: '', type: 'address', internalType: 'contract ACL' }],
7
+ stateMutability: 'view',
8
+ },
9
+ {
10
+ type: 'function',
11
+ name: 'decodeLowLevelReversion',
12
+ inputs: [{ name: 'data', type: 'bytes', internalType: 'bytes' }],
13
+ outputs: [{ name: 'error', type: 'string', internalType: 'string' }],
14
+ stateMutability: 'pure',
15
+ },
16
+ {
17
+ type: 'function',
18
+ name: 'exists',
19
+ inputs: [],
20
+ outputs: [{ name: '', type: 'bool', internalType: 'bool' }],
21
+ stateMutability: 'pure',
22
+ },
23
+ {
24
+ type: 'function',
25
+ name: 'initialize',
26
+ inputs: [
27
+ { name: '_taskManager', type: 'address', internalType: 'address' },
28
+ { name: '_acl', type: 'address', internalType: 'address' },
29
+ ],
30
+ outputs: [],
31
+ stateMutability: 'nonpayable',
32
+ },
33
+ {
34
+ type: 'function',
35
+ name: 'queryDecrypt',
36
+ inputs: [
37
+ { name: 'ctHash', type: 'uint256', internalType: 'uint256' },
38
+ { name: '', type: 'uint256', internalType: 'uint256' },
39
+ {
40
+ name: 'permission',
41
+ type: 'tuple',
42
+ internalType: 'struct Permission',
43
+ components: [
44
+ { name: 'issuer', type: 'address', internalType: 'address' },
45
+ { name: 'expiration', type: 'uint64', internalType: 'uint64' },
46
+ { name: 'recipient', type: 'address', internalType: 'address' },
47
+ { name: 'validatorId', type: 'uint256', internalType: 'uint256' },
48
+ {
49
+ name: 'validatorContract',
50
+ type: 'address',
51
+ internalType: 'address',
52
+ },
53
+ { name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
54
+ { name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
55
+ { name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
56
+ ],
57
+ },
58
+ ],
59
+ outputs: [
60
+ { name: 'allowed', type: 'bool', internalType: 'bool' },
61
+ { name: 'error', type: 'string', internalType: 'string' },
62
+ { name: '', type: 'uint256', internalType: 'uint256' },
63
+ ],
64
+ stateMutability: 'view',
65
+ },
66
+ {
67
+ type: 'function',
68
+ name: 'querySealOutput',
69
+ inputs: [
70
+ { name: 'ctHash', type: 'uint256', internalType: 'uint256' },
71
+ { name: '', type: 'uint256', internalType: 'uint256' },
72
+ {
73
+ name: 'permission',
74
+ type: 'tuple',
75
+ internalType: 'struct Permission',
76
+ components: [
77
+ { name: 'issuer', type: 'address', internalType: 'address' },
78
+ { name: 'expiration', type: 'uint64', internalType: 'uint64' },
79
+ { name: 'recipient', type: 'address', internalType: 'address' },
80
+ { name: 'validatorId', type: 'uint256', internalType: 'uint256' },
81
+ {
82
+ name: 'validatorContract',
83
+ type: 'address',
84
+ internalType: 'address',
85
+ },
86
+ { name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
87
+ { name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
88
+ { name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
89
+ ],
90
+ },
91
+ ],
92
+ outputs: [
93
+ { name: 'allowed', type: 'bool', internalType: 'bool' },
94
+ { name: 'error', type: 'string', internalType: 'string' },
95
+ { name: '', type: 'bytes32', internalType: 'bytes32' },
96
+ ],
97
+ stateMutability: 'view',
98
+ },
99
+ {
100
+ type: 'function',
101
+ name: 'seal',
102
+ inputs: [
103
+ { name: 'input', type: 'uint256', internalType: 'uint256' },
104
+ { name: 'key', type: 'bytes32', internalType: 'bytes32' },
105
+ ],
106
+ outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }],
107
+ stateMutability: 'pure',
108
+ },
109
+ {
110
+ type: 'function',
111
+ name: 'taskManager',
112
+ inputs: [],
113
+ outputs: [{ name: '', type: 'address', internalType: 'contract TaskManager' }],
114
+ stateMutability: 'view',
115
+ },
116
+ {
117
+ type: 'function',
118
+ name: 'unseal',
119
+ inputs: [
120
+ { name: 'hashed', type: 'bytes32', internalType: 'bytes32' },
121
+ { name: 'key', type: 'bytes32', internalType: 'bytes32' },
122
+ ],
123
+ outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }],
124
+ stateMutability: 'pure',
125
+ },
126
+ { type: 'error', name: 'NotAllowed', inputs: [] },
127
+ { type: 'error', name: 'SealingKeyInvalid', inputs: [] },
128
+ { type: 'error', name: 'SealingKeyMissing', inputs: [] },
129
+ ] as const;
@@ -0,0 +1,57 @@
1
+ import { type Permit, PermitUtils } from '@/permits';
2
+
3
+ import { type PublicClient } from 'viem';
4
+ import { sleep } from '../utils.js';
5
+ import { MockQueryDecrypterAbi } from './MockQueryDecrypterAbi.js';
6
+ import { FheTypes } from '../types.js';
7
+ import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
8
+
9
+ // Address the Mock Query Decrypter contract is deployed to on the Hardhat chain
10
+ export const MockQueryDecrypterAddress = '0x0000000000000000000000000000000000000200';
11
+
12
+ export async function cofheMocksSealOutput(
13
+ ctHash: bigint,
14
+ utype: FheTypes,
15
+ permit: Permit,
16
+ publicClient: PublicClient,
17
+ mocksSealOutputDelay: number
18
+ ): Promise<bigint> {
19
+ // Configurable delay before decrypting the output to simulate the CoFHE decrypt processing time
20
+ // Recommended 1000ms on web
21
+ // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
22
+ if (mocksSealOutputDelay > 0) await sleep(mocksSealOutputDelay);
23
+
24
+ const permission = PermitUtils.getPermission(permit, true);
25
+ const permissionWithBigInts = {
26
+ ...permission,
27
+ expiration: BigInt(permission.expiration),
28
+ validatorId: BigInt(permission.validatorId),
29
+ };
30
+
31
+ const [allowed, error, result] = await publicClient.readContract({
32
+ address: MockQueryDecrypterAddress,
33
+ abi: MockQueryDecrypterAbi,
34
+ functionName: 'querySealOutput',
35
+ args: [ctHash, BigInt(utype), permissionWithBigInts],
36
+ });
37
+
38
+ if (error != '') {
39
+ throw new CofhesdkError({
40
+ code: CofhesdkErrorCode.SealOutputFailed,
41
+ message: `mocks querySealOutput call failed: ${error}`,
42
+ });
43
+ }
44
+
45
+ if (allowed == false) {
46
+ throw new CofhesdkError({
47
+ code: CofhesdkErrorCode.SealOutputFailed,
48
+ message: `mocks querySealOutput call failed: ACL Access Denied (NotAllowed)`,
49
+ });
50
+ }
51
+
52
+ const sealedBigInt = BigInt(result);
53
+ const sealingKeyBigInt = BigInt(permission.sealingKey);
54
+ const unsealed = sealedBigInt ^ sealingKeyBigInt;
55
+
56
+ return unsealed;
57
+ }
@@ -0,0 +1,281 @@
1
+ import { hardhat } from '@/chains';
2
+ import { type Permit, PermitUtils } from '@/permits';
3
+
4
+ import { FheTypes, type UnsealedItem } from '../types.js';
5
+ import { getThresholdNetworkUrlOrThrow } from '../config.js';
6
+ import { type Result, resultWrapper } from '../result.js';
7
+ import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
8
+ import { permits } from '../permits.js';
9
+ import { isValidUtype, convertViaUtype } from './decryptUtils.js';
10
+ import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
11
+ import { cofheMocksSealOutput } from './cofheMocksSealOutput.js';
12
+ import { tnSealOutput } from './tnSealOutput.js';
13
+
14
+ /**
15
+ * API
16
+ *
17
+ * await client.decryptHandle(ctHash, utype)
18
+ * .setChainId(chainId)
19
+ * .setAccount(account)
20
+ * .setPermitHash(permitHash)
21
+ * .setPermit(permit)
22
+ * .decrypt()
23
+ *
24
+ * If chainId not set, uses client's chainId
25
+ * If account not set, uses client's account
26
+ * If permitHash not set, uses chainId and account to get active permit
27
+ * If permit is set, uses permit to decrypt regardless of chainId, account, or permitHash
28
+ *
29
+ * Returns a Result<UnsealedItem<U>>
30
+ */
31
+
32
+ type DecryptHandlesBuilderParams<U extends FheTypes> = BaseBuilderParams & {
33
+ ctHash: bigint;
34
+ utype: U;
35
+ permitHash?: string;
36
+ permit?: Permit;
37
+ };
38
+
39
+ export class DecryptHandlesBuilder<U extends FheTypes> extends BaseBuilder {
40
+ private ctHash: bigint;
41
+ private utype: U;
42
+ private permitHash?: string;
43
+ private permit?: Permit;
44
+
45
+ constructor(params: DecryptHandlesBuilderParams<U>) {
46
+ super({
47
+ config: params.config,
48
+ publicClient: params.publicClient,
49
+ walletClient: params.walletClient,
50
+ chainId: params.chainId,
51
+ account: params.account,
52
+ requireConnected: params.requireConnected,
53
+ });
54
+
55
+ this.ctHash = params.ctHash;
56
+ this.utype = params.utype;
57
+ this.permitHash = params.permitHash;
58
+ this.permit = params.permit;
59
+ }
60
+
61
+ /**
62
+ * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
63
+ *
64
+ * If not provided, the chainId will be fetched from the connected publicClient.
65
+ *
66
+ * Example:
67
+ * ```typescript
68
+ * const unsealed = await decryptHandle(ctHash, utype)
69
+ * .setChainId(11155111)
70
+ * .decrypt();
71
+ * ```
72
+ *
73
+ * @returns The chainable DecryptHandlesBuilder instance.
74
+ */
75
+ setChainId(chainId: number): DecryptHandlesBuilder<U> {
76
+ this.chainId = chainId;
77
+ return this;
78
+ }
79
+
80
+ getChainId(): number | undefined {
81
+ return this.chainId;
82
+ }
83
+
84
+ /**
85
+ * @param account - Account to decrypt values from. Used to fetch the correct permit.
86
+ *
87
+ * If not provided, the account will be fetched from the connected walletClient.
88
+ *
89
+ * Example:
90
+ * ```typescript
91
+ * const unsealed = await decryptHandle(ctHash, utype)
92
+ * .setAccount('0x1234567890123456789012345678901234567890')
93
+ * .decrypt();
94
+ * ```
95
+ *
96
+ * @returns The chainable DecryptHandlesBuilder instance.
97
+ */
98
+ setAccount(account: string): DecryptHandlesBuilder<U> {
99
+ this.account = account;
100
+ return this;
101
+ }
102
+
103
+ getAccount(): string | undefined {
104
+ return this.account;
105
+ }
106
+
107
+ /**
108
+ * @param permitHash - Permit hash to decrypt values from. Used to fetch the correct permit.
109
+ *
110
+ * If not provided, the active permit for the chainId and account will be used.
111
+ * If `setPermit()` is called, it will be used regardless of chainId, account, or permitHash.
112
+ *
113
+ * Example:
114
+ * ```typescript
115
+ * const unsealed = await decryptHandle(ctHash, utype)
116
+ * .setPermitHash('0x1234567890123456789012345678901234567890')
117
+ * .decrypt();
118
+ * ```
119
+ *
120
+ * @returns The chainable DecryptHandlesBuilder instance.
121
+ */
122
+ setPermitHash(permitHash: string): DecryptHandlesBuilder<U> {
123
+ this.permitHash = permitHash;
124
+ return this;
125
+ }
126
+
127
+ getPermitHash(): string | undefined {
128
+ return this.permitHash;
129
+ }
130
+
131
+ /**
132
+ * @param permit - Permit to decrypt values with. If provided, it will be used regardless of chainId, account, or permitHash.
133
+ *
134
+ * If not provided, the permit will be determined by chainId, account, and permitHash.
135
+ *
136
+ * Example:
137
+ * ```typescript
138
+ * const unsealed = await decryptHandle(ctHash, utype)
139
+ * .setPermit(permit)
140
+ * .decrypt();
141
+ * ```
142
+ *
143
+ * @returns The chainable DecryptHandlesBuilder instance.
144
+ */
145
+ setPermit(permit: Permit): DecryptHandlesBuilder<U> {
146
+ this.permit = permit;
147
+ return this;
148
+ }
149
+
150
+ getPermit(): Permit | undefined {
151
+ return this.permit;
152
+ }
153
+
154
+ private getThresholdNetworkUrl(chainId: number): string {
155
+ const config = this.getConfigOrThrow();
156
+ return getThresholdNetworkUrlOrThrow(config, chainId);
157
+ }
158
+
159
+ private validateUtypeOrThrow(): void {
160
+ if (!isValidUtype(this.utype))
161
+ throw new CofhesdkError({
162
+ code: CofhesdkErrorCode.InvalidUtype,
163
+ message: `Invalid utype to decrypt to`,
164
+ context: {
165
+ utype: this.utype,
166
+ },
167
+ });
168
+ }
169
+
170
+ private async getResolvedPermit(): Promise<Permit> {
171
+ if (this.permit) return this.permit;
172
+
173
+ const chainId = await this.getChainIdOrThrow();
174
+ const account = await this.getAccountOrThrow();
175
+
176
+ // Fetch with permit hash
177
+ if (this.permitHash) {
178
+ const permit = await permits.getPermit(chainId, account, this.permitHash);
179
+ if (!permit) {
180
+ throw new CofhesdkError({
181
+ code: CofhesdkErrorCode.PermitNotFound,
182
+ message: `Permit with hash <${this.permitHash}> not found for account <${account}> and chainId <${chainId}>`,
183
+ hint: 'Ensure the permit exists and is valid.',
184
+ context: {
185
+ chainId,
186
+ account,
187
+ permitHash: this.permitHash,
188
+ },
189
+ });
190
+ }
191
+ return permit;
192
+ }
193
+
194
+ // Fetch with active permit
195
+ const permit = await permits.getActivePermit(chainId, account);
196
+ if (!permit) {
197
+ throw new CofhesdkError({
198
+ code: CofhesdkErrorCode.PermitNotFound,
199
+ message: `Active permit not found for chainId <${chainId}> and account <${account}>`,
200
+ hint: 'Ensure a permit exists for this account on this chain.',
201
+ context: {
202
+ chainId,
203
+ account,
204
+ },
205
+ });
206
+ }
207
+ return permit;
208
+ }
209
+
210
+ /**
211
+ * On hardhat, interact with MockZkVerifier contract instead of CoFHE
212
+ */
213
+ private async mocksSealOutput(permit: Permit): Promise<bigint> {
214
+ const config = this.getConfigOrThrow();
215
+ const mocksSealOutputDelay = config.mocks.sealOutputDelay;
216
+ return cofheMocksSealOutput(this.ctHash, this.utype, permit, this.getPublicClientOrThrow(), mocksSealOutputDelay);
217
+ }
218
+
219
+ /**
220
+ * In the production context, perform a true decryption with the CoFHE coprocessor.
221
+ */
222
+ private async productionSealOutput(chainId: number, permit: Permit): Promise<bigint> {
223
+ const thresholdNetworkUrl = this.getThresholdNetworkUrl(chainId);
224
+ const permission = PermitUtils.getPermission(permit, true);
225
+ const sealed = await tnSealOutput(this.ctHash, chainId, permission, thresholdNetworkUrl);
226
+ return PermitUtils.unseal(permit, sealed);
227
+ }
228
+
229
+ /**
230
+ * Final step of the decryption process. MUST BE CALLED LAST IN THE CHAIN.
231
+ *
232
+ * This will:
233
+ * - Use a permit based on provided permit OR chainId + account + permitHash
234
+ * - Check permit validity
235
+ * - Call CoFHE `/sealoutput` with the permit, which returns a sealed (encrypted) item
236
+ * - Unseal the sealed item with the permit
237
+ * - Return the unsealed item
238
+ *
239
+ * Example:
240
+ * ```typescript
241
+ * const unsealed = await decryptHandle(ctHash, utype)
242
+ * .setChainId(11155111) // optional
243
+ * .setAccount('0x123...890') // optional
244
+ * .decrypt(); // execute
245
+ * ```
246
+ *
247
+ * @returns The unsealed item.
248
+ */
249
+ decrypt(): Promise<Result<UnsealedItem<U>>> {
250
+ return resultWrapper(async () => {
251
+ // Ensure cofhe client is connected
252
+ await this.requireConnectedOrThrow();
253
+
254
+ // Ensure utype is valid
255
+ this.validateUtypeOrThrow();
256
+
257
+ // Resolve permit
258
+ const permit = await this.getResolvedPermit();
259
+
260
+ // Ensure permit validity
261
+ await PermitUtils.validate(permit);
262
+
263
+ // Extract chainId from signed permit
264
+ // Use this chainId to fetch the threshold network URL since this.chainId may be undefined
265
+ const chainId = permit._signedDomain!.chainId;
266
+
267
+ // Check permit validity on-chain
268
+ // TODO: PermitUtils.validateOnChain(permit, this.publicClient);
269
+
270
+ let unsealed: bigint;
271
+
272
+ if (chainId === hardhat.id) {
273
+ unsealed = await this.mocksSealOutput(permit);
274
+ } else {
275
+ unsealed = await this.productionSealOutput(chainId, permit);
276
+ }
277
+
278
+ return convertViaUtype(this.utype, unsealed);
279
+ });
280
+ }
281
+ }