@tokenops/sdk 1.5.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 (120) hide show
  1. package/CHANGELOG.md +158 -2
  2. package/SECURITY.md +7 -1
  3. package/dist/{chunk-67OV5CX2.cjs → chunk-2RNW4MIJ.cjs} +18 -0
  4. package/dist/{chunk-KLKI352M.cjs → chunk-43RFBQ73.cjs} +29 -29
  5. package/dist/{chunk-ZA673I3O.js → chunk-46T67CE2.js} +1 -1
  6. package/dist/{chunk-6BMP4ICG.js → chunk-4KJ66YRH.js} +61 -3
  7. package/dist/{chunk-JYOTGRSO.cjs → chunk-4ZCXK4VI.cjs} +68 -8
  8. package/dist/{chunk-SG65XWH7.js → chunk-6WSNS3UV.js} +1 -1
  9. package/dist/{chunk-V2PXZVBF.cjs → chunk-7G7UOQV4.cjs} +4 -4
  10. package/dist/{chunk-WENHUPZC.cjs → chunk-AYGRYDBX.cjs} +35 -36
  11. package/dist/{chunk-2NCLOQ56.cjs → chunk-BD4LZBVF.cjs} +85 -9
  12. package/dist/{chunk-5X5WL5CU.cjs → chunk-BK7YIVLK.cjs} +110 -149
  13. package/dist/{chunk-W7IGCOVL.cjs → chunk-CBRL2PJA.cjs} +12 -11
  14. package/dist/{chunk-ORFTDNFZ.js → chunk-CNP4L3GF.js} +4 -4
  15. package/dist/{chunk-QBJ7O2B4.js → chunk-DRSPMIZ7.js} +11 -1
  16. package/dist/{chunk-C7BRZXJA.cjs → chunk-EUKPOXWR.cjs} +15 -16
  17. package/dist/{chunk-YQIBBEFJ.js → chunk-HETNKZEE.js} +1 -1
  18. package/dist/{chunk-ODLTEGHB.js → chunk-KSDSXJ34.js} +1 -1
  19. package/dist/{chunk-TIUKIY5V.cjs → chunk-LBWRFZR3.cjs} +58 -40
  20. package/dist/{chunk-WMACINXO.js → chunk-NFW7AUEX.js} +1 -1
  21. package/dist/{chunk-4WPGQSNT.cjs → chunk-OL6SHN3D.cjs} +3 -3
  22. package/dist/{chunk-AOP6HMPW.js → chunk-PD7ME2BT.js} +75 -2
  23. package/dist/{chunk-2PWNH3UE.js → chunk-QET3Q4JP.js} +65 -9
  24. package/dist/{chunk-XGGTQQFH.js → chunk-QKKBBH7I.js} +2 -2
  25. package/dist/{chunk-6R4KNAPK.js → chunk-S2XD75JM.js} +10 -3
  26. package/dist/{chunk-MCRBVJGZ.cjs → chunk-SPGSO5SO.cjs} +29 -22
  27. package/dist/{chunk-FAXIIH5R.cjs → chunk-SYHNZSZZ.cjs} +3 -3
  28. package/dist/{chunk-IXLO7GO5.js → chunk-TN65XNTI.js} +47 -83
  29. package/dist/chunk-U57COLUE.js +56 -0
  30. package/dist/{chunk-DUZIIRPF.js → chunk-UE5XK2SY.js} +27 -9
  31. package/dist/{chunk-TEBM66WA.cjs → chunk-UJS4N2XM.cjs} +12 -12
  32. package/dist/{chunk-OXTLPTO3.js → chunk-V5D7BHW3.js} +4 -5
  33. package/dist/{chunk-NTZY6IB7.js → chunk-VHSNYUYV.js} +5 -4
  34. package/dist/{chunk-JK4XMMKM.cjs → chunk-VR3FREBX.cjs} +3 -3
  35. package/dist/{chunk-T46YXSBP.cjs → chunk-WO72UBQD.cjs} +11 -11
  36. package/dist/chunk-WOEETTH7.cjs +59 -0
  37. package/dist/{chunk-IJFQ5L4I.js → chunk-WTK6JDSI.js} +4 -5
  38. package/dist/{chunk-PUPKNW3R.cjs → chunk-WUXUWTFW.cjs} +3 -3
  39. package/dist/{chunk-PFXURMBZ.cjs → chunk-YFIWCYHC.cjs} +112 -54
  40. package/dist/{chunk-ZWXJTBZO.js → chunk-YPYCWLYP.js} +1 -1
  41. package/dist/core/addresses.d.ts +1 -1
  42. package/dist/core/errors.d.ts +1 -1
  43. package/dist/fhe/index.cjs +84 -64
  44. package/dist/fhe/index.d.cts +1 -0
  45. package/dist/fhe/index.d.ts +1 -0
  46. package/dist/fhe/index.js +21 -9
  47. package/dist/fhe/mock-erc7984.d.ts +4 -4
  48. package/dist/fhe/operators.d.ts +142 -2
  49. package/dist/fhe/react/index.cjs +21 -8
  50. package/dist/fhe/react/index.d.cts +2 -0
  51. package/dist/fhe/react/index.d.ts +2 -0
  52. package/dist/fhe/react/index.js +8 -3
  53. package/dist/fhe/react/useEnsureOperator.d.ts +56 -0
  54. package/dist/fhe/react/useIsOperator.d.ts +54 -0
  55. package/dist/fhe/sepolia-encryptor-web.d.ts +18 -3
  56. package/dist/fhe-airdrop/advanced/index.cjs +8 -9
  57. package/dist/fhe-airdrop/advanced/index.js +6 -7
  58. package/dist/fhe-airdrop/advanced/react/index.cjs +13 -14
  59. package/dist/fhe-airdrop/advanced/react/index.js +9 -10
  60. package/dist/fhe-airdrop/airdrop.d.ts +7 -0
  61. package/dist/fhe-airdrop/index.cjs +66 -67
  62. package/dist/fhe-airdrop/index.js +7 -8
  63. package/dist/fhe-airdrop/react/index.cjs +148 -137
  64. package/dist/fhe-airdrop/react/index.d.cts +3 -1
  65. package/dist/fhe-airdrop/react/index.d.ts +3 -1
  66. package/dist/fhe-airdrop/react/index.js +16 -14
  67. package/dist/fhe-airdrop/react/useAccessClaimAmount.d.ts +17 -11
  68. package/dist/fhe-airdrop/react/useClaim.d.ts +8 -0
  69. package/dist/fhe-airdrop/react/useSignClaimAuthorization.d.ts +1 -1
  70. package/dist/fhe-airdrop/types.d.ts +14 -3
  71. package/dist/fhe-disperse/errors.d.ts +55 -0
  72. package/dist/fhe-disperse/index.cjs +74 -67
  73. package/dist/fhe-disperse/index.d.cts +1 -1
  74. package/dist/fhe-disperse/index.d.ts +1 -1
  75. package/dist/fhe-disperse/index.js +8 -9
  76. package/dist/fhe-disperse/react/index.cjs +105 -88
  77. package/dist/fhe-disperse/react/index.d.cts +3 -1
  78. package/dist/fhe-disperse/react/index.d.ts +3 -1
  79. package/dist/fhe-disperse/react/index.js +12 -11
  80. package/dist/fhe-vesting/advanced/index.cjs +8 -9
  81. package/dist/fhe-vesting/advanced/index.js +6 -7
  82. package/dist/fhe-vesting/advanced/react/index.cjs +14 -15
  83. package/dist/fhe-vesting/advanced/react/index.js +11 -12
  84. package/dist/fhe-vesting/index.cjs +75 -76
  85. package/dist/fhe-vesting/index.js +9 -10
  86. package/dist/fhe-vesting/manager.d.ts +19 -0
  87. package/dist/fhe-vesting/react/index.cjs +245 -227
  88. package/dist/fhe-vesting/react/index.d.cts +6 -4
  89. package/dist/fhe-vesting/react/index.d.ts +6 -4
  90. package/dist/fhe-vesting/react/index.js +27 -21
  91. package/dist/fhe-vesting/react/useAccessClaimableAmount.d.ts +11 -3
  92. package/dist/fhe-vesting/react/useAccessSettledAmount.d.ts +11 -3
  93. package/dist/fhe-vesting/react/useAccessTotalAllocation.d.ts +11 -3
  94. package/dist/fhe-vesting/react/useAccessVestedAmount.d.ts +11 -3
  95. package/dist/fhe-vesting/react/useAdminGetClaimableAmount.d.ts +1 -1
  96. package/dist/fhe-vesting/react/useAdminGetSettledAmount.d.ts +1 -1
  97. package/dist/fhe-vesting/react/useAdminGetTotalAllocation.d.ts +1 -1
  98. package/dist/fhe-vesting/react/useAdminGetVestedAmount.d.ts +1 -1
  99. package/dist/fhe-vesting/react/useAdminPartialClaim.d.ts +8 -0
  100. package/dist/fhe-vesting/react/useClaim.d.ts +1 -1
  101. package/dist/fhe-vesting/react/useDiscloseHandleToParty.d.ts +1 -1
  102. package/dist/fhe-vesting/react/useManagerDiscloseHandleToParty.d.ts +1 -1
  103. package/dist/fhe-vesting/react/usePartialClaim.d.ts +1 -1
  104. package/dist/fhe-vesting/react/useVestingInfo.d.ts +1 -1
  105. package/dist/fhe-vesting/types.d.ts +12 -1
  106. package/dist/index.cjs +87 -88
  107. package/dist/index.js +2 -3
  108. package/dist/testnet-faucet/index.cjs +55 -56
  109. package/dist/testnet-faucet/index.js +5 -6
  110. package/dist/testnet-faucet/react/index.cjs +53 -54
  111. package/dist/testnet-faucet/react/index.js +7 -8
  112. package/package.json +13 -8
  113. package/dist/chunk-66IHPTOK.cjs +0 -20
  114. package/dist/chunk-KWFFIJYX.js +0 -11
  115. package/dist/fhe-airdrop/react/useAirdropClaim.d.ts +0 -27
  116. package/dist/fhe-airdrop/react/useGetClaimAmount.d.ts +0 -30
  117. package/dist/fhe-vesting/react/useGetClaimableAmount.d.ts +0 -13
  118. package/dist/fhe-vesting/react/useGetSettledAmount.d.ts +0 -14
  119. package/dist/fhe-vesting/react/useGetTotalAllocation.d.ts +0 -13
  120. 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/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.
