@tokenops/sdk 1.1.1 → 1.6.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 (163) hide show
  1. package/CHANGELOG.md +158 -2
  2. package/README.md +18 -8
  3. package/SECURITY.md +7 -1
  4. package/dist/{chunk-56UI7LUR.cjs → chunk-2RNW4MIJ.cjs} +37 -0
  5. package/dist/{chunk-VDXVNZCO.cjs → chunk-43RFBQ73.cjs} +85 -29
  6. package/dist/{chunk-BE2AIZ3K.js → chunk-46T67CE2.js} +1 -1
  7. package/dist/{chunk-WRCUDSUJ.js → chunk-4KJ66YRH.js} +61 -3
  8. package/dist/{chunk-ITQ7WKVC.cjs → chunk-4ZCXK4VI.cjs} +68 -8
  9. package/dist/{chunk-JFLEEXKP.js → chunk-6WSNS3UV.js} +58 -2
  10. package/dist/{chunk-W7JHOK7G.cjs → chunk-7G7UOQV4.cjs} +4 -4
  11. package/dist/{chunk-5KHIQG7Y.cjs → chunk-AYGRYDBX.cjs} +35 -36
  12. package/dist/{chunk-5SGMJADP.cjs → chunk-BD4LZBVF.cjs} +85 -9
  13. package/dist/{chunk-OYLZEEHF.cjs → chunk-BK7YIVLK.cjs} +304 -179
  14. package/dist/{chunk-SEKC7H5Y.cjs → chunk-CBRL2PJA.cjs} +12 -11
  15. package/dist/{chunk-XWDVPV42.js → chunk-CNP4L3GF.js} +4 -4
  16. package/dist/{chunk-IVE3QEGD.js → chunk-DRSPMIZ7.js} +28 -1
  17. package/dist/{chunk-SAGJIEQK.cjs → chunk-EUKPOXWR.cjs} +15 -16
  18. package/dist/chunk-FT4LNBOS.js +11 -0
  19. package/dist/{chunk-ITQBDRWX.js → chunk-HETNKZEE.js} +1 -1
  20. package/dist/{chunk-JJ4R5QXF.js → chunk-KSDSXJ34.js} +1 -1
  21. package/dist/{chunk-SYHXDHWX.cjs → chunk-LBWRFZR3.cjs} +218 -31
  22. package/dist/{chunk-ZXCOJY2Z.js → chunk-NFW7AUEX.js} +1 -1
  23. package/dist/{chunk-FPQBFIUW.cjs → chunk-OL6SHN3D.cjs} +3 -3
  24. package/dist/{chunk-XSNLAS5M.js → chunk-PD7ME2BT.js} +75 -2
  25. package/dist/{chunk-FXCW7LVB.js → chunk-QET3Q4JP.js} +94 -9
  26. package/dist/{chunk-M7V2EDPB.js → chunk-QKKBBH7I.js} +2 -2
  27. package/dist/{chunk-XXXDQSHE.js → chunk-S2XD75JM.js} +12 -3
  28. package/dist/{chunk-5SA2HF2W.cjs → chunk-SPGSO5SO.cjs} +31 -22
  29. package/dist/{chunk-Q7ARQSUH.cjs → chunk-SYHNZSZZ.cjs} +3 -3
  30. package/dist/{chunk-X56UVEXS.js → chunk-TN65XNTI.js} +248 -120
  31. package/dist/chunk-U57COLUE.js +56 -0
  32. package/dist/{chunk-OQZAZIAS.js → chunk-UE5XK2SY.js} +193 -6
  33. package/dist/{chunk-NS44KBV5.cjs → chunk-UJS4N2XM.cjs} +12 -12
  34. package/dist/{chunk-IR5AK5U5.js → chunk-V5D7BHW3.js} +4 -5
  35. package/dist/{chunk-3PQ2RFPC.js → chunk-VHSNYUYV.js} +5 -4
  36. package/dist/{chunk-CQIPRNS7.cjs → chunk-VR3FREBX.cjs} +3 -3
  37. package/dist/{chunk-G7G75C46.cjs → chunk-WO72UBQD.cjs} +11 -11
  38. package/dist/chunk-WOEETTH7.cjs +59 -0
  39. package/dist/{chunk-QE7ZONJ2.js → chunk-WTK6JDSI.js} +4 -5
  40. package/dist/{chunk-3ZRHDTEX.cjs → chunk-WUXUWTFW.cjs} +3 -3
  41. package/dist/{chunk-23F7JIK5.cjs → chunk-YFIWCYHC.cjs} +141 -54
  42. package/dist/{chunk-FYQ2UW4T.js → chunk-YPYCWLYP.js} +1 -1
  43. package/dist/chunk-ZQUE2B3Q.cjs +33 -0
  44. package/dist/core/addresses.d.ts +1 -1
  45. package/dist/core/errors.d.ts +41 -1
  46. package/dist/core/revert-mapper.d.ts +77 -0
  47. package/dist/core/wagmi-compat.d.ts +40 -0
  48. package/dist/fhe/index.cjs +134 -68
  49. package/dist/fhe/index.d.cts +2 -1
  50. package/dist/fhe/index.d.ts +2 -1
  51. package/dist/fhe/index.js +64 -10
  52. package/dist/fhe/mock-erc7984.d.ts +4 -4
  53. package/dist/fhe/operators.d.ts +142 -2
  54. package/dist/fhe/react/index.cjs +21 -8
  55. package/dist/fhe/react/index.d.cts +2 -0
  56. package/dist/fhe/react/index.d.ts +2 -0
  57. package/dist/fhe/react/index.js +8 -3
  58. package/dist/fhe/react/useEnsureOperator.d.ts +56 -0
  59. package/dist/fhe/react/useIsOperator.d.ts +54 -0
  60. package/dist/fhe/sepolia-encryptor-web.d.ts +45 -4
  61. package/dist/fhe/sepolia-encryptor.d.ts +38 -0
  62. package/dist/fhe-airdrop/advanced/index.cjs +9 -9
  63. package/dist/fhe-airdrop/advanced/index.js +7 -7
  64. package/dist/fhe-airdrop/advanced/react/index.cjs +14 -14
  65. package/dist/fhe-airdrop/advanced/react/index.js +10 -10
  66. package/dist/fhe-airdrop/airdrop.d.ts +9 -0
  67. package/dist/fhe-airdrop/encryption.d.ts +8 -0
  68. package/dist/fhe-airdrop/factory.d.ts +86 -1
  69. package/dist/fhe-airdrop/index.cjs +69 -66
  70. package/dist/fhe-airdrop/index.d.cts +3 -3
  71. package/dist/fhe-airdrop/index.d.ts +3 -3
  72. package/dist/fhe-airdrop/index.js +7 -8
  73. package/dist/fhe-airdrop/react/index.cjs +176 -135
  74. package/dist/fhe-airdrop/react/index.d.cts +7 -4
  75. package/dist/fhe-airdrop/react/index.d.ts +7 -4
  76. package/dist/fhe-airdrop/react/index.js +43 -16
  77. package/dist/fhe-airdrop/react/useAccessClaimAmount.d.ts +17 -11
  78. package/dist/fhe-airdrop/react/useAirdropIsSignatureValid.d.ts +3 -2
  79. package/dist/fhe-airdrop/react/useClaim.d.ts +10 -0
  80. package/dist/fhe-airdrop/react/useCreateAndFundConfidentialAirdrop.d.ts +8 -0
  81. package/dist/fhe-airdrop/react/useCreateAndFundConfidentialAirdropAndGetAddress.d.ts +8 -0
  82. package/dist/fhe-airdrop/react/useFundConfidentialAirdrop.d.ts +8 -0
  83. package/dist/fhe-airdrop/react/usePreflightCreateAirdrop.d.ts +51 -0
  84. package/dist/fhe-airdrop/react/useSignClaimAuthorization.d.ts +1 -1
  85. package/dist/fhe-airdrop/types.d.ts +56 -0
  86. package/dist/fhe-disperse/encryption.d.ts +8 -0
  87. package/dist/fhe-disperse/errors.d.ts +55 -0
  88. package/dist/fhe-disperse/index.cjs +77 -66
  89. package/dist/fhe-disperse/index.d.cts +2 -2
  90. package/dist/fhe-disperse/index.d.ts +2 -2
  91. package/dist/fhe-disperse/index.js +8 -9
  92. package/dist/fhe-disperse/react/index.cjs +108 -87
  93. package/dist/fhe-disperse/react/index.d.cts +4 -2
  94. package/dist/fhe-disperse/react/index.d.ts +4 -2
  95. package/dist/fhe-disperse/react/index.js +12 -11
  96. package/dist/fhe-disperse/react/useCalculateFee.d.ts +7 -0
  97. package/dist/fhe-disperse/react/useDisperse.d.ts +9 -0
  98. package/dist/fhe-disperse/react/usePreflightDisperse.d.ts +7 -0
  99. package/dist/fhe-disperse/react/useSingletonCalculateFee.d.ts +7 -0
  100. package/dist/fhe-disperse/react/useSingletonWithdrawTokenFee.d.ts +6 -0
  101. package/dist/fhe-disperse/react/useWithdrawTokenFee.d.ts +6 -0
  102. package/dist/fhe-disperse/singleton.d.ts +29 -0
  103. package/dist/fhe-disperse/subtotals.d.ts +6 -0
  104. package/dist/fhe-vesting/advanced/index.cjs +8 -9
  105. package/dist/fhe-vesting/advanced/index.js +6 -7
  106. package/dist/fhe-vesting/advanced/react/index.cjs +15 -15
  107. package/dist/fhe-vesting/advanced/react/index.js +12 -12
  108. package/dist/fhe-vesting/encryption.d.ts +8 -0
  109. package/dist/fhe-vesting/index.cjs +82 -79
  110. package/dist/fhe-vesting/index.d.cts +2 -2
  111. package/dist/fhe-vesting/index.d.ts +2 -2
  112. package/dist/fhe-vesting/index.js +9 -10
  113. package/dist/fhe-vesting/manager.d.ts +119 -1
  114. package/dist/fhe-vesting/react/index.cjs +264 -224
  115. package/dist/fhe-vesting/react/index.d.cts +9 -6
  116. package/dist/fhe-vesting/react/index.d.ts +9 -6
  117. package/dist/fhe-vesting/react/index.js +44 -21
  118. package/dist/fhe-vesting/react/useAccessClaimableAmount.d.ts +11 -3
  119. package/dist/fhe-vesting/react/useAccessSettledAmount.d.ts +11 -3
  120. package/dist/fhe-vesting/react/useAccessTotalAllocation.d.ts +11 -3
  121. package/dist/fhe-vesting/react/useAccessVestedAmount.d.ts +11 -3
  122. package/dist/fhe-vesting/react/useAdminClaim.d.ts +2 -0
  123. package/dist/fhe-vesting/react/useAdminGetClaimableAmount.d.ts +1 -1
  124. package/dist/fhe-vesting/react/useAdminGetSettledAmount.d.ts +1 -1
  125. package/dist/fhe-vesting/react/useAdminGetTotalAllocation.d.ts +1 -1
  126. package/dist/fhe-vesting/react/useAdminGetVestedAmount.d.ts +1 -1
  127. package/dist/fhe-vesting/react/useAdminPartialClaim.d.ts +10 -0
  128. package/dist/fhe-vesting/react/useBatchCreateVesting.d.ts +8 -0
  129. package/dist/fhe-vesting/react/useClaim.d.ts +3 -1
  130. package/dist/fhe-vesting/react/useCreateVesting.d.ts +8 -0
  131. package/dist/fhe-vesting/react/useDiscloseHandleToParty.d.ts +1 -1
  132. package/dist/fhe-vesting/react/useManagerDiscloseHandleToParty.d.ts +1 -1
  133. package/dist/fhe-vesting/react/useManagerWithdrawTokenFee.d.ts +6 -0
  134. package/dist/fhe-vesting/react/usePartialClaim.d.ts +9 -1
  135. package/dist/fhe-vesting/react/usePreflightCreateVesting.d.ts +37 -0
  136. package/dist/fhe-vesting/react/useVestingClaim.d.ts +2 -0
  137. package/dist/fhe-vesting/react/useVestingInfo.d.ts +1 -1
  138. package/dist/fhe-vesting/react/useWithdrawAdmin.d.ts +6 -0
  139. package/dist/fhe-vesting/react/useWithdrawTokenFee.d.ts +6 -0
  140. package/dist/fhe-vesting/types.d.ts +45 -0
  141. package/dist/index.cjs +93 -86
  142. package/dist/index.d.cts +1 -1
  143. package/dist/index.d.ts +1 -1
  144. package/dist/index.js +2 -3
  145. package/dist/testnet-faucet/index.cjs +58 -55
  146. package/dist/testnet-faucet/index.d.cts +1 -1
  147. package/dist/testnet-faucet/index.d.ts +1 -1
  148. package/dist/testnet-faucet/index.js +5 -6
  149. package/dist/testnet-faucet/react/index.cjs +60 -56
  150. package/dist/testnet-faucet/react/index.d.cts +3 -2
  151. package/dist/testnet-faucet/react/index.d.ts +3 -2
  152. package/dist/testnet-faucet/react/index.js +11 -11
  153. package/dist/testnet-faucet/react/useConfidentialBalance.d.ts +3 -2
  154. package/dist/testnet-faucet/react/useUnderlyingBalance.d.ts +2 -2
  155. package/package.json +14 -8
  156. package/dist/chunk-66IHPTOK.cjs +0 -20
  157. package/dist/chunk-KWFFIJYX.js +0 -11
  158. package/dist/fhe-airdrop/react/useAirdropClaim.d.ts +0 -25
  159. package/dist/fhe-airdrop/react/useGetClaimAmount.d.ts +0 -30
  160. package/dist/fhe-vesting/react/useGetClaimableAmount.d.ts +0 -13
  161. package/dist/fhe-vesting/react/useGetSettledAmount.d.ts +0 -14
  162. package/dist/fhe-vesting/react/useGetTotalAllocation.d.ts +0 -13
  163. package/dist/fhe-vesting/react/useGetVestedAmount.d.ts +0 -25
