@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/CHANGELOG.md CHANGED
@@ -5,6 +5,152 @@ All notable changes to `@tokenops/sdk` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.6.0](https://github.com/VestingLabs/tokenops-sdk/compare/v1.5.1...v1.6.0) (2026-07-28)
9
+
10
+
11
+ ### Features
12
+
13
+ * **fhe:** add operator prerequisite DX helpers (S2, TOK-75) ([#63](https://github.com/VestingLabs/tokenops-sdk/issues/63)) ([ef45c2e](https://github.com/VestingLabs/tokenops-sdk/commit/ef45c2e61529f57a2b2338c7573b466e57fbf2cf))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **airdrop:** stop preflightCreateAirdrop throwing on a non-ERC7984 token (TOK-436) ([b650324](https://github.com/VestingLabs/tokenops-sdk/commit/b6503243cb430fe224913fbbb39251586a4d4925))
19
+ * correct operator-deadline guidance in SECURITY.md, record advisory rationale ([b09273d](https://github.com/VestingLabs/tokenops-sdk/commit/b09273d7d911a4dcfdfe8e2ae7f4e3a24c2130e8))
20
+ * **fhe-vesting:** point Sepolia factory at the redeployed address ([0888404](https://github.com/VestingLabs/tokenops-sdk/commit/08884040987d0ebb4326d1fb80a2c2d630a6e3a6))
21
+ * **vesting:** stop preflightCreateVesting throwing on a non-ERC7984 token ([dfbc795](https://github.com/VestingLabs/tokenops-sdk/commit/dfbc795fa7f0fd2aee9e1abfb711e82bca22ec4c))
22
+
23
+ ## [Unreleased]
24
+
25
+ ### Added — operator prerequisite DX helpers (TOK-75)
26
+
27
+ - **`isOperator()` and `ensureOperator()`** — new headless operator-prerequisite helpers on `@tokenops/sdk/fhe`. `isOperator({ publicClient, token, holder, spender })` is a read-only check of `spender`'s ERC-7984 operator status for `holder` on `token` (an expired grant reads `false` — expiry needs no revoke tx). `ensureOperator({ publicClient, walletClient, token, spender, deadline, account?, waitForReceipt? })` is the idempotent check-then-set: it resolves `{ alreadyOperator: true, hash: null }` without sending a transaction when the grant is already active (cost: one `eth_call`), and otherwise performs the `setOperator` write and returns its hash. `deadline` is **required** — unlike `setOperator`, `ensureOperator` refuses to silently default a security-sensitive approval window to the never-expiring `ERC7984_OPERATOR_MAX_DEADLINE`; pass that constant explicitly if forever is what you want. Out-of-range deadlines (`<= 0` or `> 2**48 - 1`) and unresolvable accounts throw `TokenOpsContractError` before any RPC call. Caveat (documented in TSDoc): the pre-check confirms the grant is currently active, not until when — `ensureOperator` does not refresh a grant that expires mid-flow; call `setOperator` directly to extend a window.
28
+ - **`useIsOperator` and `useEnsureOperator` React hooks** — the same prerequisite pair for wagmi + TanStack Query apps, exported from `@tokenops/sdk/fhe/react` and re-exported from `@tokenops/sdk/fhe-vesting/react`, `@tokenops/sdk/fhe-airdrop/react`, and `@tokenops/sdk/fhe-disperse/react`. `useIsOperator({ token, spender, holder?, chainId?, enabled? })` returns `UseQueryResult<boolean, Error>` — `holder` defaults to the connected account and the query stays disabled until `token`, `holder`, and `spender` are all present. Query key: `["tokenops-sdk", "fhe", "isOperator", chainId, token, holder, spender]` (lowercased addresses) — invalidate it after a successful grant. `useEnsureOperator({ chainId? })` returns `UseMutationResult<EnsureOperatorResult, Error, EnsureOperatorVariables>` and throws `MissingPublicClientError` / `MissingWalletClientError` when the wagmi clients are unavailable.
29
+ - New exported types: `IsOperatorArgs`, `EnsureOperatorArgs`, `EnsureOperatorResult` from `@tokenops/sdk/fhe`; `UseIsOperatorOptions`, `UseEnsureOperatorOptions`, `EnsureOperatorVariables` from `@tokenops/sdk/fhe/react` and the three product `/react` subpaths.
30
+ - `ConfidentialVestingManagerClient.createVesting` and `.batchCreateVesting` TSDoc now documents the operator prerequisite (set the manager as operator via `token.setOperator(...)` or `ensureOperator()`) and the `OperatorNotApprovedError` thrown when it is skipped. Documentation only; no runtime change.
31
+
32
+ ### Added — typed approval blockers in `preflightDisperse` (TOK-69)
33
+
34
+ - **`SubwalletsNotApprovedError` and `SingletonNotApprovedError`** — `ConfidentialDisperseClient.preflightDisperse` now pushes typed errors into `PreflightReport.blockerErrors[]` for the two approval checks that were previously string-only entries in the deprecated `blockers: string[]`: `SubwalletsNotApprovedError` (`TOKENOPS_SUBWALLETS_NOT_APPROVED`, `context.{wallet0Approved,wallet1Approved}`; emitted in `"wallet"` / `"wallet-token-fee"` modes when the **registered** sub-wallet pair is not fully approved — unregistered users get only `NotRegisteredError`, with approval state still reported informationally via `hasApprovedSubwallets`) and `SingletonNotApprovedError` (`TOKENOPS_SINGLETON_NOT_APPROVED`; emitted in `"direct"` mode when the sender has not approved the singleton as ERC-7984 operator). Both classes are exported from `@tokenops/sdk/fhe-disperse` and `/fhe-disperse/react`; both codes join the `TokenOpsSdkErrorCode` union. Errors are collected, never thrown — the preflight contract is unchanged.
35
+
36
+ ### Added
37
+
38
+ - **`Encryptor`, `EncryptorSource`, and `FheValueInput` re-exported from `@tokenops/sdk/fhe`** — next to the `createSepoliaEncryptor*` / `createMockEncryptor` factories that produce them. Previously these types were only importable from the product subpaths even though the factories live on `/fhe` (TOK-69).
39
+ - **Opt-in `threads` option on `createSepoliaEncryptorWeb`** — `CreateSepoliaEncryptorWebOpts.threads?: number` is forwarded to the Zama relayer's WASM worker pool for multi-threaded encryption (~2–3× faster at 4–8 threads). Requires a cross-origin-isolated page (`Cross-Origin-Opener-Policy: same-origin` + `Cross-Origin-Embedder-Policy: require-corp`); when `threads > 1` is set on a non-isolated page the SDK logs a warning (configured logger or `console.warn`) explaining that encryption will silently run single-threaded and which headers to add. Omitting `threads` keeps the single-threaded default (TOK-69).
40
+
41
+ ### Changed
42
+
43
+ - **Sepolia `confidentialVestingFactory` address updated to `0x059d6Bb8ff9a13E794fe416d4757d7310CdC69ab`** (was `0xA87701CE9A52D43681600583a99c85b50DbE3150`). The factory was redeployed with the post-audit fix that packs a constant zero as the clone's deployment-block-number immutable arg (the `DEPLOYMENT_BLOCK_NUMBER` getter surfaced as `ConfidentialVestingManagerClient.deploymentBlockNumber()`), and with `defaultGasFee = 0` instead of `3500000000000`. `getFheVestingFactoryAddress(sepolia.id)` and `DEPLOYED_ADDRESSES.fheVesting.confidentialVestingFactory[11155111]` now resolve to the new address. Managers already created through the old factory are unaffected and remain usable — construct their clients with an explicit `address`. Side effect of the constant-zero packing: `predictManagerAddress` against the new factory is block-stable (predict-then-deploy works on Sepolia again), unlike the old factory where the packed `block.number` made predictions single-block.
44
+ - **`createMockEncryptor` / `createSepoliaEncryptor` peer-dependency errors now carry `cause`** — the `Error` thrown when a required peer dependency fails to dynamically import now attaches the original import failure as `error.cause`, restoring the error chain for programmatic inspection. Message unchanged; no migration needed.
45
+
46
+ ### Deprecated
47
+
48
+ - **`useGet*` encrypted-view hooks renamed to `useAccess*`** (TOK-69) — the encrypted-view hooks are mutations that submit a transaction, and the `useGet*` names read as free queries, which misled integrators. `@tokenops/sdk/fhe-vesting/react` now exports `useAccessVestedAmount`, `useAccessClaimableAmount`, `useAccessTotalAllocation`, `useAccessSettledAmount` (plus `UseAccess*Args` types); `@tokenops/sdk/fhe-airdrop/react` now exports `useAccessClaimAmount`. The old `useGetVestedAmount` / `useGetClaimableAmount` / `useGetTotalAllocation` / `useGetSettledAmount` / `useGetClaimAmount` names (and `UseGet*Args` types) remain exported as `@deprecated` aliases of the same function objects — identical behavior, arguments, and thrown errors — and will be removed in the next major. Migration: rename the import; nothing else changes. Only visible delta: the `MissingClientError` raised when no client is available now names the `useAccess*` hook in its message and `context.hook`, even when called via the alias.
49
+
50
+ ### Fixed
51
+
52
+ - **`preflightCreateAirdrop` no longer throws on a non-ERC-7984 token** (TOK-436). Passing a `params.token` that is not a confidential token — a deployed contract without the `isOperator(address,address)` selector, or an EOA/undeployed address — previously rejected the preflight's internal `Promise.all` and threw the raw viem error out of `ConfidentialAirdropFactoryClient.preflightCreateAirdrop`. It now resolves with `ready: false` and a typed `InvalidArgumentError` (`TOKENOPS_INVALID_ARGUMENT`, `context.argument: "params.token"`) in `blockers`/`blockerErrors`; `isFactoryOperator` reports `false` (undetermined). Infrastructure failures (HTTP errors, timeouts) still propagate, so an RPC outage is never misreported as a bad token. `usePreflightCreateAirdrop` now resolves with the report instead of entering its query error state.
53
+ - **`preflightCreateVesting` no longer throws on a non-ERC-7984 token.** Same fix on `ConfidentialVestingManagerClient.preflightCreateVesting`: a failed `isOperator` read on the manager's bound distribution token becomes a typed `InvalidArgumentError` blocker instead of a thrown error; `isOperatorSet` reports `false` (undetermined). Affects `usePreflightCreateVesting` the same way.
54
+ - **Duplicate operator blocker suppressed in both create-preflights.** An invalid token previously produced two blockers in `preflightCreateAirdrop` — the token `InvalidArgumentError` plus a spurious `OperatorNotApprovedError` telling the user to approve an operator on an address that cannot hold a token (and the same double-report would have appeared in `preflightCreateVesting` once its `isOperator` failure became a blocker). Both preflights now emit the operator blocker only when the token was actually readable, so checklist UIs get exactly one actionable item. Migration note: consumers who caught a thrown error from these preflights to detect a bad token must switch to inspecting `report.blockerErrors`; don't render approve-operator prompts from `isFactoryOperator` / `isOperatorSet` alone — check `blockerErrors` for `TOKENOPS_OPERATOR_NOT_APPROVED`.
55
+
56
+ ### Fixed — docs sweep
57
+
58
+ - `CreateAirdropPreflightReport.isFactoryOperator` TSDoc: clarified that `false` also covers "could not be determined" (structurally invalid token address, or a non-ERC-7984 token whose operator read failed and became a blocker), not just "not approved" (TOK-436).
59
+ - `ConfidentialAirdropClient.claim` + `useClaim` TSDoc: the silent-zero note (insufficient encrypted balance moves an encrypted zero rather than reverting) must not be generalized to a never-funded pool — that claim reverts with `FheHandleNotAllowedError`, and `preflightClaim` cannot detect it because the pool balance is an encrypted `euint64` the SDK cannot read. Previously this warning appeared only on the preflight surface (TOK-434).
60
+ - `useAdminPartialClaim` TSDoc: documents the 6-decimals convention of TokenOps confidential (ERC-7984) tokens (1 token = 1_000_000 base units); the hook shares `PartialClaimArgs` with `usePartialClaim`, and an 18-decimals assumption over-claims by 10^12 (TOK-435).
61
+ - Deleted the dead, never-exported `useAirdropClaim` hook file — it was absent from the `@tokenops/sdk/fhe-airdrop/react` barrel so no consumer could reach it, and its JSDoc described a plaintext-`amount` re-encryption flow the SDK deliberately does not implement (the admin signature commits to a specific handle). The exported `useClaim` is the real claim hook. No API change (TOK-437).
62
+ - Fixed four `@example` blocks on `@tokenops/sdk/fhe` (`mintMockERC7984`, `createMockErc7984Client`, `setOperator`, `revokeOperator`) that referenced the nonexistent `DEPLOYED_ADDRESSES.tokens.testConfidential[...]` path (the registry has no `tokens` key) — two also imported symbols from entry points that don't export them. Examples now use placeholder addresses and every import specifier resolves.
63
+ - `SECURITY.md` operator-deadline guidance corrected: the old text misspelled the constant (`ERC_7984_OPERATOR_MAX_DEADLINE`), recommended the never-expiring deadline for production (the inverse of the `setOperator` TSDoc guidance), and claimed a past-deadline warning that does not exist. It now matches actual behavior: the SDK validates only that the deadline is within uint48 range; use `ERC7984_OPERATOR_MAX_DEADLINE` for dev loops and fixtures, scope deadlines (and `revokeOperator`) in production.
64
+ - `SECURITY.md` gains a "Known Advisories" section: remaining open Dependabot alerts (`axios` via `wagmi` → `@wagmi/connectors` → `@base-org/account` → `@coinbase/cdp-sdk`; `hono` via `porto`) are reachable only through the consumer's own `wagmi` peer tree, not through the SDK's sole runtime dependency (`abitype`); the repo's `pnpm.overrides` protect only this repo's lockfile, so consumer-side `npm audit` findings there are remediated upstream in wagmi.
65
+
66
+ ### Security
67
+
68
+ - Development-tree dependency floors raised to clear 20+ Dependabot alerts — brace-expansion CVE-2026-14257 across the 1.x / 2.x / 5.x lines (floors 1.1.16 / 2.1.3 / 5.0.8), postcss GHSA-r28c-9q8g-f849 (pinned 8.5.23), plus axios / fast-uri / hono / js-yaml / linkify-it advisories — and eslint upgraded 9 → 10. **Dev-tree only:** all of these live in `devDependencies` / `pnpm.overrides`; the published package's runtime dependency (`abitype ^1.0.8`) and every peerDependency range are unchanged, so consumer dependency trees are unaffected.
69
+
70
+ ## [1.5.1] - 2026-07-23
71
+
72
+ _Republish baseline._ No code or API changes — the package content is identical to 1.5.0 apart from the version number. Published minutes after 1.5.0 as the last release of the same 2026-07-23 per-merge auto-publish run; later that day 1.2.0–1.5.0 were unpublished from npm (burned versions; npm forbids reusing them), leaving **1.5.1 as the first npm-installable version that actually delivers everything from [1.2.0] through [1.5.0]** to consumers. (The repo-only `BOUNTY_DX_TRIAGE.md` added in #56 is not part of the npm tarball.)
73
+
74
+ ## [1.5.0] - 2026-07-23 [YANKED]
75
+
76
+ _Published and unpublished from npm the same day; its changes reach consumers via [1.5.1]._
77
+
78
+ ### Added
79
+
80
+ - **wagmi v3 peer support** (S7, TOK-70, #57) — the `wagmi` peer dependency range widens from `^2.0.0` to `^2.0.0 || ^3.0.0`; wagmi v2 remains fully supported and no other peer range changes. wagmi v3 renamed `useAccount` to `useConnection`, and the SDK now resolves the installed major's account hook internally, so the three hooks that default to the connected wallet — `useAirdropIsSignatureValid` (`@tokenops/sdk/fhe-airdrop/react`), `useConfidentialBalance` and `useUnderlyingBalance` (`@tokenops/sdk/testnet-faucet/react`) — work under either major with no code change; all hook signatures and return types are unchanged, and `usePublicClient` / `useWalletClient` usage is unaffected. README gains a "wagmi v2 and v3" section documenting dual-major support and how to diagnose `ERESOLVE` errors after a wagmi v3 upgrade (`npm ls wagmi` / `pnpm why wagmi` — the offending peer range usually belongs to another package, not `@tokenops/sdk`).
81
+
82
+ ## [1.4.2] - 2026-07-23 [YANKED]
83
+
84
+ _Published and unpublished from npm the same day; its changes reach consumers via [1.5.1]._
85
+
86
+ ### Docs
87
+
88
+ - **6-decimals amount convention surfaced at every amount-taking entry point** (S6, TOK-71, #65) — a canonical TSDoc note now ships on each public API that takes a token amount: TokenOps confidential (ERC-7984) tokens use a 6-decimals convention (1 token = 1_000_000 base units), not the 18 decimals typical of ERC-20; amount parameters are base units of the token's actual decimals — for the CTTT test token (6 decimals) `1_000_000n` = 1 CTTT, while the transparent TTT test token uses 18 decimals. Surfaced on the exported `encryptUint64` helpers of all three products (covering `encryptUint64Batch` values), `computeSubtotals`, `ConfidentialVestingManagerClient.{createVesting, batchCreateVesting, partialClaim, adminPartialClaim, withdrawAdmin, withdrawTokenFee}`, `ConfidentialAirdropFactoryClient.{createAndFundConfidentialAirdrop, fundConfidentialAirdrop, createAndFundConfidentialAirdropAndGetAddress}`, `ConfidentialDisperseClient.{calculateFee, preflightDisperse, disperse, withdrawTokenFee}`, and the corresponding amount-taking React hooks in `/fhe-vesting/react`, `/fhe-airdrop/react`, and `/fhe-disperse/react`. Documentation-only; no branded decimals type was introduced and no runtime behavior changed.
89
+
90
+ ## [1.4.1] - 2026-07-23 [YANKED]
91
+
92
+ _Published and unpublished from npm the same day; its changes reach consumers via [1.5.1]._
93
+
94
+ ### Docs
95
+
96
+ - **Silent-zero transfer warning** (S5, TOK-72, #58) — every public method and React mutation hook that moves confidential tokens or funds a contract now carries a canonical TSDoc warning: ERC-7984 transfers do not revert when the sender's encrypted balance is insufficient — the transfer succeeds and moves an encrypted zero instead (by design: reverting would leak balance information), so the transaction receipt alone cannot tell you whether value actually moved. Applied to `ConfidentialVestingManagerClient.{createVesting, batchCreateVesting, claim, adminClaim, partialClaim, adminPartialClaim}`, `ConfidentialAirdropClient.claim`, `ConfidentialAirdropFactoryClient.{createAndFundConfidentialAirdrop, fundConfidentialAirdrop, createAndFundConfidentialAirdropAndGetAddress}`, `ConfidentialDisperseClient.disperse`, and the corresponding claim / fund / disperse mutation hooks in the three `/react` subpaths. Documentation-only; no runtime behavior changed.
97
+
98
+ ## [1.4.0] - 2026-07-23 [YANKED]
99
+
100
+ _Published and unpublished from npm the same day; its changes reach consumers via [1.5.1]._
101
+
102
+ ### Added
103
+
104
+ - **`EncryptorInitPhase` and the `onPhase` init-phase callback** (S4, TOK-73, #61) — new exported type on `@tokenops/sdk/fhe`: the union `"initializing" | "downloading-params" | "ready"`, plus an optional `onPhase?: (phase: EncryptorInitPhase) => void` option on both `createSepoliaEncryptorWeb` (`CreateSepoliaEncryptorWebOpts`) and `createSepoliaEncryptor` (`CreateSepoliaEncryptorOptions`). Motivation: the first in-browser encryption downloads several MB of FHE public material with no signal — `onPhase` gives UIs a loading-state hook. Web semantics: `"initializing"` fires during relayer construction, `"downloading-params"` when the Web Worker's lazy init and FHE key/params download actually begin (near-instant on a warm IndexedDB cache), `"ready"` on completion; the last two can re-fire if the relayer re-initializes (e.g. chain switch). Node semantics: `RelayerNode` exposes no status events, so phases bracket the first `encrypt()` call; a failed first attempt re-fires `"downloading-params"` on the next call. Deliberate design points: no fake progress (the Zama SDKs expose no download-progress API), no error phase (init failures surface as rejections from the pending operation), and callback exceptions are swallowed so a throwing UI observer can never break the encryption path.
105
+
106
+ ## [1.3.0] - 2026-07-23 [YANKED]
107
+
108
+ _Published and unpublished from npm the same day; its changes reach consumers via [1.5.1]._
109
+
110
+ ### Added — create-side preflight for vesting and airdrop (S3, TOK-74, #59)
111
+
112
+ - **`ConfidentialVestingManagerClient.preflightCreateVesting({ params, creator })`** (`@tokenops/sdk/fhe-vesting`) — read-only, opt-in create-side preflight for `createVesting` / `batchCreateVesting`. Returns the new `CreateVestingPreflightReport` (`token`, `hasCreatorRole`, `isOperatorSet`, `ready`, `blockers`, `blockerErrors`) reporting structural param violations (`InvalidArgumentError`, mirroring the contract's `_validateVestingParams` invariants), missing `VESTING_CREATOR_ROLE` (`AccessDeniedError`), and missing ERC-7984 operator approval of the manager on the distribution token (`OperatorNotApprovedError`). `blockerErrors` are typed `TokenOpsSdkError`s index-aligned with the human-readable `blockers` strings — branch on `error.code`. RPC failures propagate rather than producing a false report; paused state is deliberately not a blocker (pause only blocks claims). Documented not-detectable: the creator's encrypted `euint64` balance (an underfunded create silently zeroes amounts via `FHE.select` instead of reverting) and `batchCreateVesting`'s `maxBatchSize()`.
113
+ - **`ConfidentialAirdropFactoryClient.preflightCreateAirdrop(args)`** (`@tokenops/sdk/fhe-airdrop`) — same for `createConfidentialAirdrop` / `createAndFundConfidentialAirdrop`, with new `PreflightCreateAirdropArgs` (`params`, `creator`, optional `userSalt`, optional `funding`) and `CreateAirdropPreflightReport` (`isFactoryOperator`, `gasFee`, `predictedAirdrop?`, `alreadyDeployed?`, `ready`, `blockers`, `blockerErrors`) types. Validates params structurally (non-zero `token` / `admin`, timestamp ordering, `endTimestamp` in the future), reports the resolved per-claim `gasFee` (creator's custom fee when enabled, else `defaultGasFee`), treats a missing factory-operator approval as a blocker only with `funding: true` (informational via `isFactoryOperator` otherwise), and — when `userSalt` is supplied — predicts the CREATE2 clone address and flags an existing deployment as an `AlreadyInitializedError` blocker.
114
+ - **`usePreflightCreateVesting`** (`@tokenops/sdk/fhe-vesting/react`) and **`usePreflightCreateAirdrop`** (`@tokenops/sdk/fhe-airdrop/react`) — TanStack Query wrappers over the two preflights (disabled until `params` and `creator` are set, `staleTime: 0`), with new `UsePreflightCreateVestingArgs` / `UsePreflightCreateAirdropArgs` types; the report types are re-exported from the `/react` subpaths.
115
+
116
+ ### Changed
117
+
118
+ - **`OperatorNotApprovedError` gains optional `tokenAddress`** — constructor args and `context` now carry `tokenAddress?: Address` identifying the ERC-7984 token whose operator approval is missing; populated by the new create-side preflights where the token is statically known. Additive and optional — no migration needed.
119
+
120
+ ## [1.2.0] - 2026-07-23 [YANKED]
121
+
122
+ _Published and unpublished from npm the same day; its changes reach consumers via [1.5.1]._
123
+
124
+ ### Added — ERC-7984 reverts translated into actionable errors (S1, TOK-76, #60)
125
+
126
+ - **`OperatorNotApprovedError`** — new typed error with code `TOKENOPS_OPERATOR_NOT_APPROVED` (new member of the `TokenOpsSdkErrorCode` union), thrown when an ERC-7984 confidential token rejects a TokenOps contract as spender (`ERC7984UnauthorizedSpender`, selector `0x79f2cb38`) because the holder never called `setOperator()` — the common failure in vesting creation, airdrop funding, and disperse token pulls. Carries `context.holder` and `context.spender` decoded from the revert, and a remediation-first message telling you to call `setOperator()` via the `/fhe` operator helpers. Exported from the root entrypoint and from `/fhe`, `/fhe-vesting`, `/fhe-airdrop`, `/fhe-disperse`, `/testnet-faucet`, and each product's `/react` subpath.
127
+ - **`OPERATOR_NOT_APPROVED_REMEDIATION`** — exported string constant (root entrypoint) holding the canonical `OperatorNotApprovedError` message, for surfaces that need the exact remediation copy.
128
+
129
+ ### Changed
130
+
131
+ - **ERC-7984 revert decoding across all clients** — `ConfidentialVestingManagerClient`, `ConfidentialVestingFactoryClient`, `ConfidentialAirdropClient`, `ConfidentialAirdropFactoryClient`, `ConfidentialDisperseClient`, and `TestnetFaucetClient` now decode token-originated ERC-7984 standard reverts that appear in none of the vesting / airdrop / disperse product ABIs (the testnet faucet's confidential test-token ABI already declared most of them; 1.2.0 adds the shared decode set and the name-based mapping for every client). Besides `ERC7984UnauthorizedSpender` → `OperatorNotApprovedError`, the sibling errors `ERC7984UnauthorizedCaller`, `ERC7984UnauthorizedUseOfEncryptedAmount`, `ERC7984InvalidReceiver`, `ERC7984InvalidSender`, `ERC7984ZeroBalance`, and `ERC7984InvalidGatewayRequest` now surface as `ContractRevertError` with `context.revertName` / `revertArgs` populated instead of a raw selector hex string (exception: `ERC7984InvalidReceiver` on `TestnetFaucetClient` keeps its pre-existing `InvalidArgumentError` mapping via the faucet product mapper). Works across the simulate, wallet-transport, and raw-RPC error paths; errors declared in the product ABI keep precedence (e.g. `EnforcedPause` still maps to `PausedError`). Migration: code matching raw `0x79f2cb38` data or selector-hex `ContractRevertError` messages should switch to `instanceof OperatorNotApprovedError` / `error.code === "TOKENOPS_OPERATOR_NOT_APPROVED"`, or to `context.revertName`.
132
+
133
+ ## [1.1.1] - 2026-06-23
134
+
135
+ Release-pipeline fixes only (npm OIDC trusted publishing via npm >= 11.5.1; `--provenance` dropped because npm rejects it for private packages); no consumer-facing changes relative to [1.1.0] — the published contents are identical apart from the version field and dev-only package.json fields (`devDependencies` / `pnpm.overrides`). Because 1.1.0 never reached npm, **1.1.1 is the first published release of the 1.1.x line** and the version that delivered the testnet-faucet subpath to npm consumers. Dev-tree dependency bumps only (`vitest` 2 → 3, `pnpm.overrides` churn); `dependencies` / `peerDependencies` unchanged.
136
+
137
+ ## [1.1.0] - 2026-06-22
138
+
139
+ _Tagged but never published — the release workflow failed on this tag; the content first reached npm in [1.1.1]._
140
+
141
+ ### Added
142
+
143
+ - **New `@tokenops/sdk/testnet-faucet` subpath** — a headless, viem-first client for the testnet-only TokenOps test-token pair: `TokenopsTestToken` (TTT, plain 18-decimal ERC-20 with an open mint) and its ERC-7984 confidential wrapper `ConfidentialTokenopsTestToken` (CTTT, 6-decimal, open fully-backed faucet mint). Exports `TestnetFaucetClient` / `createTestnetFaucetClient` (config: `publicClient`, optional `walletClient`, optional CTTT proxy `address` override, `chainId`, `telemetry`); reads `underlyingToken`, `confidentialBalanceOf` (euint64 ciphertext handle), `underlyingBalanceOf`, `rate` (10^12), `decimals` (6), `underlyingDecimals` (18), `inferredTotalSupply`, `maxTotalSupply`, `getMetadata`; writes `mintConfidential` (returns `{ hash, to, amount, underlyingMinted, handle }` decoded from the `ConfidentialMint` event) and `mintUnderlying` (returns `{ hash, to, amount }`). Faucet mint amounts are **public plaintext** (`uint64` in CTTT 6-decimal units / `uint256` in TTT 18-decimal units) — no encryptor and no `@zama-fhe/sdk` peer is needed anywhere in the faucet subpaths. The backing TTT is never supplied by the consumer: it resolves from the registry for the canonical CTTT, or from a custom CTTT's own authoritative `underlying()` getter.
144
+ - **Testnet-only chain guard** — `TESTNET_FAUCET_SUPPORTED_CHAIN_IDS` (Sepolia `11155111` and local dev chain `31337`), `isTestnetFaucetChainId`, and `assertTestnetFaucetChain`. `TestnetFaucetClient` throws `UnsupportedChainError` at construction on any other chain — the faucet can never run on mainnet.
145
+ - **New `@tokenops/sdk/testnet-faucet/react` subpath** — wagmi + TanStack Query hooks mirroring every client method: reads `useConfidentialBalance`, `useUnderlyingBalance`, `useFaucetRate`, `useFaucetDecimals`, `useUnderlyingDecimals`, `useUnderlyingTokenAddress`, `useInferredTotalSupply`, `useMaxTotalSupply`, `useFaucetMetadata`; mutations `useMintConfidential`, `useMintUnderlying`. All hooks accept `BaseHookOptions` (`{ address?, chainId? }`); query keys follow `["tokenops-sdk", "testnet-faucet", method, chainId, address, ...args]` with exported constants `TESTNET_FAUCET_KEY` / `TESTNET_FAUCET_NAMESPACE`. On an unsupported chain the hooks never construct a client during render, and mutations surface the typed `UnsupportedChainError` / `DeploymentAddressUnavailableError` rather than a generic `MissingClientError`. Mutations do not auto-invalidate read queries — invalidate `["tokenops-sdk", "testnet-faucet"]` yourself. Requires only the `wagmi` and `@tanstack/react-query` peers, not `@zama-fhe/react-sdk`.
146
+ - **New error `FaucetSupplyExhaustedError`** (code `TOKENOPS_FAUCET_SUPPLY_EXHAUSTED`, new member of the `TokenOpsSdkErrorCode` union) — thrown when the confidential wrapper's backing reaches `maxTotalSupply` (`type(uint64).max`), mapped from the on-chain `ERC7984TotalSupplyOverflow` revert. Both faucet subpaths also re-export the full canonical TokenOps error vocabulary for parity with the other product subpaths.
147
+ - **`DEPLOYED_ADDRESSES.testnetFaucet` registry and address accessors** — Sepolia deployments for TTT (`0x37a057Fa8C201a7bf8caF32dfa9A0878f577D92b`) and the CTTT UUPS proxy (`0x258F9D60dc023870e4E3109c894D834D5377361a`), with new functions `getTestTokenAddress`, `requireTestTokenAddress`, `getConfidentialTestTokenAddress`, `requireConfidentialTestTokenAddress` on the root `@tokenops/sdk` and re-exported from `/testnet-faucet`. ABIs `tokenopsTestTokenAbi` and `confidentialTokenopsTestTokenAbi` are exported too.
148
+
149
+ ### Docs
150
+
151
+ - README: new "Quickstart — testnet faucet" sections (headless and React hooks) and subpath-table entries for `@tokenops/sdk/testnet-faucet` and `@tokenops/sdk/testnet-faucet/react`.
152
+ - TSDoc cleanup in published typings: internal audit-pipeline codenames removed from doc comments across 23 modules, so the `.d.ts` documentation shipped in the package no longer references internal process artifacts.
153
+
8
154
  ## [1.0.0] - 2026-05-27
9
155
 
10
156
  ### Changed — factory create methods now block on receipt before returning
@@ -202,6 +348,16 @@ The package has not been published to npm yet (`npm view @tokenops/sdk` returns
202
348
  - Sepolia factory address wired in `src/core/addresses.ts` (`0xA87701CE9A52D43681600583a99c85b50DbE3150`).
203
349
  - Three-surface test rig: unit, local FHEVM (anvil + forge-fhevm host contracts + `@fhevm/mock-utils`), Sepolia smoke.
204
350
 
205
- [1.1.0]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.0.0...v1.1.0
206
- [1.0.0]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.0.0-alpha.0...v1.0.0
351
+ [unreleased]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.5.1...HEAD
352
+ [1.5.1]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.5.0...v1.5.1
353
+ [1.5.0]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.4.2...v1.5.0
354
+ [1.4.2]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.4.1...v1.4.2
355
+ [1.4.1]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.4.0...v1.4.1
356
+ [1.4.0]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.3.0...v1.4.0
357
+ [1.3.0]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.2.0...v1.3.0
358
+ [1.2.0]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.1.1...v1.2.0
359
+ [1.1.1]: https://github.com/VestingLabs/tokenops-sdk/compare/v1.1.0...v1.1.1
360
+ [1.1.0]: https://github.com/VestingLabs/tokenops-sdk/compare/efda6cd...v1.1.0
361
+ [1.0.0]: https://github.com/VestingLabs/tokenops-sdk/commit/efda6cd
362
+ [1.0.0-rc.1]: https://github.com/VestingLabs/tokenops-sdk/releases/tag/v1.0.0-rc.1
207
363
  [1.0.0-alpha.0]: https://github.com/VestingLabs/tokenops-sdk/releases/tag/v1.0.0-alpha.0
package/README.md CHANGED
@@ -720,17 +720,27 @@ See [Testnet Faucet — React hooks on docs.tokenops.xyz](https://docs.tokenops.
720
720
 
721
721
  ## Peer dependencies
722
722
 
723
- | Package | Range | Required for |
724
- | ----------------------- | -------- | ------------------------------------------------------------------------------ |
725
- | `viem` | `^2.47` | all subpaths (hard required) |
726
- | `@zama-fhe/sdk` | `^3.0.0` | encryption + decryption + FHE write flows on every FHE subpath (optional peer) |
727
- | `@zama-fhe/react-sdk` | `^3.0.0` | `/react` hook subpaths that submit encrypted inputs (optional peer) |
728
- | `react` | `>=18` | `/react` hook subpaths (optional peer) |
729
- | `wagmi` | `^2` | `/react` hook subpaths (optional peer) |
730
- | `@tanstack/react-query` | `^5` | `/react` hook subpaths (optional peer) |
723
+ | Package | Range | Required for |
724
+ | ----------------------- | ------------ | ------------------------------------------------------------------------------ |
725
+ | `viem` | `^2.47` | all subpaths (hard required) |
726
+ | `@zama-fhe/sdk` | `^3.0.0` | encryption + decryption + FHE write flows on every FHE subpath (optional peer) |
727
+ | `@zama-fhe/react-sdk` | `^3.0.0` | `/react` hook subpaths that submit encrypted inputs (optional peer) |
728
+ | `react` | `>=18` | `/react` hook subpaths (optional peer) |
729
+ | `wagmi` | `^2 \|\| ^3` | `/react` hook subpaths (optional peer) |
730
+ | `@tanstack/react-query` | `^5` | `/react` hook subpaths (optional peer) |
731
731
 
732
732
  All peers except `viem` are marked `optional` via `peerDependenciesMeta` so read-only / ABI-only consumers can install the package without pulling them in. Install `@zama-fhe/sdk` explicitly the first time you encrypt, decrypt, or submit an FHE write; install the React peers if you use any `/react` hook subpath.
733
733
 
734
+ ### wagmi v2 and v3
735
+
736
+ The SDK supports both wagmi majors. wagmi v3 renamed `useAccount` → `useConnection`; the SDK resolves the right hook internally at runtime, so no code change is needed on your side when you upgrade — the hooks' public API is identical under both majors. CI typechecks and runs the hook suites against both.
737
+
738
+ **Still seeing `ERESOLVE` after upgrading to wagmi v3?** The error is almost certainly coming from _another_ package in your dependency tree, not from `@tokenops/sdk` (which accepts `^2.0.0 || ^3.0.0`). To diagnose:
739
+
740
+ 1. Read the `ERESOLVE` output carefully — npm names the package whose peer range could not be satisfied (`Could not resolve dependency: peer wagmi@"^2.x" from <package>@<version>`). If `<package>` is not `@tokenops/sdk`, that package still pins wagmi v2.
741
+ 2. Confirm with `npm ls wagmi` (or `pnpm why wagmi`) — it prints every dependent and the range each one declares.
742
+ 3. Fix by upgrading the offending package to a wagmi-v3-compatible release, or — if none exists yet — stay on wagmi v2 (fully supported by this SDK) until it does. npm's `--legacy-peer-deps` / pnpm's looser peer handling can silence the error, but you are then running that package against a wagmi major it does not declare support for.
743
+
734
744
  ## Design
735
745
 
736
746
  - **Single package, subpath exports.** No companion `@tokenops/react-sdk` — hooks live at `@tokenops/sdk/<product>/react`.
package/SECURITY.md CHANGED
@@ -46,7 +46,7 @@ The following are **not in scope** for this SDK's security policy:
46
46
 
47
47
  **Encryptor binding is address-scoped.** Input proofs from `encryptUint64` are bound to `(contractAddress, userAddress)` — they cannot be replayed against a different contract or submitted by a different sender. Do not reuse proofs across different contract deployments.
48
48
 
49
- **Operator approvals (`setOperator`) have a deadline.** Set the deadline appropriately for your use case. The SDK exports `ERC_7984_OPERATOR_MAX_DEADLINE` for production flows and warns when the deadline is in the past.
49
+ **Operator approvals (`setOperator`) have a deadline — scope it in production.** The SDK exports `ERC7984_OPERATOR_MAX_DEADLINE` (the `uint48` max, effectively forever) as the default when a consumer doesn't care about expiry — appropriate for local-chain dev loops and test fixtures, not for production. For production flows, scope the deadline to the expected operation window (e.g. `Date.now()/1000 + 3600`) and call `revokeOperator` when the authorization is no longer needed. The SDK validates that the deadline is within the `uint48` range but does **not** warn when a supplied deadline is already in the past — a past-but-otherwise-valid deadline is accepted silently.
50
50
 
51
51
  ## Supported Versions
52
52
 
@@ -54,6 +54,12 @@ The following are **not in scope** for this SDK's security policy:
54
54
  | ------- | ------------------ |
55
55
  | 1.x | :white_check_mark: |
56
56
 
57
+ ## Known Advisories
58
+
59
+ Open Dependabot alerts against this repo are all **transitive** — the SDK's only runtime dependency is `abitype`. The remaining alerts (e.g. `axios`, reached via `wagmi` → `@wagmi/connectors` → `@base-org/account` → `@coinbase/cdp-sdk`; and `hono`, reached via `porto`) come in through the `wagmi` **peer** dependency tree, not through anything this SDK ships at runtime.
60
+
61
+ This repo's `pnpm.overrides` pin those transitive versions for our own lockfile, which fixes CI and our security dashboard — but that pin has no effect for consumers, who resolve their own `wagmi`/peer dependency tree at install time. Consumers running `npm audit` (or similar) against their own install may still see these advisories; that reflects their resolved tree, not a vulnerability this SDK ships. Real remediation is upstream, in `wagmi`'s own dependency graph.
62
+
57
63
  ## Acknowledgments
58
64
 
59
65
  We will acknowledge reporters in the relevant release notes unless they request anonymity.
@@ -208,6 +208,23 @@ var AccessDeniedError = class extends TokenOpsSdkError {
208
208
  });
209
209
  }
210
210
  };
211
+ var OPERATOR_NOT_APPROVED_REMEDIATION = "The token has not approved this contract as an operator. Call `setOperator()` (see `/fhe` operators) before this transaction.";
212
+ var OperatorNotApprovedError = class extends TokenOpsSdkError {
213
+ name = "OperatorNotApprovedError";
214
+ constructor(args) {
215
+ super(OPERATOR_NOT_APPROVED_REMEDIATION, {
216
+ code: "TOKENOPS_OPERATOR_NOT_APPROVED",
217
+ cause: args.cause,
218
+ context: {
219
+ method: args.method,
220
+ contractAddress: args.contractAddress,
221
+ tokenAddress: args.tokenAddress,
222
+ holder: args.holder,
223
+ spender: args.spender
224
+ }
225
+ });
226
+ }
227
+ };
211
228
  var InsufficientFeeError = class extends TokenOpsSdkError {
212
229
  name = "InsufficientFeeError";
213
230
  constructor(args) {
@@ -477,6 +494,16 @@ var TokenOpsContractError = class extends TokenOpsSdkError {
477
494
  }
478
495
  };
479
496
 
497
+ // src/core/brands.ts
498
+ var asEncryptedHandle = (h) => h;
499
+ var asExternalInputProof = (h) => h;
500
+ var asTxHash = (h) => h;
501
+ var asVestingId = (h) => h;
502
+ var asAirdropId = (h) => h;
503
+ var asDisperseId = (h) => h;
504
+ var asRole = (h) => h;
505
+ var asSignature = (h) => h;
506
+
480
507
  exports.AccessDeniedError = AccessDeniedError;
481
508
  exports.AlreadyInitializedError = AlreadyInitializedError;
482
509
  exports.BatchTooLargeError = BatchTooLargeError;
@@ -497,6 +524,8 @@ exports.MissingEncryptorError = MissingEncryptorError;
497
524
  exports.MissingPublicClientError = MissingPublicClientError;
498
525
  exports.MissingWalletClientError = MissingWalletClientError;
499
526
  exports.NetworkError = NetworkError;
527
+ exports.OPERATOR_NOT_APPROVED_REMEDIATION = OPERATOR_NOT_APPROVED_REMEDIATION;
528
+ exports.OperatorNotApprovedError = OperatorNotApprovedError;
500
529
  exports.PausedError = PausedError;
501
530
  exports.ReceiptEventAmbiguousError = ReceiptEventAmbiguousError;
502
531
  exports.ReceiptEventNotFoundError = ReceiptEventNotFoundError;
@@ -513,4 +542,12 @@ exports.UserDecryptNotAllowedError = UserDecryptNotAllowedError;
513
542
  exports.UserRejectedSignatureError = UserRejectedSignatureError;
514
543
  exports.WalletChainMismatchError = WalletChainMismatchError;
515
544
  exports.WalletRejectedError = WalletRejectedError;
545
+ exports.asAirdropId = asAirdropId;
546
+ exports.asDisperseId = asDisperseId;
547
+ exports.asEncryptedHandle = asEncryptedHandle;
548
+ exports.asExternalInputProof = asExternalInputProof;
549
+ exports.asRole = asRole;
550
+ exports.asSignature = asSignature;
551
+ exports.asTxHash = asTxHash;
552
+ exports.asVestingId = asVestingId;
516
553
  exports.isTokenOpsSdkError = isTokenOpsSdkError;
@@ -1,12 +1,55 @@
1
1
  'use strict';
2
2
 
3
- var chunk56UI7LUR_cjs = require('./chunk-56UI7LUR.cjs');
3
+ var chunk2RNW4MIJ_cjs = require('./chunk-2RNW4MIJ.cjs');
4
4
  var viem = require('viem');
5
5
 
6
6
  // src/core/version.ts
7
7
  var SDK_VERSION = "1.0.0";
8
+ var ERC7984_ERRORS_ABI = [
9
+ {
10
+ type: "error",
11
+ name: "ERC7984UnauthorizedSpender",
12
+ inputs: [
13
+ { name: "holder", type: "address" },
14
+ { name: "spender", type: "address" }
15
+ ]
16
+ },
17
+ {
18
+ type: "error",
19
+ name: "ERC7984UnauthorizedCaller",
20
+ inputs: [{ name: "caller", type: "address" }]
21
+ },
22
+ {
23
+ type: "error",
24
+ name: "ERC7984UnauthorizedUseOfEncryptedAmount",
25
+ inputs: [
26
+ { name: "amount", type: "bytes32" },
27
+ { name: "user", type: "address" }
28
+ ]
29
+ },
30
+ {
31
+ type: "error",
32
+ name: "ERC7984InvalidReceiver",
33
+ inputs: [{ name: "receiver", type: "address" }]
34
+ },
35
+ {
36
+ type: "error",
37
+ name: "ERC7984InvalidSender",
38
+ inputs: [{ name: "sender", type: "address" }]
39
+ },
40
+ {
41
+ type: "error",
42
+ name: "ERC7984ZeroBalance",
43
+ inputs: [{ name: "holder", type: "address" }]
44
+ },
45
+ {
46
+ type: "error",
47
+ name: "ERC7984InvalidGatewayRequest",
48
+ inputs: [{ name: "requestId", type: "uint256" }]
49
+ }
50
+ ];
8
51
  function mapContractRevert(error, args) {
9
- if (error instanceof chunk56UI7LUR_cjs.TokenOpsSdkError) return error;
52
+ if (error instanceof chunk2RNW4MIJ_cjs.TokenOpsSdkError) return error;
10
53
  const ctx = {
11
54
  method: args.method,
12
55
  contractAddress: args.contractAddress,
@@ -24,7 +67,10 @@ function mapContractRevert(error, args) {
24
67
  if (revertName === void 0 && rawRevertData !== void 0 && rawRevertData.length >= 10) {
25
68
  revertSelector = rawRevertData.slice(0, 10);
26
69
  try {
27
- const decoded = viem.decodeErrorResult({ abi: args.abi, data: rawRevertData });
70
+ const decoded = viem.decodeErrorResult({
71
+ abi: [...args.abi, ...ERC7984_ERRORS_ABI],
72
+ data: rawRevertData
73
+ });
28
74
  revertName = decoded.errorName;
29
75
  revertArgs = decoded.args;
30
76
  } catch {
@@ -38,7 +84,7 @@ function mapContractRevert(error, args) {
38
84
  const fromDefault = defaultRevertNameMapper(revertName, revertArgs, ctx, args.account);
39
85
  if (fromDefault) return attachCause(fromDefault, error);
40
86
  }
41
- return new chunk56UI7LUR_cjs.ContractRevertError({
87
+ return new chunk2RNW4MIJ_cjs.ContractRevertError({
42
88
  method: args.method,
43
89
  contractAddress: args.contractAddress,
44
90
  revertName,
@@ -66,62 +112,72 @@ function defaultRevertNameMapper(name, errArgs, ctx, account) {
66
112
  case "EnforcedPause":
67
113
  case "PausableUpgradeable__Paused":
68
114
  case "ClaimsPaused":
69
- return new chunk56UI7LUR_cjs.PausedError(ctx);
115
+ return new chunk2RNW4MIJ_cjs.PausedError(ctx);
70
116
  case "AccessControlUnauthorizedAccount": {
71
117
  const acct = errArgs?.[0] ?? account;
72
118
  const role = errArgs?.[1];
73
- return new chunk56UI7LUR_cjs.AccessDeniedError({ ...ctx, account: acct, role });
119
+ return new chunk2RNW4MIJ_cjs.AccessDeniedError({ ...ctx, account: acct, role });
74
120
  }
75
121
  case "AccessControlBadConfirmation":
76
- return new chunk56UI7LUR_cjs.InvalidArgumentError({
122
+ return new chunk2RNW4MIJ_cjs.InvalidArgumentError({
77
123
  method: ctx.method,
78
124
  argument: "callerConfirmation",
79
125
  reason: "must equal msg.sender for renounceRole"
80
126
  });
81
127
  case "InvalidInitialization":
82
128
  case "Initializable_InvalidInitialization":
83
- return new chunk56UI7LUR_cjs.AlreadyInitializedError(ctx);
129
+ return new chunk2RNW4MIJ_cjs.AlreadyInitializedError(ctx);
84
130
  case "ReentrancyGuardReentrantCall":
85
131
  case "ReentrancyGuardTransient":
86
- return new chunk56UI7LUR_cjs.ReentrancyError(ctx);
132
+ return new chunk2RNW4MIJ_cjs.ReentrancyError(ctx);
87
133
  // Cross-product custom errors with stable, unambiguous meaning.
88
134
  case "InsufficientFee":
89
135
  case "InsufficientGasFee":
90
- return new chunk56UI7LUR_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas" });
136
+ return new chunk2RNW4MIJ_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas" });
91
137
  case "InsufficientAmount": {
92
138
  const provided = errArgs?.[0];
93
139
  const required = errArgs?.[1];
94
- return new chunk56UI7LUR_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas", required, provided });
140
+ return new chunk2RNW4MIJ_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas", required, provided });
95
141
  }
96
142
  case "ZeroBalance":
97
143
  case "InsufficientBalance":
98
- return new chunk56UI7LUR_cjs.InsufficientBalanceError({ ...ctx, balanceKind: "eth" });
144
+ return new chunk2RNW4MIJ_cjs.InsufficientBalanceError({ ...ctx, balanceKind: "eth" });
99
145
  case "BatchTooLarge": {
100
146
  const requested = Number(errArgs?.[0] ?? 0);
101
147
  const max = errArgs?.[1] ?? 0n;
102
- return new chunk56UI7LUR_cjs.BatchTooLargeError({ ...ctx, requested, max });
148
+ return new chunk2RNW4MIJ_cjs.BatchTooLargeError({ ...ctx, requested, max });
103
149
  }
104
150
  case "SplitDisabled":
105
- return new chunk56UI7LUR_cjs.FeatureDisabledError({ ...ctx, feature: "split" });
151
+ return new chunk2RNW4MIJ_cjs.FeatureDisabledError({ ...ctx, feature: "split" });
106
152
  case "PausableDisabled":
107
- return new chunk56UI7LUR_cjs.FeatureDisabledError({ ...ctx, feature: "pause" });
153
+ return new chunk2RNW4MIJ_cjs.FeatureDisabledError({ ...ctx, feature: "pause" });
108
154
  case "ExtensionNotAllowed":
109
- return new chunk56UI7LUR_cjs.FeatureDisabledError({ ...ctx, feature: "extendClaimWindow" });
155
+ return new chunk2RNW4MIJ_cjs.FeatureDisabledError({ ...ctx, feature: "extendClaimWindow" });
110
156
  case "TransferFailed":
111
- return new chunk56UI7LUR_cjs.TransferFailedError({ ...ctx, asset: "eth" });
157
+ return new chunk2RNW4MIJ_cjs.TransferFailedError({ ...ctx, asset: "eth" });
112
158
  case "HandleNotAllowed":
113
- return new chunk56UI7LUR_cjs.FheHandleNotAllowedError({ ...ctx, account });
159
+ return new chunk2RNW4MIJ_cjs.FheHandleNotAllowedError({ ...ctx, account });
114
160
  case "SenderNotAllowedToUseHandle": {
115
161
  const handle = errArgs?.[0];
116
162
  const sender = errArgs?.[1] ?? account;
117
- return new chunk56UI7LUR_cjs.FheHandleNotAllowedError({ ...ctx, handle, account: sender });
163
+ return new chunk2RNW4MIJ_cjs.FheHandleNotAllowedError({ ...ctx, handle, account: sender });
118
164
  }
119
165
  case "SenderNotAllowed": {
120
166
  const sender = errArgs?.[0] ?? account;
121
- return new chunk56UI7LUR_cjs.FheHandleNotAllowedError({ ...ctx, account: sender });
167
+ return new chunk2RNW4MIJ_cjs.FheHandleNotAllowedError({ ...ctx, account: sender });
122
168
  }
123
169
  case "InvalidSignature":
124
- return new chunk56UI7LUR_cjs.InvalidSignatureError(ctx);
170
+ return new chunk2RNW4MIJ_cjs.InvalidSignatureError(ctx);
171
+ case "ERC7984UnauthorizedSpender": {
172
+ const holder = errArgs?.[0];
173
+ const spender = errArgs?.[1];
174
+ return new chunk2RNW4MIJ_cjs.OperatorNotApprovedError({
175
+ method: ctx.method,
176
+ contractAddress: ctx.contractAddress,
177
+ holder,
178
+ spender
179
+ });
180
+ }
125
181
  // Range / arg-validation reverts the SDK should already have caught in
126
182
  // preflight or input validation. If they slip through, surface them as
127
183
  // structural `InvalidArgumentError` so consumers see `TOKENOPS_INVALID_ARGUMENT`
@@ -155,7 +211,7 @@ function defaultRevertNameMapper(name, errArgs, ctx, account) {
155
211
  case "ArrayLengthMismatch":
156
212
  case "VestingIdsNotStrictlyAsc":
157
213
  case "CustomFeeNotSet":
158
- return new chunk56UI7LUR_cjs.InvalidArgumentError({
214
+ return new chunk2RNW4MIJ_cjs.InvalidArgumentError({
159
215
  method: ctx.method,
160
216
  argument: invalidArgumentLabelFor(name),
161
217
  reason: `the contract reverted with \`${name}\`. If you did not pass an invalid value, this may be an SDK bug \u2014 please file an issue.`
@@ -228,7 +284,7 @@ function isContractRevert(error) {
228
284
  }
229
285
  function classifyNonRevert(error, args) {
230
286
  if (!(error instanceof viem.BaseError)) {
231
- return new chunk56UI7LUR_cjs.UnknownWriteFailureError({
287
+ return new chunk2RNW4MIJ_cjs.UnknownWriteFailureError({
232
288
  method: args.method,
233
289
  contractAddress: args.contractAddress,
234
290
  cause: error
@@ -239,21 +295,21 @@ function classifyNonRevert(error, args) {
239
295
  return predicate(frame) ? frame : void 0;
240
296
  };
241
297
  if (walk((e) => e instanceof viem.UserRejectedRequestError) || walk((e) => e instanceof viem.TransactionRejectedRpcError)) {
242
- return new chunk56UI7LUR_cjs.WalletRejectedError({
298
+ return new chunk2RNW4MIJ_cjs.WalletRejectedError({
243
299
  method: args.method,
244
300
  contractAddress: args.contractAddress,
245
301
  cause: error
246
302
  });
247
303
  }
248
304
  if (walk((e) => e instanceof viem.ChainMismatchError)) {
249
- return new chunk56UI7LUR_cjs.WalletChainMismatchError({
305
+ return new chunk2RNW4MIJ_cjs.WalletChainMismatchError({
250
306
  method: args.method,
251
307
  contractAddress: args.contractAddress,
252
308
  cause: error
253
309
  });
254
310
  }
255
311
  if (walk((e) => e instanceof viem.InsufficientFundsError)) {
256
- return new chunk56UI7LUR_cjs.InsufficientGasFundsError({
312
+ return new chunk2RNW4MIJ_cjs.InsufficientGasFundsError({
257
313
  method: args.method,
258
314
  contractAddress: args.contractAddress,
259
315
  cause: error
@@ -261,7 +317,7 @@ function classifyNonRevert(error, args) {
261
317
  }
262
318
  const httpFrame = walk((e) => e instanceof viem.HttpRequestError);
263
319
  if (httpFrame) {
264
- return new chunk56UI7LUR_cjs.NetworkError({
320
+ return new chunk2RNW4MIJ_cjs.NetworkError({
265
321
  method: args.method,
266
322
  contractAddress: args.contractAddress,
267
323
  statusCode: httpFrame.status,
@@ -269,13 +325,13 @@ function classifyNonRevert(error, args) {
269
325
  });
270
326
  }
271
327
  if (walk((e) => e instanceof viem.TimeoutError)) {
272
- return new chunk56UI7LUR_cjs.NetworkError({
328
+ return new chunk2RNW4MIJ_cjs.NetworkError({
273
329
  method: args.method,
274
330
  contractAddress: args.contractAddress,
275
331
  cause: error
276
332
  });
277
333
  }
278
- return new chunk56UI7LUR_cjs.UnknownWriteFailureError({
334
+ return new chunk2RNW4MIJ_cjs.UnknownWriteFailureError({
279
335
  method: args.method,
280
336
  contractAddress: args.contractAddress,
281
337
  cause: error
@@ -1,4 +1,4 @@
1
- import { TokenOpsValidationError } from './chunk-IVE3QEGD.js';
1
+ import { TokenOpsValidationError } from './chunk-DRSPMIZ7.js';
2
2
  import { isAddress, getAddress } from 'viem';
3
3
 
4
4
  function normaliseAddress(input) {