@@ -494,6 +494,16 @@ var TokenOpsContractError = class extends TokenOpsSdkError {
494
494
  }
495
495
  };
496
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
+
497
507
  exports.AccessDeniedError = AccessDeniedError;
498
508
  exports.AlreadyInitializedError = AlreadyInitializedError;
499
509
  exports.BatchTooLargeError = BatchTooLargeError;
@@ -532,4 +542,12 @@ exports.UserDecryptNotAllowedError = UserDecryptNotAllowedError;
532
542
  exports.UserRejectedSignatureError = UserRejectedSignatureError;
533
543
  exports.WalletChainMismatchError = WalletChainMismatchError;
534
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;
535
553
  exports.isTokenOpsSdkError = isTokenOpsSdkError;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk67OV5CX2_cjs = require('./chunk-67OV5CX2.cjs');
3
+ var chunk2RNW4MIJ_cjs = require('./chunk-2RNW4MIJ.cjs');
4
4
  var viem = require('viem');
5
5
 
6
6
  // src/core/version.ts
@@ -49,7 +49,7 @@ var ERC7984_ERRORS_ABI = [
49
49
  }
50
50
  ];
51
51
  function mapContractRevert(error, args) {
52
- if (error instanceof chunk67OV5CX2_cjs.TokenOpsSdkError) return error;
52
+ if (error instanceof chunk2RNW4MIJ_cjs.TokenOpsSdkError) return error;
53
53
  const ctx = {
54
54
  method: args.method,
55
55
  contractAddress: args.contractAddress,
@@ -84,7 +84,7 @@ function mapContractRevert(error, args) {
84
84
  const fromDefault = defaultRevertNameMapper(revertName, revertArgs, ctx, args.account);
85
85
  if (fromDefault) return attachCause(fromDefault, error);
86
86
  }
87
- return new chunk67OV5CX2_cjs.ContractRevertError({
87
+ return new chunk2RNW4MIJ_cjs.ContractRevertError({
88
88
  method: args.method,
89
89
  contractAddress: args.contractAddress,
90
90
  revertName,
@@ -112,66 +112,66 @@ function defaultRevertNameMapper(name, errArgs, ctx, account) {
112
112
  case "EnforcedPause":
113
113
  case "PausableUpgradeable__Paused":
114
114
  case "ClaimsPaused":
115
- return new chunk67OV5CX2_cjs.PausedError(ctx);
115
+ return new chunk2RNW4MIJ_cjs.PausedError(ctx);
116
116
  case "AccessControlUnauthorizedAccount": {
117
117
  const acct = errArgs?.[0] ?? account;
118
118
  const role = errArgs?.[1];
119
- return new chunk67OV5CX2_cjs.AccessDeniedError({ ...ctx, account: acct, role });
119
+ return new chunk2RNW4MIJ_cjs.AccessDeniedError({ ...ctx, account: acct, role });
120
120
  }
121
121
  case "AccessControlBadConfirmation":
122
- return new chunk67OV5CX2_cjs.InvalidArgumentError({
122
+ return new chunk2RNW4MIJ_cjs.InvalidArgumentError({
123
123
  method: ctx.method,
124
124
  argument: "callerConfirmation",
125
125
  reason: "must equal msg.sender for renounceRole"
126
126
  });
127
127
  case "InvalidInitialization":
128
128
  case "Initializable_InvalidInitialization":
129
- return new chunk67OV5CX2_cjs.AlreadyInitializedError(ctx);
129
+ return new chunk2RNW4MIJ_cjs.AlreadyInitializedError(ctx);
130
130
  case "ReentrancyGuardReentrantCall":
131
131
  case "ReentrancyGuardTransient":
132
- return new chunk67OV5CX2_cjs.ReentrancyError(ctx);
132
+ return new chunk2RNW4MIJ_cjs.ReentrancyError(ctx);
133
133
  // Cross-product custom errors with stable, unambiguous meaning.
134
134
  case "InsufficientFee":
135
135
  case "InsufficientGasFee":
136
- return new chunk67OV5CX2_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas" });
136
+ return new chunk2RNW4MIJ_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas" });
137
137
  case "InsufficientAmount": {
138
138
  const provided = errArgs?.[0];
139
139
  const required = errArgs?.[1];
140
- return new chunk67OV5CX2_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas", required, provided });
140
+ return new chunk2RNW4MIJ_cjs.InsufficientFeeError({ ...ctx, feeKind: "gas", required, provided });
141
141
  }
142
142
  case "ZeroBalance":
143
143
  case "InsufficientBalance":
144
- return new chunk67OV5CX2_cjs.InsufficientBalanceError({ ...ctx, balanceKind: "eth" });
144
+ return new chunk2RNW4MIJ_cjs.InsufficientBalanceError({ ...ctx, balanceKind: "eth" });
145
145
  case "BatchTooLarge": {
146
146
  const requested = Number(errArgs?.[0] ?? 0);
147
147
  const max = errArgs?.[1] ?? 0n;
148
- return new chunk67OV5CX2_cjs.BatchTooLargeError({ ...ctx, requested, max });
148
+ return new chunk2RNW4MIJ_cjs.BatchTooLargeError({ ...ctx, requested, max });
149
149
  }
150
150
  case "SplitDisabled":
151
- return new chunk67OV5CX2_cjs.FeatureDisabledError({ ...ctx, feature: "split" });
151
+ return new chunk2RNW4MIJ_cjs.FeatureDisabledError({ ...ctx, feature: "split" });
152
152
  case "PausableDisabled":
153
- return new chunk67OV5CX2_cjs.FeatureDisabledError({ ...ctx, feature: "pause" });
153
+ return new chunk2RNW4MIJ_cjs.FeatureDisabledError({ ...ctx, feature: "pause" });
154
154
  case "ExtensionNotAllowed":
155
- return new chunk67OV5CX2_cjs.FeatureDisabledError({ ...ctx, feature: "extendClaimWindow" });
155
+ return new chunk2RNW4MIJ_cjs.FeatureDisabledError({ ...ctx, feature: "extendClaimWindow" });
156
156
  case "TransferFailed":
157
- return new chunk67OV5CX2_cjs.TransferFailedError({ ...ctx, asset: "eth" });
157
+ return new chunk2RNW4MIJ_cjs.TransferFailedError({ ...ctx, asset: "eth" });
158
158
  case "HandleNotAllowed":
159
- return new chunk67OV5CX2_cjs.FheHandleNotAllowedError({ ...ctx, account });
159
+ return new chunk2RNW4MIJ_cjs.FheHandleNotAllowedError({ ...ctx, account });
160
160
  case "SenderNotAllowedToUseHandle": {
161
161
  const handle = errArgs?.[0];
162
162
  const sender = errArgs?.[1] ?? account;
163
- return new chunk67OV5CX2_cjs.FheHandleNotAllowedError({ ...ctx, handle, account: sender });
163
+ return new chunk2RNW4MIJ_cjs.FheHandleNotAllowedError({ ...ctx, handle, account: sender });
164
164
  }
165
165
  case "SenderNotAllowed": {
166
166
  const sender = errArgs?.[0] ?? account;
167
- return new chunk67OV5CX2_cjs.FheHandleNotAllowedError({ ...ctx, account: sender });
167
+ return new chunk2RNW4MIJ_cjs.FheHandleNotAllowedError({ ...ctx, account: sender });
168
168
  }
169
169
  case "InvalidSignature":
170
- return new chunk67OV5CX2_cjs.InvalidSignatureError(ctx);
170
+ return new chunk2RNW4MIJ_cjs.InvalidSignatureError(ctx);
171
171
  case "ERC7984UnauthorizedSpender": {
172
172
  const holder = errArgs?.[0];
173
173
  const spender = errArgs?.[1];
174
- return new chunk67OV5CX2_cjs.OperatorNotApprovedError({
174
+ return new chunk2RNW4MIJ_cjs.OperatorNotApprovedError({
175
175
  method: ctx.method,
176
176
  contractAddress: ctx.contractAddress,
177
177
  holder,
@@ -211,7 +211,7 @@ function defaultRevertNameMapper(name, errArgs, ctx, account) {
211
211
  case "ArrayLengthMismatch":
212
212
  case "VestingIdsNotStrictlyAsc":
213
213
  case "CustomFeeNotSet":
214
- return new chunk67OV5CX2_cjs.InvalidArgumentError({
214
+ return new chunk2RNW4MIJ_cjs.InvalidArgumentError({
215
215
  method: ctx.method,
216
216
  argument: invalidArgumentLabelFor(name),
217
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.`
@@ -284,7 +284,7 @@ function isContractRevert(error) {
284
284
  }
285
285
  function classifyNonRevert(error, args) {
286
286
  if (!(error instanceof viem.BaseError)) {
287
- return new chunk67OV5CX2_cjs.UnknownWriteFailureError({
287
+ return new chunk2RNW4MIJ_cjs.UnknownWriteFailureError({
288
288
  method: args.method,
289
289
  contractAddress: args.contractAddress,
290
290
  cause: error
@@ -295,21 +295,21 @@ function classifyNonRevert(error, args) {
295
295
  return predicate(frame) ? frame : void 0;
296
296
  };
297
297
  if (walk((e) => e instanceof viem.UserRejectedRequestError) || walk((e) => e instanceof viem.TransactionRejectedRpcError)) {
298
- return new chunk67OV5CX2_cjs.WalletRejectedError({
298
+ return new chunk2RNW4MIJ_cjs.WalletRejectedError({
299
299
  method: args.method,
300
300
  contractAddress: args.contractAddress,
301
301
  cause: error
302
302
  });
303
303
  }
304
304
  if (walk((e) => e instanceof viem.ChainMismatchError)) {
305
- return new chunk67OV5CX2_cjs.WalletChainMismatchError({
305
+ return new chunk2RNW4MIJ_cjs.WalletChainMismatchError({
306
306
  method: args.method,
307
307
  contractAddress: args.contractAddress,
308
308
  cause: error
309
309
  });
310
310
  }
311
311
  if (walk((e) => e instanceof viem.InsufficientFundsError)) {
312
- return new chunk67OV5CX2_cjs.InsufficientGasFundsError({
312
+ return new chunk2RNW4MIJ_cjs.InsufficientGasFundsError({
313
313
  method: args.method,
314
314
  contractAddress: args.contractAddress,
315
315
  cause: error
@@ -317,7 +317,7 @@ function classifyNonRevert(error, args) {
317
317
  }
318
318
  const httpFrame = walk((e) => e instanceof viem.HttpRequestError);
319
319
  if (httpFrame) {
320
- return new chunk67OV5CX2_cjs.NetworkError({
320
+ return new chunk2RNW4MIJ_cjs.NetworkError({
321
321
  method: args.method,
322
322
  contractAddress: args.contractAddress,
323
323
  statusCode: httpFrame.status,
@@ -325,13 +325,13 @@ function classifyNonRevert(error, args) {
325
325
  });
326
326
  }
327
327
  if (walk((e) => e instanceof viem.TimeoutError)) {
328
- return new chunk67OV5CX2_cjs.NetworkError({
328
+ return new chunk2RNW4MIJ_cjs.NetworkError({
329
329
  method: args.method,
330
330
  contractAddress: args.contractAddress,
331
331
  cause: error
332
332
  });
333
333
  }
334
- return new chunk67OV5CX2_cjs.UnknownWriteFailureError({
334
+ return new chunk2RNW4MIJ_cjs.UnknownWriteFailureError({
335
335
  method: args.method,
336
336
  contractAddress: args.contractAddress,
337
337
  cause: error
@@ -1,4 +1,4 @@
1
- import { TokenOpsValidationError } from './chunk-QBJ7O2B4.js';
1
+ import { TokenOpsValidationError } from './chunk-DRSPMIZ7.js';
2
2
  import { isAddress, getAddress } from 'viem';
3
3
 
4
4
  function normaliseAddress(input) {
@@ -1,6 +1,7 @@
1
- import { normaliseAddress } from './chunk-ZA673I3O.js';
1
+ import { erc7984OperatorAbi } from './chunk-7VELUOMI.js';
2
+ import { normaliseAddress } from './chunk-46T67CE2.js';
3
+ import { TokenOpsContractError } from './chunk-DRSPMIZ7.js';
2
4
  import { withTelemetry } from './chunk-Q2GP5UDC.js';
3
- import { TokenOpsContractError } from './chunk-QBJ7O2B4.js';
4
5
 
5
6
  // src/fhe/operators.ts
6
7
  var ERC7984_SET_OPERATOR_ABI = [
@@ -92,5 +93,62 @@ async function revokeOperator(args) {
92
93
  });
93
94
  });
94
95
  }
96
+ async function isOperator(args) {
97
+ const token = normaliseAddress(args.token);
98
+ const holder = normaliseAddress(args.holder);
99
+ const spender = normaliseAddress(args.spender);
100
+ return withTelemetry(args.telemetry, "fhe.isOperator", async () => {
101
+ try {
102
+ return await args.publicClient.readContract({
103
+ address: token,
104
+ abi: erc7984OperatorAbi,
105
+ functionName: "isOperator",
106
+ args: [holder, spender]
107
+ });
108
+ } catch (err) {
109
+ throw new TokenOpsContractError(
110
+ `isOperator: readContract failed (token=${token}, holder=${holder}, spender=${spender})`,
111
+ { contract: "ERC7984", call: "isOperator", token, holder, spender, cause: err }
112
+ );
113
+ }
114
+ });
115
+ }
116
+ async function ensureOperator(args) {
117
+ return withTelemetry(args.telemetry, "fhe.ensureOperator", async () => {
118
+ if (args.deadline <= 0n || args.deadline > ERC7984_OPERATOR_MAX_DEADLINE) {
119
+ throw new TokenOpsContractError(
120
+ `ensureOperator: deadline ${args.deadline} out of range (0, ${ERC7984_OPERATOR_MAX_DEADLINE}]`,
121
+ { contract: "ERC7984", call: "ensureOperator", deadline: args.deadline.toString() }
122
+ );
123
+ }
124
+ const fromAccount = args.account ?? args.walletClient.account;
125
+ if (!fromAccount) {
126
+ throw new TokenOpsContractError(
127
+ "ensureOperator: no account available \u2014 pass `account` explicitly or attach an account to the walletClient",
128
+ { contract: "ERC7984", call: "ensureOperator" }
129
+ );
130
+ }
131
+ const holder = typeof fromAccount === "string" ? fromAccount : fromAccount.address;
132
+ const authorized = await isOperator({
133
+ publicClient: args.publicClient,
134
+ token: args.token,
135
+ holder,
136
+ spender: args.spender
137
+ });
138
+ if (authorized) {
139
+ return { alreadyOperator: true, hash: null };
140
+ }
141
+ const hash = await setOperator({
142
+ publicClient: args.publicClient,
143
+ walletClient: args.walletClient,
144
+ ...args.account !== void 0 ? { account: args.account } : {},
145
+ token: args.token,
146
+ spender: args.spender,
147
+ deadline: args.deadline,
148
+ ...args.waitForReceipt !== void 0 ? { waitForReceipt: args.waitForReceipt } : {}
149
+ });
150
+ return { alreadyOperator: false, hash };
151
+ });
152
+ }
95
153
 
96
- export { ERC7984_OPERATOR_MAX_DEADLINE, ERC7984_SET_OPERATOR_ABI, revokeOperator, setOperator };
154
+ export { ERC7984_OPERATOR_MAX_DEADLINE, ERC7984_SET_OPERATOR_ABI, ensureOperator, isOperator, revokeOperator, setOperator };
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkJK4XMMKM_cjs = require('./chunk-JK4XMMKM.cjs');
3
+ var chunkFWHYQZ5E_cjs = require('./chunk-FWHYQZ5E.cjs');
4
+ var chunkVR3FREBX_cjs = require('./chunk-VR3FREBX.cjs');
5
+ var chunk2RNW4MIJ_cjs = require('./chunk-2RNW4MIJ.cjs');
4
6
  var chunkVJKZWYYJ_cjs = require('./chunk-VJKZWYYJ.cjs');
5
- var chunk67OV5CX2_cjs = require('./chunk-67OV5CX2.cjs');
6
7
 
7
8
  // src/fhe/operators.ts
8
9
  var ERC7984_SET_OPERATOR_ABI = [
@@ -20,19 +21,19 @@ var ERC7984_SET_OPERATOR_ABI = [
20
21
  var ERC7984_OPERATOR_MAX_DEADLINE = (1n << 48n) - 1n;
21
22
  async function setOperator(args) {
22
23
  const { publicClient, walletClient, account, waitForReceipt = true, telemetry } = args;
23
- const token = chunkJK4XMMKM_cjs.normaliseAddress(args.token);
24
- const spender = chunkJK4XMMKM_cjs.normaliseAddress(args.spender);
24
+ const token = chunkVR3FREBX_cjs.normaliseAddress(args.token);
25
+ const spender = chunkVR3FREBX_cjs.normaliseAddress(args.spender);
25
26
  const deadline = args.deadline ?? ERC7984_OPERATOR_MAX_DEADLINE;
26
27
  return chunkVJKZWYYJ_cjs.withTelemetry(telemetry, "fhe.setOperator", async () => {
27
28
  const fromAccount = account ?? walletClient.account;
28
29
  if (!fromAccount) {
29
- throw new chunk67OV5CX2_cjs.TokenOpsContractError(
30
+ throw new chunk2RNW4MIJ_cjs.TokenOpsContractError(
30
31
  "setOperator: no account available \u2014 pass `account` explicitly or attach an account to the walletClient",
31
32
  { contract: "ERC7984", call: "setOperator" }
32
33
  );
33
34
  }
34
35
  if (deadline < 0n || deadline > ERC7984_OPERATOR_MAX_DEADLINE) {
35
- throw new chunk67OV5CX2_cjs.TokenOpsContractError(
36
+ throw new chunk2RNW4MIJ_cjs.TokenOpsContractError(
36
37
  `setOperator: deadline ${deadline} out of uint48 range [0, ${ERC7984_OPERATOR_MAX_DEADLINE}]`,
37
38
  { contract: "ERC7984", call: "setOperator", deadline: deadline.toString() }
38
39
  );
@@ -53,7 +54,7 @@ async function setOperator(args) {
53
54
  chain: walletClient.chain
54
55
  });
55
56
  } catch (err) {
56
- throw new chunk67OV5CX2_cjs.TokenOpsContractError(
57
+ throw new chunk2RNW4MIJ_cjs.TokenOpsContractError(
57
58
  `setOperator: writeContract failed (token=${token}, spender=${spender})`,
58
59
  {
59
60
  contract: "ERC7984",
@@ -68,7 +69,7 @@ async function setOperator(args) {
68
69
  try {
69
70
  await publicClient.waitForTransactionReceipt({ hash });
70
71
  } catch (err) {
71
- throw new chunk67OV5CX2_cjs.TokenOpsContractError(`setOperator: receipt wait failed (tx=${hash})`, {
72
+ throw new chunk2RNW4MIJ_cjs.TokenOpsContractError(`setOperator: receipt wait failed (tx=${hash})`, {
72
73
  contract: "ERC7984",
73
74
  call: "setOperator",
74
75
  tx: hash,
@@ -94,8 +95,67 @@ async function revokeOperator(args) {
94
95
  });
95
96
  });
96
97
  }
98
+ async function isOperator(args) {
99
+ const token = chunkVR3FREBX_cjs.normaliseAddress(args.token);
100
+ const holder = chunkVR3FREBX_cjs.normaliseAddress(args.holder);
101
+ const spender = chunkVR3FREBX_cjs.normaliseAddress(args.spender);
102
+ return chunkVJKZWYYJ_cjs.withTelemetry(args.telemetry, "fhe.isOperator", async () => {
103
+ try {
104
+ return await args.publicClient.readContract({
105
+ address: token,
106
+ abi: chunkFWHYQZ5E_cjs.erc7984OperatorAbi,
107
+ functionName: "isOperator",
108
+ args: [holder, spender]
109
+ });
110
+ } catch (err) {
111
+ throw new chunk2RNW4MIJ_cjs.TokenOpsContractError(
112
+ `isOperator: readContract failed (token=${token}, holder=${holder}, spender=${spender})`,
113
+ { contract: "ERC7984", call: "isOperator", token, holder, spender, cause: err }
114
+ );
115
+ }
116
+ });
117
+ }
118
+ async function ensureOperator(args) {
119
+ return chunkVJKZWYYJ_cjs.withTelemetry(args.telemetry, "fhe.ensureOperator", async () => {
120
+ if (args.deadline <= 0n || args.deadline > ERC7984_OPERATOR_MAX_DEADLINE) {
121
+ throw new chunk2RNW4MIJ_cjs.TokenOpsContractError(
122
+ `ensureOperator: deadline ${args.deadline} out of range (0, ${ERC7984_OPERATOR_MAX_DEADLINE}]`,
123
+ { contract: "ERC7984", call: "ensureOperator", deadline: args.deadline.toString() }
124
+ );
125
+ }
126
+ const fromAccount = args.account ?? args.walletClient.account;
127
+ if (!fromAccount) {
128
+ throw new chunk2RNW4MIJ_cjs.TokenOpsContractError(
129
+ "ensureOperator: no account available \u2014 pass `account` explicitly or attach an account to the walletClient",
130
+ { contract: "ERC7984", call: "ensureOperator" }
131
+ );
132
+ }
133
+ const holder = typeof fromAccount === "string" ? fromAccount : fromAccount.address;
134
+ const authorized = await isOperator({
135
+ publicClient: args.publicClient,
136
+ token: args.token,
137
+ holder,
138
+ spender: args.spender
139
+ });
140
+ if (authorized) {
141
+ return { alreadyOperator: true, hash: null };
142
+ }
143
+ const hash = await setOperator({
144
+ publicClient: args.publicClient,
145
+ walletClient: args.walletClient,
146
+ ...args.account !== void 0 ? { account: args.account } : {},
147
+ token: args.token,
148
+ spender: args.spender,
149
+ deadline: args.deadline,
150
+ ...args.waitForReceipt !== void 0 ? { waitForReceipt: args.waitForReceipt } : {}
151
+ });
152
+ return { alreadyOperator: false, hash };
153
+ });
154
+ }
97
155
 
98
156
  exports.ERC7984_OPERATOR_MAX_DEADLINE = ERC7984_OPERATOR_MAX_DEADLINE;
99
157
  exports.ERC7984_SET_OPERATOR_ABI = ERC7984_SET_OPERATOR_ABI;
158
+ exports.ensureOperator = ensureOperator;
159
+ exports.isOperator = isOperator;
100
160
  exports.revokeOperator = revokeOperator;
101
161
  exports.setOperator = setOperator;