package/dist/fhe/index.js CHANGED
@@ -1,11 +1,11 @@
1
- export { ERC7984_OPERATOR_MAX_DEADLINE, ERC7984_SET_OPERATOR_ABI, revokeOperator, setOperator } from '../chunk-WRCUDSUJ.js';
1
+ export { ERC7984_OPERATOR_MAX_DEADLINE, ERC7984_SET_OPERATOR_ABI, ensureOperator, isOperator, revokeOperator, setOperator } from '../chunk-4KJ66YRH.js';
2
+ export { FHE_SPLIT_DENOMINATOR, scaleRatio, share } from '../chunk-PD7ME2BT.js';
3
+ export { ACL_ALLOWED_EVENT, FHEVM_ACL_ADDRESS_BY_CHAIN, getFhevmAclAddress, requireFhevmAclAddress } from '../chunk-YPYCWLYP.js';
2
4
  export { erc7984OperatorAbi } from '../chunk-7VELUOMI.js';
3
- export { FHE_SPLIT_DENOMINATOR, scaleRatio, share } from '../chunk-XSNLAS5M.js';
4
- export { ACL_ALLOWED_EVENT, FHEVM_ACL_ADDRESS_BY_CHAIN, getFhevmAclAddress, requireFhevmAclAddress } from '../chunk-FYQ2UW4T.js';
5
- import { normaliseAddress } from '../chunk-BE2AIZ3K.js';
5
+ import { normaliseAddress } from '../chunk-46T67CE2.js';
6
+ import { TokenOpsContractError } from '../chunk-DRSPMIZ7.js';
7
+ export { AccessDeniedError, AlreadyInitializedError, BatchTooLargeError, ContractRevertError, DecryptionFailedError, DeploymentAddressUnavailableError, EncryptionFailedError, FeatureDisabledError, FheHandleNotAllowedError, InsufficientBalanceError, InsufficientFeeError, InsufficientGasFundsError, InvalidArgumentError, InvalidSignatureError, MissingAccountError, MissingClientError, MissingEncryptorError, MissingPublicClientError, MissingWalletClientError, NetworkError, OperatorNotApprovedError, PausedError, ReceiptEventAmbiguousError, ReceiptEventNotFoundError, ReentrancyError, RelayerUnreachableError, SigningFailedError, TokenOpsContractError, TokenOpsSdkError, TokenOpsValidationError, TransferFailedError, UnknownWriteFailureError, UnsupportedChainError, UserDecryptNotAllowedError, UserRejectedSignatureError, WalletChainMismatchError, WalletRejectedError, isTokenOpsSdkError } from '../chunk-DRSPMIZ7.js';
6
8
  import { withTelemetry } from '../chunk-Q2GP5UDC.js';
7
- import { TokenOpsContractError } from '../chunk-IVE3QEGD.js';
8
- export { AccessDeniedError, AlreadyInitializedError, BatchTooLargeError, ContractRevertError, DecryptionFailedError, DeploymentAddressUnavailableError, EncryptionFailedError, FeatureDisabledError, FheHandleNotAllowedError, InsufficientBalanceError, InsufficientFeeError, InsufficientGasFundsError, InvalidArgumentError, InvalidSignatureError, MissingAccountError, MissingClientError, MissingEncryptorError, MissingPublicClientError, MissingWalletClientError, NetworkError, PausedError, ReceiptEventAmbiguousError, ReceiptEventNotFoundError, ReentrancyError, RelayerUnreachableError, SigningFailedError, TokenOpsContractError, TokenOpsSdkError, TokenOpsValidationError, TransferFailedError, UnknownWriteFailureError, UnsupportedChainError, UserDecryptNotAllowedError, UserRejectedSignatureError, WalletChainMismatchError, WalletRejectedError, isTokenOpsSdkError } from '../chunk-IVE3QEGD.js';
9
9
  import { RelayerWeb, MainnetConfig, SepoliaConfig } from '@zama-fhe/sdk';
10
10
 
11
11
  // src/fhe/mock-encryptor.ts
@@ -22,7 +22,8 @@ async function requirePeerDep(spec, hint) {
22
22
  throw new Error(
23
23
  `@tokenops/sdk: peer dependency "${spec}" is required for createMockEncryptor.
24
24
  install it with: ${hint}
25
- underlying error: ${String(err)}`
25
+ underlying error: ${String(err)}`,
26
+ { cause: err }
26
27
  );
27
28
  }
28
29
  }
@@ -302,12 +303,21 @@ async function requirePeerDep2(spec, hint) {
302
303
  throw new Error(
303
304
  `@tokenops/sdk: peer dependency "${spec}" is required for createSepoliaEncryptor.
304
305
  install it with: ${hint}
305
- underlying error: ${String(err)}`
306
+ underlying error: ${String(err)}`,
307
+ { cause: err }
306
308
  );
307
309
  }
308
310
  }
309
311
  async function createSepoliaEncryptor(options = {}) {
310
312
  const chainId = options.chainId ?? SEPOLIA_CHAIN_ID;
313
+ const firePhase = (phase) => {
314
+ if (!options.onPhase) return;
315
+ try {
316
+ options.onPhase(phase);
317
+ } catch {
318
+ }
319
+ };
320
+ firePhase("initializing");
311
321
  const zamaSdkNode = await requirePeerDep2(
312
322
  "@zama-fhe/sdk/node",
313
323
  "pnpm add @zama-fhe/sdk@^3"
@@ -336,11 +346,29 @@ async function createSepoliaEncryptor(options = {}) {
336
346
  fheArtifactStorage: options.fheArtifactStorage,
337
347
  fheArtifactCacheTTL: options.fheArtifactCacheTTL
338
348
  });
349
+ let initState = "idle";
339
350
  return {
340
351
  instance,
341
352
  chainId,
342
353
  async encrypt({ values, contractAddress, userAddress }) {
343
- return instance.encrypt({ values, contractAddress, userAddress });
354
+ const startedInit = initState === "idle";
355
+ if (startedInit) {
356
+ initState = "in-flight";
357
+ firePhase("downloading-params");
358
+ }
359
+ try {
360
+ const result = await instance.encrypt({ values, contractAddress, userAddress });
361
+ if (initState !== "ready") {
362
+ initState = "ready";
363
+ firePhase("ready");
364
+ }
365
+ return result;
366
+ } catch (err) {
367
+ if (startedInit && initState === "in-flight") {
368
+ initState = "idle";
369
+ }
370
+ throw err;
371
+ }
344
372
  },
345
373
  terminate() {
346
374
  instance.terminate();
@@ -374,10 +402,36 @@ function createSepoliaEncryptorWeb(opts) {
374
402
  },
375
403
  ...opts.logger
376
404
  } : void 0;
405
+ const firePhase = (phase) => {
406
+ if (!opts.onPhase) return;
407
+ try {
408
+ opts.onPhase(phase);
409
+ } catch {
410
+ }
411
+ };
412
+ firePhase("initializing");
413
+ const onStatusChange = opts.onPhase ? (status) => {
414
+ if (status === "initializing") {
415
+ firePhase("downloading-params");
416
+ } else if (status === "ready") {
417
+ firePhase("ready");
418
+ }
419
+ } : void 0;
420
+ if (opts.threads !== void 0 && opts.threads > 1) {
421
+ const isolated = globalThis.crossOriginIsolated === true;
422
+ if (!isolated) {
423
+ const warn = safeLogger?.warn ?? console.warn;
424
+ warn(
425
+ `@tokenops/sdk: createSepoliaEncryptorWeb({ threads: ${opts.threads} }) \u2014 this page is NOT cross-origin isolated, so SharedArrayBuffer is unavailable and encryption will run single-threaded. Serve \`Cross-Origin-Opener-Policy: same-origin\` + \`Cross-Origin-Embedder-Policy: require-corp\` to enable the worker pool, or omit \`threads\`.`
426
+ );
427
+ }
428
+ }
377
429
  const instance = new RelayerWeb({
378
430
  transports: { [chainId]: transportConfig },
379
431
  getChainId: () => Promise.resolve(chainId),
380
- logger: safeLogger
432
+ logger: safeLogger,
433
+ onStatusChange,
434
+ ...opts.threads !== void 0 ? { threads: opts.threads } : {}
381
435
  });
382
436
  return Promise.resolve({
383
437
  instance,
@@ -76,12 +76,12 @@ export interface MintMockERC7984Result {
76
76
  *
77
77
  * @example
78
78
  * ```ts
79
- * import { mintMockERC7984, DEPLOYED_ADDRESSES } from "@tokenops/sdk";
79
+ * import { mintMockERC7984 } from "@tokenops/sdk/fhe";
80
80
  *
81
81
  * const { hash, blockNumber } = await mintMockERC7984({
82
82
  * publicClient,
83
83
  * walletClient,
84
- * token: DEPLOYED_ADDRESSES.tokens.testConfidential[11155111]!,
84
+ * token: "0xYourMockToken", // MockERC7984Token address
85
85
  * to: walletClient.account!.address,
86
86
  * amount: 1_000_000n,
87
87
  * });
@@ -119,12 +119,12 @@ export interface MockErc7984Client {
119
119
  *
120
120
  * @example
121
121
  * ```ts
122
- * import { createMockErc7984Client, DEPLOYED_ADDRESSES } from "@tokenops/sdk/fhe";
122
+ * import { createMockErc7984Client } from "@tokenops/sdk/fhe";
123
123
  *
124
124
  * const token = createMockErc7984Client({
125
125
  * publicClient,
126
126
  * walletClient,
127
- * address: DEPLOYED_ADDRESSES.tokens.testConfidential[11155111]!,
127
+ * address: "0xYourMockToken", // MockERC7984Token address
128
128
  * });
129
129
  *
130
130
  * await token.mint({ to: alice, amount: 1_000_000n });
@@ -108,7 +108,7 @@ export interface SetOperatorArgs {
108
108
  * await setOperator({
109
109
  * publicClient,
110
110
  * walletClient,
111
- * token: DEPLOYED_ADDRESSES.tokens.testConfidential[31337]!,
111
+ * token: "0xYourToken", // ERC-7984 token address
112
112
  * spender: manager,
113
113
  * // deadline defaults to ERC7984_OPERATOR_MAX_DEADLINE (forever).
114
114
  * });
@@ -177,7 +177,7 @@ export interface RevokeOperatorArgs {
177
177
  * await revokeOperator({
178
178
  * publicClient,
179
179
  * walletClient,
180
- * token: DEPLOYED_ADDRESSES.tokens.testConfidential[31337]!,
180
+ * token: "0xYourToken", // ERC-7984 token address
181
181
  * spender: staleManager, // old manager clone after deploying a new one
182
182
  * });
183
183
  * // `staleManager` can no longer call `confidentialTransferFrom` against
@@ -188,3 +188,143 @@ export interface RevokeOperatorArgs {
188
188
  * step throws. The original viem error is attached as the cause.
189
189
  */
190
190
  export declare function revokeOperator(args: RevokeOperatorArgs): Promise<Hex>;
191
+ /** Arguments for {@link isOperator}. Read-only — no wallet required. */
192
+ export interface IsOperatorArgs {
193
+ /** Read-only viem client the `isOperator` view call goes through. */
194
+ publicClient: PublicClient;
195
+ /** ERC-7984 token contract address. */
196
+ token: Address;
197
+ /** Account that owns the tokens (the operator-authorization granter). */
198
+ holder: Address;
199
+ /**
200
+ * Address whose operator status is being checked — typically a manager
201
+ * clone (`/fhe-vesting`), an airdrop clone/factory (`/fhe-airdrop`), or
202
+ * the disperse singleton (`/fhe-disperse`).
203
+ */
204
+ spender: Address;
205
+ /** Optional telemetry sink — span emits as `fhe.isOperator`. */
206
+ telemetry?: SdkTelemetry;
207
+ }
208
+ /**
209
+ * Check whether `spender` is currently an authorized ERC-7984 operator for
210
+ * `holder` on `token`. Read-only sibling of {@link setOperator} — the
211
+ * contract-side check is `until >= block.timestamp`, so an expired grant
212
+ * reads `false` here without any revoke transaction having happened.
213
+ *
214
+ * Use this to gate UX ("Approve operator" button vs "Create vesting" button)
215
+ * or as a cheap pre-check before flows that would otherwise fail with
216
+ * {@link OperatorNotApprovedError} — the SDK's mapping of the on-chain
217
+ * `ERC7984UnauthorizedSpender` revert, whose message is the canonical
218
+ * {@link OPERATOR_NOT_APPROVED_REMEDIATION} copy. For a check-then-set in
219
+ * one call, see {@link ensureOperator}.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * import { isOperator } from "@tokenops/sdk/fhe";
224
+ *
225
+ * const authorized = await isOperator({
226
+ * publicClient,
227
+ * token,
228
+ * holder: account.address,
229
+ * spender: manager,
230
+ * });
231
+ * if (!authorized) {
232
+ * // prompt the user to run setOperator / ensureOperator first
233
+ * }
234
+ * ```
235
+ *
236
+ * @throws {@link TokenOpsContractError} if the read call itself fails (RPC
237
+ * error, non-ERC-7984 token). The original viem error is attached as the
238
+ * cause.
239
+ */
240
+ export declare function isOperator(args: IsOperatorArgs): Promise<boolean>;
241
+ /**
242
+ * Arguments for {@link ensureOperator}. Same shape as {@link SetOperatorArgs}
243
+ * except `deadline` is **required**: `ensureOperator` only sends a
244
+ * transaction when the authorization is missing, so the expiry it would
245
+ * grant must be an explicit caller decision — the SDK refuses to default a
246
+ * security-sensitive approval window (a silent
247
+ * {@link ERC7984_OPERATOR_MAX_DEADLINE} would grant "forever").
248
+ */
249
+ export interface EnsureOperatorArgs {
250
+ /** Read-only viem client for the `isOperator` pre-check + receipt waiting. */
251
+ publicClient: PublicClient;
252
+ /** Wallet client used to send the `setOperator` call (only when needed). */
253
+ walletClient: WalletClient;
254
+ /**
255
+ * Account that owns the tokens. Same `Account | Address` semantics and
256
+ * fallback hierarchy as {@link SetOperatorArgs} — also used to derive the
257
+ * `holder` for the read-side `isOperator` pre-check.
258
+ */
259
+ account?: Account | Address;
260
+ /** ERC-7984 token contract address. */
261
+ token: Address;
262
+ /** Address being authorized to pull tokens. */
263
+ spender: Address;
264
+ /**
265
+ * `uint48` unix timestamp after which the operator authorization expires.
266
+ * Required — scope it to the expected operation window (e.g.
267
+ * `BigInt(Math.floor(Date.now() / 1000) + 3600)` for one hour). Pass
268
+ * {@link ERC7984_OPERATOR_MAX_DEADLINE} only when you explicitly want a
269
+ * never-expiring grant (local dev loops, test fixtures).
270
+ */
271
+ deadline: bigint;
272
+ /**
273
+ * If `true` (default), waits for the transaction to be mined before
274
+ * returning. Only relevant when a transaction is actually sent.
275
+ */
276
+ waitForReceipt?: boolean;
277
+ /** Optional telemetry sink — span emits as `fhe.ensureOperator`. */
278
+ telemetry?: SdkTelemetry;
279
+ }
280
+ /** Result of {@link ensureOperator}. */
281
+ export interface EnsureOperatorResult {
282
+ /**
283
+ * `true` when the authorization was already in place and no transaction
284
+ * was sent; `false` when this call performed the `setOperator` write.
285
+ */
286
+ alreadyOperator: boolean;
287
+ /**
288
+ * Transaction hash of the `setOperator` call, or `null` when no
289
+ * transaction was needed (`alreadyOperator === true`).
290
+ */
291
+ hash: Hex | null;
292
+ }
293
+ /**
294
+ * Idempotent operator authorization: check {@link isOperator} first and call
295
+ * {@link setOperator} only when the grant is missing. The no-op path sends
296
+ * no transaction and costs one `eth_call`.
297
+ *
298
+ * This is the recommended pre-step before any flow that pulls tokens from
299
+ * the caller's balance (`createVesting` / `batchCreateVesting`,
300
+ * `fundConfidentialAirdrop` / `createAndFundConfidentialAirdrop`, disperse
301
+ * registration) — skipping it fails with {@link OperatorNotApprovedError},
302
+ * the SDK's mapping of the on-chain `ERC7984UnauthorizedSpender` revert,
303
+ * whose message is the canonical {@link OPERATOR_NOT_APPROVED_REMEDIATION}
304
+ * copy.
305
+ *
306
+ * **Note on expiry:** the pre-check only tells you the grant is currently
307
+ * active, not until when. If an existing grant expires mid-flow,
308
+ * `ensureOperator` will not have refreshed it — pass a fresh
309
+ * {@link setOperator} call instead when you need to extend a window.
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * import { ensureOperator } from "@tokenops/sdk/fhe";
314
+ *
315
+ * const oneHour = BigInt(Math.floor(Date.now() / 1000) + 3600);
316
+ * const { alreadyOperator, hash } = await ensureOperator({
317
+ * publicClient,
318
+ * walletClient,
319
+ * token,
320
+ * spender: manager,
321
+ * deadline: oneHour,
322
+ * });
323
+ * // Now safe to call manager.createVesting / factory.fundConfidentialAirdrop / etc.
324
+ * ```
325
+ *
326
+ * @throws {@link TokenOpsContractError} if no holder account can be
327
+ * resolved, if `deadline` is outside `(0, 2**48 - 1]`, or if the
328
+ * read/write/receipt step fails.
329
+ */
330
+ export declare function ensureOperator(args: EnsureOperatorArgs): Promise<EnsureOperatorResult>;
@@ -1,7 +1,12 @@
1
1
  'use strict';
2
2
 
3
- require('../../chunk-66IHPTOK.cjs');
4
- var chunk56UI7LUR_cjs = require('../../chunk-56UI7LUR.cjs');
3
+ var chunkWOEETTH7_cjs = require('../../chunk-WOEETTH7.cjs');
4
+ require('../../chunk-ZQUE2B3Q.cjs');
5
+ require('../../chunk-4ZCXK4VI.cjs');
6
+ require('../../chunk-FWHYQZ5E.cjs');
7
+ require('../../chunk-VR3FREBX.cjs');
8
+ var chunk2RNW4MIJ_cjs = require('../../chunk-2RNW4MIJ.cjs');
9
+ require('../../chunk-VJKZWYYJ.cjs');
5
10
  var react = require('react');
6
11
 
7
12
  function resolveUserDecryptor(source) {
@@ -35,7 +40,7 @@ function useDecryptedHandle(opts) {
35
40
  setState({
36
41
  status: "error",
37
42
  value: void 0,
38
- error: new chunk56UI7LUR_cjs.TokenOpsValidationError(
43
+ error: new chunk2RNW4MIJ_cjs.TokenOpsValidationError(
39
44
  "useDecryptedHandle: no `userDecryptor` resolved \u2014 pass `userDecryptor: () => useZamaSDK().relayer` (or an eager RelayerSDK / MockFhevmInstance).",
40
45
  { method: "useDecryptedHandle" }
41
46
  )
@@ -52,7 +57,7 @@ function useDecryptedHandle(opts) {
52
57
  });
53
58
  const plain = result[handleStr];
54
59
  if (plain === void 0) {
55
- throw new chunk56UI7LUR_cjs.TokenOpsValidationError(
60
+ throw new chunk2RNW4MIJ_cjs.TokenOpsValidationError(
56
61
  `useDecryptedHandle: relayer returned no value for handle ${handleStr} on contract ${contractStr} \u2014 check ACL grant + (handle, contractAddress, userAddress) triple`,
57
62
  { method: "useDecryptedHandle", contractAddress: contractStr }
58
63
  );
@@ -83,20 +88,28 @@ function serializableSlice(v) {
83
88
  return out;
84
89
  }
85
90
 
91
+ Object.defineProperty(exports, "useEnsureOperator", {
92
+ enumerable: true,
93
+ get: function () { return chunkWOEETTH7_cjs.useEnsureOperator; }
94
+ });
95
+ Object.defineProperty(exports, "useIsOperator", {
96
+ enumerable: true,
97
+ get: function () { return chunkWOEETTH7_cjs.useIsOperator; }
98
+ });
86
99
  Object.defineProperty(exports, "TokenOpsContractError", {
87
100
  enumerable: true,
88
- get: function () { return chunk56UI7LUR_cjs.TokenOpsContractError; }
101
+ get: function () { return chunk2RNW4MIJ_cjs.TokenOpsContractError; }
89
102
  });
90
103
  Object.defineProperty(exports, "TokenOpsSdkError", {
91
104
  enumerable: true,
92
- get: function () { return chunk56UI7LUR_cjs.TokenOpsSdkError; }
105
+ get: function () { return chunk2RNW4MIJ_cjs.TokenOpsSdkError; }
93
106
  });
94
107
  Object.defineProperty(exports, "TokenOpsValidationError", {
95
108
  enumerable: true,
96
- get: function () { return chunk56UI7LUR_cjs.TokenOpsValidationError; }
109
+ get: function () { return chunk2RNW4MIJ_cjs.TokenOpsValidationError; }
97
110
  });
98
111
  Object.defineProperty(exports, "isTokenOpsSdkError", {
99
112
  enumerable: true,
100
- get: function () { return chunk56UI7LUR_cjs.isTokenOpsSdkError; }
113
+ get: function () { return chunk2RNW4MIJ_cjs.isTokenOpsSdkError; }
101
114
  });
102
115
  exports.useDecryptedHandle = useDecryptedHandle;
@@ -7,4 +7,6 @@
7
7
  * `EncryptedHandle` return value).
8
8
  */
9
9
  export { useDecryptedHandle, type UseDecryptedHandleOptions, type UseDecryptedHandleResult, type UserDecryptor, type UserDecryptorSource, } from "./use-decrypted-handle.js";
10
+ export { useIsOperator, type UseIsOperatorOptions } from "./useIsOperator.js";
11
+ export { useEnsureOperator, type UseEnsureOperatorOptions, type EnsureOperatorVariables, } from "./useEnsureOperator.js";
10
12
  export { TokenOpsSdkError, isTokenOpsSdkError, TokenOpsValidationError, TokenOpsContractError, } from "../../core/errors.js";
@@ -7,4 +7,6 @@
7
7
  * `EncryptedHandle` return value).
8
8
  */
9
9
  export { useDecryptedHandle, type UseDecryptedHandleOptions, type UseDecryptedHandleResult, type UserDecryptor, type UserDecryptorSource, } from "./use-decrypted-handle.js";
10
+ export { useIsOperator, type UseIsOperatorOptions } from "./useIsOperator.js";
11
+ export { useEnsureOperator, type UseEnsureOperatorOptions, type EnsureOperatorVariables, } from "./useEnsureOperator.js";
10
12
  export { TokenOpsSdkError, isTokenOpsSdkError, TokenOpsValidationError, TokenOpsContractError, } from "../../core/errors.js";
@@ -1,6 +1,11 @@
1
- import '../../chunk-KWFFIJYX.js';
2
- import { TokenOpsValidationError } from '../../chunk-IVE3QEGD.js';
3
- export { TokenOpsContractError, TokenOpsSdkError, TokenOpsValidationError, isTokenOpsSdkError } from '../../chunk-IVE3QEGD.js';
1
+ export { useEnsureOperator, useIsOperator } from '../../chunk-U57COLUE.js';
2
+ import '../../chunk-FT4LNBOS.js';
3
+ import '../../chunk-4KJ66YRH.js';
4
+ import '../../chunk-7VELUOMI.js';
5
+ import '../../chunk-46T67CE2.js';
6
+ import { TokenOpsValidationError } from '../../chunk-DRSPMIZ7.js';
7
+ export { TokenOpsContractError, TokenOpsSdkError, TokenOpsValidationError, isTokenOpsSdkError } from '../../chunk-DRSPMIZ7.js';
8
+ import '../../chunk-Q2GP5UDC.js';
4
9
  import { useState, useRef, useEffect } from 'react';
5
10
 
6
11
  function resolveUserDecryptor(source) {
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Cross-product mutation hook over {@link ensureOperator} — check-then-set
3
+ * operator authorization that sends no transaction when the grant already
4
+ * exists.
5
+ *
6
+ * Lives in `/fhe/react` (not a product subpath) because the operator
7
+ * prerequisite is identical across all three product flows; the product
8
+ * `react/` barrels re-export it for discoverability.
9
+ */
10
+ import { type UseMutationResult } from "@tanstack/react-query";
11
+ import type { Account, Address } from "viem";
12
+ import type { SdkTelemetry } from "../../core/telemetry.js";
13
+ import { type EnsureOperatorResult } from "../operators.js";
14
+ /** Hook-level options for {@link useEnsureOperator}. */
15
+ export interface UseEnsureOperatorOptions {
16
+ /** Override the chain id used for client resolution. */
17
+ chainId?: number | undefined;
18
+ /** Optional telemetry adapter — forwarded to the headless helper. */
19
+ telemetry?: SdkTelemetry | undefined;
20
+ }
21
+ /** Per-mutation variables for {@link useEnsureOperator}. */
22
+ export interface EnsureOperatorVariables {
23
+ /** ERC-7984 token contract address. */
24
+ token: Address;
25
+ /** Address being authorized to pull tokens. */
26
+ spender: Address;
27
+ /**
28
+ * `uint48` unix timestamp after which the authorization expires. Required
29
+ * — the expiry window is an explicit caller decision (see
30
+ * {@link ensureOperator}). Pass `ERC7984_OPERATOR_MAX_DEADLINE` only when
31
+ * you explicitly want a never-expiring grant.
32
+ */
33
+ deadline: bigint;
34
+ /** Override the from-account; defaults to the connected wallet's account. */
35
+ account?: Account | Address;
36
+ /** If `true` (default), waits for the transaction to be mined. */
37
+ waitForReceipt?: boolean;
38
+ }
39
+ /**
40
+ * Ensure `spender` is an ERC-7984 operator for the connected account on
41
+ * `token`, sending a `setOperator` transaction only when the grant is
42
+ * missing. Resolves to `{ alreadyOperator, hash }` — `hash` is `null` on
43
+ * the no-op path.
44
+ *
45
+ * This is the mutation half of the operator-prerequisite pair; use
46
+ * {@link useIsOperator} for the read side, and invalidate/refetch it after
47
+ * this mutation succeeds.
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * const ensure = useEnsureOperator();
52
+ * const oneHour = BigInt(Math.floor(Date.now() / 1000) + 3600);
53
+ * ensure.mutate({ token, spender: manager, deadline: oneHour });
54
+ * ```
55
+ */
56
+ export declare function useEnsureOperator(options?: UseEnsureOperatorOptions): UseMutationResult<EnsureOperatorResult, Error, EnsureOperatorVariables>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Cross-product query hook over {@link isOperator} — "is `spender` currently
3
+ * an authorized ERC-7984 operator for `holder` on `token`?".
4
+ *
5
+ * Lives in `/fhe/react` (not a product subpath) because the operator
6
+ * prerequisite is identical across all three product flows; the product
7
+ * `react/` barrels re-export it for discoverability.
8
+ */
9
+ import { type UseQueryResult } from "@tanstack/react-query";
10
+ import type { Address } from "viem";
11
+ import type { SdkTelemetry } from "../../core/telemetry.js";
12
+ /** Options accepted by {@link useIsOperator}. */
13
+ export interface UseIsOperatorOptions {
14
+ /** ERC-7984 token contract address. Query stays disabled while `undefined`. */
15
+ token?: Address | undefined;
16
+ /**
17
+ * Address whose operator status is being checked — typically a manager
18
+ * clone, an airdrop factory/clone, or the disperse singleton. Query stays
19
+ * disabled while `undefined`.
20
+ */
21
+ spender?: Address | undefined;
22
+ /**
23
+ * Account that owns the tokens. Defaults to the connected wallet's
24
+ * account. Query stays disabled while neither is available.
25
+ */
26
+ holder?: Address | undefined;
27
+ /** Override the chain id used for client resolution. */
28
+ chainId?: number | undefined;
29
+ /** Set `false` to pause the query without unmounting. Defaults to `true`. */
30
+ enabled?: boolean | undefined;
31
+ /** Optional telemetry adapter — forwarded to the headless helper. */
32
+ telemetry?: SdkTelemetry | undefined;
33
+ }
34
+ /**
35
+ * Read `spender`'s ERC-7984 operator status for `holder` on `token`.
36
+ *
37
+ * Typical use: gate the "create" call-to-action behind the operator
38
+ * prerequisite so builders never hit {@link OperatorNotApprovedError} — the
39
+ * SDK's mapping of the on-chain `ERC7984UnauthorizedSpender` revert, whose
40
+ * message is the canonical {@link OPERATOR_NOT_APPROVED_REMEDIATION} copy:
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const { data: authorized } = useIsOperator({ token, spender: manager });
45
+ * return authorized
46
+ * ? <CreateVestingButton />
47
+ * : <ApproveOperatorButton />; // wire to useEnsureOperator
48
+ * ```
49
+ *
50
+ * After a successful {@link useEnsureOperator} / `setOperator` mutation,
51
+ * invalidate this query (its key starts with
52
+ * `["tokenops-sdk", "fhe", "isOperator", ...]`) or refetch it.
53
+ */
54
+ export declare function useIsOperator(options?: UseIsOperatorOptions): UseQueryResult<boolean, Error>;
@@ -1,11 +1,12 @@
1
1
  import type { PublicClient, WalletClient } from "viem";
2
2
  import type { Encryptor } from "../fhe-vesting/encryption.js";
3
- import { type SepoliaEncryptorLogger } from "./sepolia-encryptor.js";
3
+ import { type EncryptorInitPhase, type SepoliaEncryptorLogger } from "./sepolia-encryptor.js";
4
4
  /**
5
5
  * Configuration for {@link createSepoliaEncryptorWeb}. Minimal by design:
6
- * the browser path doesn't expose worker pool sizing or storage knobs (the
7
- * Web Worker is single-threaded by default, and `RelayerWeb` already
8
- * defaults to `IndexedDBStorage` for FHE artifact caching).
6
+ * the browser path doesn't expose storage knobs (`RelayerWeb` already
7
+ * defaults to `IndexedDBStorage` for FHE artifact caching). Worker-pool
8
+ * sizing is the one opt-in performance knob — see `threads`; the default
9
+ * stays single-threaded so no special headers are required.
9
10
  */
10
11
  export interface CreateSepoliaEncryptorWebOpts {
11
12
  /** Viem `PublicClient` — used only to resolve the chain id for the
@@ -42,6 +43,46 @@ export interface CreateSepoliaEncryptorWebOpts {
42
43
  * background on why).
43
44
  */
44
45
  logger?: SepoliaEncryptorLogger;
46
+ /**
47
+ * Observe the encryptor's initialization lifecycle (see
48
+ * {@link EncryptorInitPhase}) — the signal to drive a loading state around
49
+ * the first in-browser encryption, which downloads several MB of FHE
50
+ * public material.
51
+ *
52
+ * `RelayerWeb` initializes lazily (Web Worker spawn + FHE param fetch
53
+ * happen inside the first operation), so phases map onto its REAL status
54
+ * events, not timers:
55
+ *
56
+ * - `"initializing"` fires synchronously while this helper constructs the
57
+ * `RelayerWeb` instance.
58
+ * - `"downloading-params"` fires when the worker's lazy init actually
59
+ * starts (upstream status `"initializing"`) — worker bring-up plus the
60
+ * FHE public key + params fetch. Near-instant when the IndexedDB
61
+ * artifact cache is warm.
62
+ * - `"ready"` fires when upstream reports `"ready"`.
63
+ *
64
+ * `"downloading-params"` / `"ready"` can fire again if the underlying
65
+ * relayer re-initializes (chain switch, stale artifact cache). Init
66
+ * failures surface as rejections from the pending operation — there is no
67
+ * error phase. No fake progress: no percentages, byte counts, or timers —
68
+ * upstream exposes no download-progress API. Callback exceptions are
69
+ * swallowed so a UI observer can never break encryption.
70
+ */
71
+ onPhase?: (phase: EncryptorInitPhase) => void;
72
+ /**
73
+ * Opt-in WASM worker-pool size for multi-threaded encryption (forwarded
74
+ * to `RelayerWeb`'s config). `4`–`8` gives ~2–3× faster encryption but
75
+ * requires the page to be **cross-origin isolated** — headers
76
+ * `Cross-Origin-Opener-Policy: same-origin` and
77
+ * `Cross-Origin-Embedder-Policy: require-corp` — without which browsers
78
+ * hide `SharedArrayBuffer` and encryption silently falls back to
79
+ * single-threaded. The SDK logs a loud warning when `threads > 1` is set
80
+ * on a non-isolated page.
81
+ *
82
+ * Omit for the default single-threaded worker — no special headers
83
+ * required.
84
+ */
85
+ threads?: number;
45
86
  }
46
87
  /**
47
88
  * Returned by {@link createSepoliaEncryptorWeb}. Same structural
@@ -5,6 +5,24 @@ import type { Encryptor } from "../fhe-vesting/encryption.js";
5
5
  export declare const SEPOLIA_CHAIN_ID: 11155111;
6
6
  /** Mainnet chain id (`1`). Same use as {@link SEPOLIA_CHAIN_ID}. */
7
7
  export declare const MAINNET_CHAIN_ID: 1;
8
+ /**
9
+ * Lifecycle phase reported by the `onPhase` callback of
10
+ * {@link createSepoliaEncryptor} / `createSepoliaEncryptorWeb`.
11
+ *
12
+ * - `"initializing"` — the helper started constructing the underlying Zama
13
+ * relayer instance (module resolution + constructor; cheap and local).
14
+ * - `"downloading-params"` — the relayer's real lazy initialization began:
15
+ * worker bring-up plus fetching the FHE public key and public params
16
+ * (several MB on a cold cache; near-instant when served from cache).
17
+ * - `"ready"` — initialization completed; subsequent encrypts skip the
18
+ * download entirely.
19
+ *
20
+ * Phases carry **no** percentages, byte counts, or time estimates — neither
21
+ * `@zama-fhe/sdk@3` nor the underlying relayer SDK exposes any
22
+ * download-progress API, so anything finer-grained would be fabricated.
23
+ * If upstream later exposes real progress this union will widen.
24
+ */
25
+ export type EncryptorInitPhase = "initializing" | "downloading-params" | "ready";
8
26
  /** Subset of `@zama-fhe/sdk/node`'s `GenericLogger` we accept. Mirrored
9
27
  * structurally so consumers don't need to import the optional peer dep just
10
28
  * to type-narrow a logger arg. */
@@ -69,6 +87,26 @@ export interface CreateSepoliaEncryptorOptions {
69
87
  * that want to exercise the fetch path).
70
88
  */
71
89
  fheArtifactCacheTTL?: number;
90
+ /**
91
+ * Observe the encryptor's initialization lifecycle (see
92
+ * {@link EncryptorInitPhase}). `RelayerNode` initializes its worker pool
93
+ * lazily and exposes no status events, so the phases bracket the first
94
+ * operation that triggers the real init:
95
+ *
96
+ * - `"initializing"` fires when this helper starts (module load +
97
+ * `RelayerNode` construction).
98
+ * - `"downloading-params"` fires immediately before the first `encrypt`
99
+ * call, which is what actually spawns the worker pool and fetches the
100
+ * FHE public key + params.
101
+ * - `"ready"` fires once that first `encrypt` resolves. If it rejects,
102
+ * `"ready"` is not fired and the next attempt re-fires
103
+ * `"downloading-params"` (the underlying SDK retries init from scratch).
104
+ *
105
+ * No fake progress: no percentages, byte counts, or timers — upstream
106
+ * exposes no download-progress API. Callback exceptions are swallowed so
107
+ * a UI observer can never break encryption.
108
+ */
109
+ onPhase?: (phase: EncryptorInitPhase) => void;
72
110
  }
73
111
  /**
74
112
  * Returned by {@link createSepoliaEncryptor}. Carries the underlying