@tokenops/sdk 2.0.0-alpha.3 → 2.0.0-alpha.4

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 (151) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +71 -35
  3. package/SUPPORT.md +1 -1
  4. package/dist/{chunk-NGU4JYIR.js → chunk-2ND7US2I.js} +1 -1
  5. package/dist/{chunk-U3TBQRT7.js → chunk-3ZSPRHR3.js} +55 -12
  6. package/dist/{chunk-BXX5TETM.js → chunk-67Q6KSVV.js} +4114 -3267
  7. package/dist/{chunk-JVNJSDQ4.cjs → chunk-6AYJRUGP.cjs} +7 -50
  8. package/dist/{chunk-AHU2HMNE.cjs → chunk-6FUHF73F.cjs} +5 -5
  9. package/dist/{chunk-NWFMBLSQ.js → chunk-7KY4K4PH.js} +1 -1
  10. package/dist/{chunk-YABHXJNH.cjs → chunk-ARZ5C3FS.cjs} +7 -7
  11. package/dist/{chunk-4IB4IY5Y.js → chunk-AX6MQAWU.js} +2 -2
  12. package/dist/{chunk-BPIVVCSQ.js → chunk-BSXEX2SH.js} +3 -3
  13. package/dist/{chunk-PK6Q6J5F.js → chunk-CHGY6B7L.js} +34 -81
  14. package/dist/{chunk-GKYZQJGP.cjs → chunk-DFK5GCJP.cjs} +55 -72
  15. package/dist/{chunk-4AWQJQXY.cjs → chunk-E4IV4M4E.cjs} +4126 -3279
  16. package/dist/{chunk-6HCT4QIX.js → chunk-GHGF65LN.js} +16 -13
  17. package/dist/{chunk-TWT3STIX.js → chunk-K3R27WX4.js} +8 -46
  18. package/dist/{chunk-GCJFAY4X.js → chunk-KJUNBOJQ.js} +52 -71
  19. package/dist/{chunk-6XOAYDC6.cjs → chunk-PO3A2AQX.cjs} +4 -4
  20. package/dist/{chunk-TND5NDV7.cjs → chunk-QHDMPT7I.cjs} +64 -19
  21. package/dist/{chunk-IEKTGJ4J.cjs → chunk-S47IBDTA.cjs} +36 -83
  22. package/dist/{chunk-UG5RKLU2.cjs → chunk-SUYBAF27.cjs} +1 -1
  23. package/dist/{chunk-WRWRA4TN.js → chunk-TWU7L5H7.js} +5 -48
  24. package/dist/{chunk-WJLECC22.cjs → chunk-UUSSWOPR.cjs} +9 -47
  25. package/dist/{chunk-YTIYTV4K.cjs → chunk-VHKMWWBA.cjs} +319 -40
  26. package/dist/{chunk-PG3WXT3K.cjs → chunk-YAL6JSAZ.cjs} +16 -13
  27. package/dist/{chunk-LEYYG4RF.js → chunk-YMWMIWBP.js} +315 -42
  28. package/dist/core/addresses.d.ts +4 -4
  29. package/dist/core/errors.d.ts +1 -1
  30. package/dist/core/index.d.ts +1 -0
  31. package/dist/core/pending.d.ts +57 -0
  32. package/dist/core/version.d.ts +1 -1
  33. package/dist/fhe/operators.d.ts +6 -5
  34. package/dist/fhe/types.d.ts +1 -1
  35. package/dist/fhe-airdrop/abis/airdrop-base.d.ts +81 -4
  36. package/dist/fhe-airdrop/abis/ecdsa.d.ts +101 -4
  37. package/dist/fhe-airdrop/abis/factory.d.ts +222 -4
  38. package/dist/fhe-airdrop/abis/merkle.d.ts +100 -4
  39. package/dist/fhe-airdrop/advanced/index.cjs +8 -4
  40. package/dist/fhe-airdrop/advanced/index.d.cts +1 -0
  41. package/dist/fhe-airdrop/advanced/index.d.ts +1 -0
  42. package/dist/fhe-airdrop/advanced/index.js +1 -1
  43. package/dist/fhe-airdrop/advanced/react/index.cjs +121 -143
  44. package/dist/fhe-airdrop/advanced/react/index.js +55 -77
  45. package/dist/fhe-airdrop/advanced/react/useClearCompliancePolicy.d.ts +3 -1
  46. package/dist/fhe-airdrop/advanced/react/useDisableCustomFee.d.ts +3 -1
  47. package/dist/fhe-airdrop/advanced/react/useFactoryComplianceDelegate.d.ts +2 -4
  48. package/dist/fhe-airdrop/advanced/react/useSetComplianceDelegate.d.ts +7 -7
  49. package/dist/fhe-airdrop/advanced/react/useSetComplianceManagerImpl.d.ts +3 -1
  50. package/dist/fhe-airdrop/advanced/react/useSetCompliancePolicy.d.ts +2 -1
  51. package/dist/fhe-airdrop/advanced/react/useSetCustomFee.d.ts +3 -1
  52. package/dist/fhe-airdrop/advanced/react/useSetDefaultDelegateToCompliance.d.ts +5 -4
  53. package/dist/fhe-airdrop/advanced/react/useSetDefaultGasFee.d.ts +3 -1
  54. package/dist/fhe-airdrop/advanced/react/useSetMaxGasFee.d.ts +7 -3
  55. package/dist/fhe-airdrop/airdrop-base.d.ts +95 -10
  56. package/dist/fhe-airdrop/campaign.d.ts +37 -73
  57. package/dist/fhe-airdrop/compliance-clone.d.ts +21 -0
  58. package/dist/fhe-airdrop/constants.d.ts +3 -1
  59. package/dist/fhe-airdrop/ecdsa.d.ts +67 -27
  60. package/dist/fhe-airdrop/encryption.d.ts +19 -6
  61. package/dist/fhe-airdrop/errors.d.ts +77 -2
  62. package/dist/fhe-airdrop/factory-params.d.ts +2361 -0
  63. package/dist/fhe-airdrop/factory.d.ts +142 -68
  64. package/dist/fhe-airdrop/guards.d.ts +110 -31
  65. package/dist/fhe-airdrop/index.cjs +79 -59
  66. package/dist/fhe-airdrop/index.d.cts +11 -9
  67. package/dist/fhe-airdrop/index.d.ts +11 -9
  68. package/dist/fhe-airdrop/index.js +5 -5
  69. package/dist/fhe-airdrop/merkle-tree.d.ts +9 -9
  70. package/dist/fhe-airdrop/merkle.d.ts +32 -11
  71. package/dist/fhe-airdrop/react/_shared.d.ts +12 -6
  72. package/dist/fhe-airdrop/react/index.cjs +294 -258
  73. package/dist/fhe-airdrop/react/index.d.cts +16 -9
  74. package/dist/fhe-airdrop/react/index.d.ts +16 -9
  75. package/dist/fhe-airdrop/react/index.js +114 -110
  76. package/dist/fhe-airdrop/react/keys.d.ts +46 -7
  77. package/dist/fhe-airdrop/react/useAccessEcdsaClaimAmount.d.ts +6 -3
  78. package/dist/fhe-airdrop/react/useAccessMerkleClaimAmount.d.ts +2 -2
  79. package/dist/fhe-airdrop/react/useAirdropConfig.d.ts +8 -3
  80. package/dist/fhe-airdrop/react/useAirdropPause.d.ts +5 -0
  81. package/dist/fhe-airdrop/react/useBuildMerkleCampaign.d.ts +4 -6
  82. package/dist/fhe-airdrop/react/useComplianceManager.d.ts +2 -2
  83. package/dist/fhe-airdrop/react/useComplianceManagerOf.d.ts +1 -2
  84. package/dist/fhe-airdrop/react/useCreateAndFundEcdsaAirdrop.d.ts +16 -11
  85. package/dist/fhe-airdrop/react/useCreateAndFundMerkleAirdrop.d.ts +23 -7
  86. package/dist/fhe-airdrop/react/useCreateEcdsaAirdrop.d.ts +13 -8
  87. package/dist/fhe-airdrop/react/useCreateMerkleAirdrop.d.ts +27 -7
  88. package/dist/fhe-airdrop/react/useDedupMode.d.ts +16 -0
  89. package/dist/fhe-airdrop/react/useDiscloseHandleToParty.d.ts +4 -3
  90. package/dist/fhe-airdrop/react/useEcdsaClaim.d.ts +6 -4
  91. package/dist/fhe-airdrop/react/useEcdsaClaimAndUnwrap.d.ts +15 -8
  92. package/dist/fhe-airdrop/react/useEcdsaDomain.d.ts +1 -1
  93. package/dist/fhe-airdrop/react/useEncryptCampaignAmounts.d.ts +5 -7
  94. package/dist/fhe-airdrop/react/useExtendClaimWindow.d.ts +4 -1
  95. package/dist/fhe-airdrop/react/useFundAirdrop.d.ts +3 -4
  96. package/dist/fhe-airdrop/react/useGrantInstanceRoles.d.ts +2 -1
  97. package/dist/fhe-airdrop/react/useIsSignatureValid.d.ts +5 -4
  98. package/dist/fhe-airdrop/react/useMerkleClaim.d.ts +4 -1
  99. package/dist/fhe-airdrop/react/useMerkleClaimAndUnwrap.d.ts +14 -3
  100. package/dist/fhe-airdrop/react/usePlanMerkleCampaign.d.ts +18 -3
  101. package/dist/fhe-airdrop/react/usePreflightClaim.d.ts +1 -1
  102. package/dist/fhe-airdrop/react/usePreflightCreateAirdrop.d.ts +34 -13
  103. package/dist/fhe-airdrop/react/useRescueERC20.d.ts +3 -3
  104. package/dist/fhe-airdrop/react/useRescueNativeToken.d.ts +23 -0
  105. package/dist/fhe-airdrop/react/useRotateMerkleRoot.d.ts +5 -2
  106. package/dist/fhe-airdrop/react/useSignClaimAuthorization.d.ts +4 -0
  107. package/dist/fhe-airdrop/react/useTokenOf.d.ts +29 -0
  108. package/dist/fhe-airdrop/react/useUnwrapRequest.d.ts +25 -0
  109. package/dist/fhe-airdrop/react/useWithdrawConfidential.d.ts +4 -0
  110. package/dist/fhe-airdrop/roles.d.ts +82 -7
  111. package/dist/fhe-airdrop/types.d.ts +109 -14
  112. package/dist/fhe-disperse/errors.d.ts +3 -3
  113. package/dist/fhe-disperse/index.cjs +22 -22
  114. package/dist/fhe-disperse/index.d.cts +2 -1
  115. package/dist/fhe-disperse/index.d.ts +2 -1
  116. package/dist/fhe-disperse/index.js +3 -3
  117. package/dist/fhe-disperse/react/index.cjs +27 -25
  118. package/dist/fhe-disperse/react/index.d.cts +2 -1
  119. package/dist/fhe-disperse/react/index.d.ts +2 -1
  120. package/dist/fhe-disperse/react/index.js +8 -6
  121. package/dist/fhe-disperse/react/useDisperse.d.ts +6 -2
  122. package/dist/fhe-disperse/react/useRegister.d.ts +9 -3
  123. package/dist/fhe-disperse/react/useSingletonWithdrawTokenFee.d.ts +7 -2
  124. package/dist/fhe-disperse/react/useWithdrawTokenFee.d.ts +7 -2
  125. package/dist/fhe-disperse/singleton.d.ts +33 -19
  126. package/dist/fhe-disperse/types.d.ts +48 -8
  127. package/dist/fhe-vesting/advanced/index.cjs +6 -6
  128. package/dist/fhe-vesting/advanced/index.js +4 -4
  129. package/dist/fhe-vesting/advanced/react/index.cjs +9 -9
  130. package/dist/fhe-vesting/advanced/react/index.js +6 -6
  131. package/dist/fhe-vesting/factory.d.ts +31 -8
  132. package/dist/fhe-vesting/index.cjs +23 -23
  133. package/dist/fhe-vesting/index.d.cts +3 -2
  134. package/dist/fhe-vesting/index.d.ts +3 -2
  135. package/dist/fhe-vesting/index.js +4 -4
  136. package/dist/fhe-vesting/manager.d.ts +27 -2
  137. package/dist/fhe-vesting/react/index.cjs +126 -126
  138. package/dist/fhe-vesting/react/index.d.cts +3 -2
  139. package/dist/fhe-vesting/react/index.d.ts +3 -2
  140. package/dist/fhe-vesting/react/index.js +5 -5
  141. package/dist/fhe-vesting/react/useCreateManager.d.ts +16 -9
  142. package/dist/fhe-vesting/react/useCreateManagerAndGetAddress.d.ts +9 -5
  143. package/dist/fhe-vesting/react/useSplitVesting.d.ts +9 -3
  144. package/dist/index.cjs +18 -18
  145. package/dist/index.js +1 -1
  146. package/dist/testnet-faucet/faucet.d.ts +3 -0
  147. package/dist/testnet-faucet/index.cjs +15 -15
  148. package/dist/testnet-faucet/index.js +3 -3
  149. package/dist/testnet-faucet/react/index.cjs +9 -9
  150. package/dist/testnet-faucet/react/index.js +4 -4
  151. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,59 @@ 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
+ ## [2.0.0-alpha.4] - 2026-10-01
9
+
10
+ > **Pre-release built against external-audit fixes that have not merged yet.** Continues the `2.0.0-alpha` line. `/fhe-airdrop` in this build targets `tokenops-fhe-airdrop-v2` at commit `005980df194e0dc66053bd517e9759bd4d580047` (branch `fix/external-audit-2026-09`), which carries the fixes for the external audit on top of the internal round but is still under review. Every Sepolia `fheAirdrop` address moved again in this build, to a preview deployment from `005980d` with the CREATE3 salt version `sdk-preview-13cad511`, set at deploy time; the contracts branch is unchanged and still names `2.0.2`, which is already consumed on Sepolia, so a redeploy needs its own fresh salt version. The factory is now `0xD5906f366Ff50909F403c8EDfDe680015563046a`. Breaking changes may land between any two alpha builds without a major bump (SemVer prerelease semantics). Published on the `alpha` dist-tag; `latest` stays on `1.6.0`. **For internal testing, not production value.**
11
+ >
12
+ > Migration guide: [`docs/migration/airdrop-v1-to-v2.md`](docs/migration/airdrop-v1-to-v2.md). Support policy: [`SUPPORT.md`](SUPPORT.md).
13
+
14
+ ### Changed - BREAKING
15
+
16
+ - **Every Merkle leaf input is bound to `(instance, instance)`, and anyone may submit a claim** (ext-audit-17). The contract now verifies each input against the airdrop itself, so the campaign builders encrypt every roster the same way: batched `MERKLE_BATCH_LIMIT` (32) values per proof, one relayer request per chunk, chunks concurrent. The payout still goes to `entry.account`, only `account` may redirect with `to`, and `claimAndUnwrap` stays self-only. A stranger who submits first settles the leaf confidentially, so a redirect or unwrap the recipient intended no longer happens; integrators check `getClaimedAmount` before offering a claim. ECDSA claims are unchanged.
17
+ - **Removed `submitter`, `CampaignSubmitterOption` and `CampaignEntry.boundTo`**, from `encryptCampaignAmounts`, `buildMerkleCampaign`, `planMerkleCampaign`, `rotateMerkleRoot` and their hooks. Removed rather than deprecated: the contract rejects any other binding, so an ignored option would mislead. Entries built under earlier alphas were bound differently and cannot be claimed against this factory.
18
+ - **`CreateAirdropResult` gained `managerImplementation` and `complianceDelegate`.** All three compliance fields are now read from the create receipt's `ComplianceManagerCloned` log instead of a follow-up registry call.
19
+ - **Indexers: two event topics moved.** `AirdropInitialized` was reshaped (it drops `deploymentBlockNumber`, now a getter, and gains the window, `canExtendClaimWindow`, `unwrappable` and `complianceManager`; the variant-specific fields arrive in the new `ECDSAAirdropInitialized(dedupMode)` and `MerkleAirdropInitialized(merkleRoot, isMerkleRootMutable)`), and the factory's `ComplianceManagerCloned` gained `managerImplementation`. A filter on either old signature matches nothing. The SDK resolves creates from `ConfidentialAirdropCreated` and is unaffected.
20
+ - **Factory compliance is ON from construction** (ext-audit-16). The constructor seeds a non-zero platform delegate with the default ON, so there is no post-deploy wiring step; `setComplianceDelegate` rotates but can never clear it, and `setDefaultDelegateToCompliance(true)` no longer depends on a prior delegate write.
21
+ - **Local fixtures: the factory constructor takes the compliance delegate** as a trailing argument and starts with compliance ON (ext-audit-16). `test/fhe-airdrop/helpers/deploy-v2.ts` passes it; a fixture that deploys the factory itself must do the same.
22
+ - **The React create, register, split and disperse hooks resolve `Mined | Pending`.** `useCreateManager`, `useCreateManagerAndGetAddress`, `useSplitVesting`, `useRegister`, `useDisperse`, `useWithdrawTokenFee`, `useSingletonWithdrawTokenFee` and the four airdrop create hooks accept `waitForReceipt`, and a hook cannot overload on it, so `data` / `mutateAsync` now resolve to the union of the mined result and its `Pending*Result`. Those fields now type as `X | undefined` on `data`, so code that used `data.manager`, `data.newVestingId`, `data.wallets` or `data.distributions` as `Address` / `Hex` / arrays no longer compiles. Headless calls are unaffected: the option is not on the base argument types, so a call or wrapper that never passes it keeps the mined result type. Migration: `if (data.pending) return;` before the read narrows to the mined result.
23
+
24
+ ### Added
25
+
26
+ - **Receipt-free writes for Safe / multisig signers.** A threshold Safe's `safeTxHash` never mines until owners co-sign, so every write that waited for its receipt hung. These now take `waitForReceipt?: boolean` (default `true`) and, with `false`, return on submission without polling: `/fhe-vesting` `createManager`, `createManagerAndGetAddress`, `splitVesting`; `/fhe-disperse` `register`, `disperse` (all three modes), `withdrawTokenFee`; `/fhe-airdrop` `createEcdsaAirdrop`, `createMerkleAirdrop`, `createAndFundEcdsaAirdrop`, `createAndFundMerkleAirdrop`. Their hooks pass the option through. The option stays off the exported argument types (`CreateManagerArgs`, `SplitVestingArgs`, `RegisterArgs`, `DisperseArgs`, `WithdrawTokenFeeArgs`, `CreateAirdropArgs`, `CreateMerkleAirdropArgs`): each method takes `Args & ReceiptMode<...>` instead, with the new `ReceiptMode` exported from the root and each product subpath, so a value typed as one of those types still resolves to the mined overload, and a runtime `boolean` is typed `Args & ReceiptMode`. Each method is overloaded on the literal: without `waitForReceipt: false` it returns the mined result exactly as before, every receipt-derived field set; with it, a new `Pending*Result` (`PendingCreateManagerResult`, `PendingSplitVestingResult`, `PendingRegisterResult`, `PendingDisperseResult`, `PendingWithdrawTokenFeeResult`, `PendingCreateAirdropResult`) whose receipt-derived field is `undefined`. Every pending result carries `pending: true` (the shared `PendingWrite` type, exported from the root and each product subpath) and every mined result declares `pending?: never` (`MinedWrite`), so `if (result.pending)` narrows the same way in all three products. A pending airdrop create still carries every address, built from its commitments, because the factory reverts unless it deploys exactly there. Encrypted views and disclosures are deliberately excluded: a Safe cannot user-decrypt in-session. `register` now takes the exported `RegisterArgs`, and `WithdrawTokenFeeArgs` is exported from `/fhe-disperse`. The ERC-7984 operator approval the funding flows need (`setOperator`, `ensureOperator`, `useEnsureOperator`) already took `waitForReceipt`; a Safe passes `false` there too.
27
+ - **`predictComplianceManagerClone`** (`/fhe-airdrop/advanced`), the compliance clone the factory deploys for an instance address.
28
+ - **`encodeInstanceRoleSplit`** with `InstanceRoleCall` and `InstanceRoleConstants`: a `planInstanceRoleSplit` plan as the ordered `grantRole` / `revokeRole` calls a Safe submits as one MultiSend batch. `grantInstanceRoles` stays receipt-bound, since each step must mine before the next simulates.
29
+ - **Create commitments** (ext-audit-09, -12). Every create now commits to the instance address, the compliance-manager implementation and the compliance delegate, and the factory reverts before consuming the salt when any of them drifted. The SDK quotes all three at send time; new `CreateCommitments` type, `CreateAirdropArgs.expected?: Partial<CreateCommitments>` to pin any of them instead, `factory.quoteCreateCommitments(...)`, `factory.resolveComplianceDelegate(creator)`, and a new `PreflightCreateResult` (what `preflightCreate` now returns) whose `commitments` is what the create would be sent with (`usePreflightCreateAirdrop` accepts `expected`). `quoteCreateCommitments` takes the new `AirdropVariantParams`, which ties `variant` to its params type. `preflightCreate` reports a pinned field that differs from the fresh quote as a `CreateCommitmentMismatchError` blocker. A planned Merkle campaign is created from its plan (next entry); `expected: { airdrop: plan.predictedAddress }` remains the lower-level way to pin the address alone.
30
+ - **Create a planned Merkle campaign from its plan.** `createMerkleAirdrop`, `createAndFundMerkleAirdrop`, their hooks, and the Merkle arm of `preflightCreate` / `usePreflightCreateAirdrop` take `plan?: PlannedCampaign` (new `CreateMerkleAirdropArgs`). A plan pins `expected.airdrop` to `plan.predictedAddress`, so the instance can no longer land at a fresh prediction its leaves are not bound to after an implementation rotation. The write path refuses before any RPC when `params.merkleRoot` differs from `plan.root` or a caller-supplied `expected.airdrop` differs from `plan.predictedAddress` (`InvalidArgumentError`), and refuses before the send when the factory's live Merkle init-code hash no longer equals `plan.initCodeHashAfter` (`PredictionDriftError`). `preflightCreate` reports the same conditions as blockers, plus an `expectedInitCodeHash` that disagrees with the plan. The ECDSA creates do not take `plan`.
31
+ - **`CreateCommitmentMismatchError`** (`TOKENOPS_CREATE_COMMITMENT_MISMATCH`), mapping `PredictionMismatch`, `ManagerImplementationMismatch` and `ComplianceDelegateMismatch`, with `context.field` (`"airdrop" | "complianceManagerImpl" | "complianceDelegate"`), `expected` and `actual`.
32
+ - **`EcdsaAirdropClient.readDedupMode()`** and **`useDedupMode`** (ext-audit-06). `EcdsaAirdropClientConfig.dedupMode` and the hook options that required it are now optional (see Deprecated).
33
+ - **`TOKENOPS_UNEXPECTED_CONTRACT_RESPONSE`** joins `TokenOpsSdkErrorCode`: a read that succeeded but returned a value this SDK cannot interpret. `readDedupMode()` throws it, with `context.{contractAddress, ordinal}`, for a `DedupMode` ordinal it does not know. Potentially breaking for an exhaustive `switch (err.code)` with a `never` check, which needs a case for it.
34
+ - **`canExtendClaimWindow()`** and **`deploymentBlockNumber()`** on every instance, both also returned by `useAirdropConfig`; **`factory.tokenOf(airdrop)`** and **`useTokenOf`**, the token `fundAirdrop` pulls (ext-audit-03, -06).
35
+ - **`rescueNativeToken({ recipient })`** and **`useRescueNativeToken`** (`RESCUER_ROLE`), sweeping ETH from a zero-fee campaign (ext-audit-07).
36
+ - **`ClawbackRequiresPauseError`** (`TOKENOPS_CLAWBACK_REQUIRES_PAUSE`): `withdrawConfidential` is refused while the campaign is unpaused inside its claim window (ext-audit-11). **`NativeRescueRequiresZeroFeeError`** (`TOKENOPS_NATIVE_RESCUE_REQUIRES_ZERO_FEE`) for a rescue on a fee-charging campaign.
37
+ - `InvalidImplementation` and `UninitializedHandle` map to `InvalidArgumentError`. `InitializationMismatch` and `UninitializedDelivery` are deliberately left to `ContractRevertError`: a platform or token fault, not a state a caller can act on.
38
+ - **`parseUnwrapRequest(receipt | logs)`**, **`readUnwrapRequest(hash)`** (on every instance client) and **`useUnwrapRequest`**, with the new `UnwrapRequest` and `UnwrapRequestSource` types. They decode the instance's `ClaimedAndUnwrapInitiated` event into `{ claimant, beneficiary, claimKey, unwrapRequestId }`, so the `finalizeUnwrap` that releases the underlying ERC-20 can be sent without decoding logs by hand (with Zama's SDK, `sdk.createToken(wrapper).finalizeUnwrap(unwrapRequestId)`). `claimAndUnwrap` itself still returns a bare hash. Only this instance's event counts; two in one transaction throw `ReceiptEventAmbiguousError`.
39
+ - **`MERKLE_BATCH_LIMIT`** is exported.
40
+ - **`/fhe-airdrop/react` now exports `GasFeeNotAcceptedError`, `UnrecognisedAirdropError`, `UINT96_MAX` and `UseDedupModeArgs`**, matching the headless barrel. The hooks could already raise both errors, and `UINT96_MAX` is the accept-any value for `common.maxAcceptedGasFee`.
41
+
42
+ ### Deprecated
43
+
44
+ - **`EcdsaAirdropClientConfig.dedupMode`, `EcdsaAirdropClient.dedupMode` and the `dedupMode` option of the ECDSA hooks.** No method branches on them; read the policy from the chain with `readDedupMode()` / `useDedupMode`. They will be removed before `2.0.0`.
45
+
46
+ ### Fixed
47
+
48
+ - **`preflightCreate` blocks a resolved fee above the factory's `maxGasFee` ceiling.** Lowering the ceiling never rewrites a configured fee, so a create could revert `GasFeeExceedsMaximum` while the preflight reported `ready: true`. The blocker is the same `InvalidArgumentError` (on `gasFee`) that the revert maps to, so a UI sees one code on both paths.
49
+ - **`preflightCreate` blocks params the instance initializer would revert on.** A zero `common.token` (`ZeroToken`), an `endTime` less than 60 seconds ahead of the local clock (`EndTimeInPast`), a `startTime` after `endTime` (`InvalidStartTime`) or equal to it (`InvalidDuration`), a zero `merkleRoot` on an immutable-root campaign (`ZeroMerkleRoot`) and a zero ECDSA `signer` (`ZeroSigner`) are each an `InvalidArgumentError` blocker naming the field. These revert after the factory's own checks, so `ready` was `true` for a create that could never land. The write path still surfaces them through its simulate.
50
+ - **A create whose resolved fee exceeds both the factory's `maxGasFee` ceiling and the creator's `maxAcceptedGasFee` now reports the ceiling.** The factory checks the ceiling first (`GasFeeExceedsMaximum`), but the write path only compared against the creator's bound and threw `GasFeeNotAcceptedError`, so a creator who raised their bound then hit the ceiling. The four `create*` methods now read `maxGasFee()` alongside the resolved fee and throw the ceiling's `InvalidArgumentError` (on `gasFee`) first, matching `preflightCreate`.
51
+ - `setEcdsaImplementation`, `setMerkleImplementation` and `setComplianceManagerImpl` document the `InvalidImplementation` revert (a codeless address or an EIP-7702 designator), not only the zero-address one.
52
+ - **The docs said `dedupMode` could not be read from the chain.** The contract now exposes it, and `docs/fhe-airdrop/README.md`, the root README and the migration guide describe the getter and the init event instead of telling consumers to record the value themselves.
53
+ - The docs now state that `claimAndUnwrap` publishes the unwrapped amount from the claim transaction on, not at `finalizeUnwrap` (ext-audit-13).
54
+ - **`preflightDisperse` checks the sender's singleton approval in every mode.** `DisperseConfidential` pulls from `msg.sender` in the wallet, direct and wallet-token-fee entrypoints and in the token-fee pull, but the preflight only read `isOperator(sender, singleton)` in direct mode, so a wallet-mode sender who had approved only the subwallets got `ready: true` and then an `ERC7984UnauthorizedSpender` revert. It now reads the approval for every mode and reports `SingletonNotApprovedError` whenever it is missing; the subwallet blocker stays for the wallet modes. `PreflightReport.hasApprovedSingleton` narrows from `boolean | null` to `boolean`: code that tested it for truthiness is unaffected, while a check for `=== null` (the old wallet-mode value) no longer matches.
55
+ - Airdrop revert decoding keeps the arguments the ABI carries: `InvalidMerkleProof` puts `{ leaf, merkleRoot }` and `InvalidImplementation` the rejected address in `context.value`, and `EndTimeInPast` / `InvalidDuration` now map in the airdrop mapper with both timestamps in the message and `context.value`, instead of the default mapper's argument-free bucket.
56
+ - **`ClaimNotStartedError` and `ClaimWindowClosedError` now carry their times from a real revert.** The contract's `uint32` start / end time decodes as a `number` in viem, which the mapper dropped, so `startTime` / `endTime` (and the "Opens at" / "Closed at" message detail) were `undefined` on chain-decoded errors; `currentTime` was likewise lost. Both are now read whether viem returns a `number` or a `bigint`.
57
+ - **Docs: ECDSA empty proofs, the signer key and `claimAndUnwrap` disclosure.** The ECDSA claim TSDoc and guide no longer say every input is bound to `(instance, claimant)`: only a non-empty proof is, while an empty proof accepts a zero handle or one the claimant and instance already hold ACL on, and campaigns that may reissue vouchers should use `perDedupId` or `both`. `SIGNER_ROLE` is documented as a key that must never be EIP-7702-delegated, with `isSignatureValid` as the pre-check. `claimAndUnwrap` and its hooks now state that the amount is public from the claim transaction, not at `finalizeUnwrap`.
58
+ - **Create preflights no longer go stale after a write that moves them.** The nine factory-admin hooks that move a create's commitments or fee checks (`useSetComplianceDelegate`, `useSetDefaultDelegateToCompliance`, `useSetCompliancePolicy`, `useClearCompliancePolicy`, `useSetComplianceManagerImpl`, `useSetDefaultGasFee`, `useSetCustomFee`, `useDisableCustomFee`, `useSetMaxGasFee`) and the four create hooks now invalidate every cached `usePreflightCreateAirdrop` report on the factory. A create also consumes its salt, so a cached `ready: true` for it is now invalidated too.
59
+ - **`useComplianceManagerOf` no longer caches a zero answer forever** (the new `useTokenOf` follows the same policy). A non-zero mapping is still held indefinitely, but a zero (not created yet, or not from this factory) stays stale, the same policy as `useIsAirdrop`. The four create hooks also invalidate both entries for the address they deploy, so a zero read of a predicted address updates as soon as the create lands.
60
+
8
61
  ## [2.0.0-alpha.3] - 2026-09-16
9
62
 
10
63
  > **Pre-release built against internally-audited, externally-unaudited contracts.** Continues the `2.0.0-alpha` line. `/fhe-airdrop` in this build targets `tokenops-fhe-airdrop-v2` at commit `c5c12c2087f61bcd5bd1daa802563ed3cd68f002`, which closes the internal audit round (audit-03 through audit-16) but has not completed an external security audit. Every Sepolia `fheAirdrop` address moved again in this build. Breaking changes may land between any two alpha builds without a major bump (SemVer prerelease semantics). Published on the `alpha` dist-tag; `latest` stays on `1.6.0`. **For internal testing, not production value.**
package/README.md CHANGED
@@ -21,24 +21,24 @@ Node `>= 22` is required (constraint from `@zama-fhe/sdk`).
21
21
 
22
22
  All subpaths are independently tree-shakeable. The `/react` paths keep React deps out of the server bundle.
23
23
 
24
- | Subpath | Status | Description |
25
- | ------------------------------------------ | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
- | `@tokenops/sdk` | stable | Root re-exports (version, core error types) |
27
- | `@tokenops/sdk/telemetry` | stable | Telemetry sink adapters (`NoopTelemetry`, `ConsoleTelemetry`, `TokenOpsTelemetry`) |
28
- | `@tokenops/sdk/fhe` | stable | FHE utility helpers (ratio scaling, encrypted-input types, operator helpers) |
29
- | `@tokenops/sdk/fhe/react` | stable | React hooks for shared FHE utilities |
30
- | `@tokenops/sdk/fhe-vesting` | factory live on Sepolia | Confidential vesting (LibClone + packed immutable args) |
31
- | `@tokenops/sdk/fhe-vesting/react` | stable | React/wagmi hooks for confidential vesting |
32
- | `@tokenops/sdk/fhe-vesting/advanced` | stable | Pre-mine address prediction (`predictManagerAddress`) |
33
- | `@tokenops/sdk/fhe-vesting/advanced/react` | stable | React hooks for advanced vesting flows |
34
- | `@tokenops/sdk/fhe-airdrop` | **v2, unaudited** — `alpha` dist-tag, factory live on Sepolia | ECDSA + Merkle confidential airdrop campaigns — see [Compatibility](#compatibility) |
35
- | `@tokenops/sdk/fhe-airdrop/react` | **v2, unaudited** | React/wagmi hooks for recipients, campaign creators, campaign owners and the compliance manager |
36
- | `@tokenops/sdk/fhe-airdrop/advanced` | **v2, unaudited** | Prediction-freshness / salt-availability guards for CREATE2 address prediction (prediction itself is on the headline `ConfidentialAirdropFactoryClient`) |
37
- | `@tokenops/sdk/fhe-airdrop/advanced/react` | **v2, unaudited** | Address-prediction hooks plus the factory-admin tier (fee setters, implementation pointers, policy setters) |
38
- | `@tokenops/sdk/fhe-disperse` | singleton live on mainnet + Sepolia | DisperseConfidential (singleton + per-user wallet-pair clones) |
39
- | `@tokenops/sdk/fhe-disperse/react` | stable | React/wagmi hooks for confidential disperse |
40
- | `@tokenops/sdk/testnet-faucet` | token pair live on Sepolia | Testnet faucet for the TokenOps test-token pair — TTT (plain ERC-20) + CTTT (ERC-7984 wrapper); open mint |
41
- | `@tokenops/sdk/testnet-faucet/react` | stable | React/wagmi hooks for the testnet faucet |
24
+ | Subpath | Status | Description |
25
+ | ------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
26
+ | `@tokenops/sdk` | stable | Root re-exports (version, core error types) |
27
+ | `@tokenops/sdk/telemetry` | stable | Telemetry sink adapters (`NoopTelemetry`, `ConsoleTelemetry`, `TokenOpsTelemetry`) |
28
+ | `@tokenops/sdk/fhe` | stable | FHE utility helpers (ratio scaling, encrypted-input types, operator helpers) |
29
+ | `@tokenops/sdk/fhe/react` | stable | React hooks for shared FHE utilities |
30
+ | `@tokenops/sdk/fhe-vesting` | factory live on Sepolia | Confidential vesting (LibClone + packed immutable args) |
31
+ | `@tokenops/sdk/fhe-vesting/react` | stable | React/wagmi hooks for confidential vesting |
32
+ | `@tokenops/sdk/fhe-vesting/advanced` | stable | Pre-mine address prediction (`predictManagerAddress`) |
33
+ | `@tokenops/sdk/fhe-vesting/advanced/react` | stable | React hooks for advanced vesting flows |
34
+ | `@tokenops/sdk/fhe-airdrop` | **v2, external-audit fixes unmerged** — `alpha` dist-tag, factory live on Sepolia | ECDSA + Merkle confidential airdrop campaigns — see [Compatibility](#compatibility) |
35
+ | `@tokenops/sdk/fhe-airdrop/react` | **v2, external-audit fixes unmerged** | React/wagmi hooks for recipients, campaign creators, campaign owners and the compliance manager |
36
+ | `@tokenops/sdk/fhe-airdrop/advanced` | **v2, external-audit fixes unmerged** | Prediction-freshness / salt-availability guards and `predictComplianceManagerClone` (instance-address prediction itself is on the headline `ConfidentialAirdropFactoryClient`) |
37
+ | `@tokenops/sdk/fhe-airdrop/advanced/react` | **v2, external-audit fixes unmerged** | Address-prediction hooks plus the factory-admin tier (fee setters, implementation pointers, policy setters) |
38
+ | `@tokenops/sdk/fhe-disperse` | singleton live on mainnet + Sepolia | DisperseConfidential (singleton + per-user wallet-pair clones) |
39
+ | `@tokenops/sdk/fhe-disperse/react` | stable | React/wagmi hooks for confidential disperse |
40
+ | `@tokenops/sdk/testnet-faucet` | token pair live on Sepolia | Testnet faucet for the TokenOps test-token pair — TTT (plain ERC-20) + CTTT (ERC-7984 wrapper); open mint |
41
+ | `@tokenops/sdk/testnet-faucet/react` | stable | React/wagmi hooks for the testnet faucet |
42
42
 
43
43
  The canonical exports map is in [`package.json`](https://github.com/VestingLabs/tokenops-sdk/blob/main/package.json) under `"exports"`.
44
44
 
@@ -46,11 +46,13 @@ The canonical exports map is in [`package.json`](https://github.com/VestingLabs/
46
46
 
47
47
  Which contract generation a given SDK version talks to, and where that generation is deployed:
48
48
 
49
- | SDK | Airdrop contracts | Contract commit | Chains |
50
- | ------------------------- | ------------------------ | --------------- | ------- |
51
- | `2.0.0-alpha.3` | v2 (internal audit only) | `c5c12c2` | Sepolia |
52
- | `2.0.0-alpha.1`-`alpha.2` | v2 (unaudited) | `7175ac8` | Sepolia |
53
- | `1.x` | v1 | n/a | Sepolia |
49
+ | SDK | Airdrop contracts | Contract commit | Chains |
50
+ | --------------- | --------------------------------------------- | --------------- | ------- |
51
+ | `2.0.0-alpha.4` | v2 (external audit fixes, preview deployment) | `005980d` | Sepolia |
52
+ | `2.0.0-alpha.3` | v2 (internal audit only) | `c5c12c2` | Sepolia |
53
+ | `2.0.0-alpha.2` | v2 (unaudited) | `7175ac8` | Sepolia |
54
+ | `2.0.0-alpha.1` | v2 (unaudited) | `8e5b144` | Sepolia |
55
+ | `1.x` | v1 | n/a | Sepolia |
54
56
 
55
57
  Alpha builds are pinned to a contract generation, and every generation so far has moved every
56
58
  deployed address. An instance created through one generation's factory is unreachable from a
@@ -271,9 +273,10 @@ function DeployManager({
271
273
  "0x0000000000000000000000000000000000000000000000000000000000000001" as `0x${string}`,
272
274
  },
273
275
  {
274
- onSuccess: ({ manager }) => {
276
+ onSuccess: (result) => {
275
277
  queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-vesting"] });
276
- onDeployed(manager);
278
+ // A hook resolves `Mined | Pending`; only a pending result lacks `manager`.
279
+ if (!result.pending) onDeployed(result.manager);
277
280
  },
278
281
  },
279
282
  )
@@ -290,7 +293,7 @@ See [Confidential Vesting — React hooks on docs.tokenops.xyz](https://docs.tok
290
293
 
291
294
  ## Quickstart — confidential airdrop
292
295
 
293
- > **v2, internally audited only.** `/fhe-airdrop` targets `tokenops-fhe-airdrop-v2` (`c5c12c2`, Sepolia only, `alpha` dist-tag) - see [Compatibility](#compatibility). Migrating from `1.x`? See [`docs/migration/airdrop-v1-to-v2.md`](docs/migration/airdrop-v1-to-v2.md).
296
+ > **v2, external-audit fixes not yet merged.** `/fhe-airdrop` targets `tokenops-fhe-airdrop-v2` (`005980d`, a preview deployment on Sepolia only, `alpha` dist-tag) - see [Compatibility](#compatibility). Migrating from `1.x`? See [`docs/migration/airdrop-v1-to-v2.md`](docs/migration/airdrop-v1-to-v2.md).
294
297
 
295
298
  Two claim-authorisation variants share one factory: **ECDSA** (an off-chain signature from a `SIGNER_ROLE` holder) and **Merkle** (inclusion in a published tree). The example below is ECDSA — see [`docs/fhe-airdrop/README.md`](docs/fhe-airdrop/README.md#quickstart--merkle-campaign) for the Merkle flow.
296
299
 
@@ -324,9 +327,8 @@ const now = Math.floor(Date.now() / 1000);
324
327
  const userSalt = "0x0000000000000000000000000000000000000000000000000000000000000001" as const;
325
328
  const token = process.env.TOKEN as `0x${string}`;
326
329
 
327
- // Create. There is no `admin` field — the factory injects `admin = msg.sender`.
328
- // `dedupMode` is required and cannot be read back from the chain later; record
329
- // it yourself alongside the deployed address.
330
+ // Create. There is no `admin` field - the factory injects `admin = msg.sender`.
331
+ // `dedupMode` is frozen here; read it back later with `readDedupMode()`.
330
332
  const { airdrop: airdropAddress } = await factory.createEcdsaAirdrop({
331
333
  params: {
332
334
  common: {
@@ -336,6 +338,7 @@ const { airdrop: airdropAddress } = await factory.createEcdsaAirdrop({
336
338
  canExtendClaimWindow: false,
337
339
  unwrappable: false,
338
340
  complianceAdmin: zeroAddress, // zero => msg.sender
341
+ maxAcceptedGasFee: 2_000_000_000_000_000n, // 0.002 ETH; a higher resolved fee reverts
339
342
  },
340
343
  signer: account.address, // SIGNER_ROLE holder authorising claims
341
344
  dedupMode: "perAddress",
@@ -384,12 +387,11 @@ const airdrop = createEcdsaAirdropClient({
384
387
  publicClient,
385
388
  walletClient,
386
389
  address: airdropAddress,
387
- dedupMode: "perAddress", // must match what was passed at create — no getter exists
388
390
  });
389
391
  await airdrop.claim({ encryptedInput, dedupId, deadline, signer: account.address, signature });
390
392
  ```
391
393
 
392
- See [`docs/fhe-airdrop/README.md`](docs/fhe-airdrop/README.md) for the full API reference, the Merkle campaign flow, guardrails, and the unaudited-contracts warning.
394
+ See [`docs/fhe-airdrop/README.md`](docs/fhe-airdrop/README.md) for the full API reference, the Merkle campaign flow, guardrails, and the external-audit status warning.
393
395
 
394
396
  ### Quickstart — confidential airdrop (React hooks)
395
397
 
@@ -451,9 +453,10 @@ function AdminPanel({
451
453
  canExtendClaimWindow: false,
452
454
  unwrappable: false,
453
455
  complianceAdmin: "0x0000000000000000000000000000000000000000",
456
+ maxAcceptedGasFee: 2_000_000_000_000_000n, // 0.002 ETH
454
457
  },
455
458
  signer: "0xYourSignerAddress" as `0x${string}`,
456
- dedupMode: "perAddress", // record this — no on-chain getter exists later
459
+ dedupMode: "perAddress", // frozen at create; `useDedupMode` reads it back
457
460
  },
458
461
  mode: "clone",
459
462
  userSalt:
@@ -518,9 +521,7 @@ function RecipientClaim({
518
521
  signature: `0x${string}`;
519
522
  };
520
523
  }) {
521
- // `dedupMode` must match what the campaign was created with — the contract
522
- // has no on-chain getter, so a consumer must already know it.
523
- const claim = useEcdsaClaim({ address: airdropAddress, dedupMode: "perAddress" });
524
+ const claim = useEcdsaClaim({ address: airdropAddress });
524
525
 
525
526
  return (
526
527
  <button onClick={() => claim.mutate(claimPayload)} disabled={claim.isPending}>
@@ -780,6 +781,41 @@ function FaucetPanel() {
780
781
 
781
782
  See [Testnet Faucet — React hooks on docs.tokenops.xyz](https://docs.tokenops.xyz/sdk/testnet-faucet#react-hooks) for the full hook catalogue (reads, mints, metadata), address overrides, and query-key reference.
782
783
 
784
+ ## Safe and multisig signers
785
+
786
+ A threshold Safe's submission returns a `safeTxHash` that does not mine until the other owners
787
+ co-sign, so a write that waits for its receipt would hang. These writes take
788
+ `waitForReceipt: false` (default `true`) and return as soon as the submission hash is known; their
789
+ React hooks pass the option through.
790
+
791
+ The ERC-7984 operator approval every funding flow needs first is the same: `setOperator`,
792
+ `ensureOperator` and `useEnsureOperator` (`@tokenops/sdk/fhe`) wait for their receipt by default,
793
+ so a Safe passes `waitForReceipt: false` there too and lets the approval execute before the
794
+ funding transaction does. That covers vesting funding, every `disperse` mode and the airdrop
795
+ `createAndFund*` / `fundAirdrop` paths.
796
+
797
+ Each method is overloaded on the literal. Called without `waitForReceipt: false`, it returns the
798
+ mined result with every field set. Called with it, it returns a `Pending*Result` carrying
799
+ `pending: true` (the shared `PendingWrite` shape). The option is not part of the exported `*Args`
800
+ types, so a wrapper typed with one of them still gets the mined result; a runtime `boolean` goes
801
+ through the exported `ReceiptMode` (`args: CreateManagerArgs & ReceiptMode`) and gets the union of
802
+ the two. A hook cannot overload, so its `data` is that union too, and `if (data.pending)` narrows
803
+ it the same way in every product:
804
+
805
+ | Product | Method | Pending result's receipt-derived field |
806
+ | --------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
807
+ | `/fhe-vesting` | `createManager`, `createManagerAndGetAddress` | `manager: undefined` - read the executed tx's `ManagerCreated` log |
808
+ | `/fhe-vesting` | `splitVesting` | `newVestingId: undefined` - read the `VestingSplit` log |
809
+ | `/fhe-disperse` | `register` | `wallets: undefined` - read the `UserRegistered` log |
810
+ | `/fhe-disperse` | `disperse` | `distributions: undefined` - read the `WalletDistribution` / `DirectDistribution` logs |
811
+ | `/fhe-disperse` | `withdrawTokenFee` | `transferredHandle: undefined` - read the `TokenFeeWithdrawn` log |
812
+ | `/fhe-airdrop` | `createEcdsaAirdrop`, `createMerkleAirdrop`, `createAndFundEcdsaAirdrop`, `createAndFundMerkleAirdrop` | none: every address is the committed one |
813
+
814
+ Airdrop role splits go through `planInstanceRoleSplit` plus `encodeInstanceRoleSplit` as one
815
+ MultiSend batch; see [Safe and multisig signers](docs/fhe-airdrop/README.md#safe-and-multisig-signers).
816
+ Encrypted views and disclosures have no receipt-free option: their handle is only useful to an
817
+ account that can user-decrypt in-session, so run them from an EOA or a delegate.
818
+
783
819
  ## Peer dependencies
784
820
 
785
821
  | Package | Range | Required for |
package/SUPPORT.md CHANGED
@@ -42,6 +42,6 @@ that cannot be scoped to one subpath.
42
42
  ## Getting help
43
43
 
44
44
  - Migrating off v1 `/fhe-airdrop`: [`docs/migration/airdrop-v1-to-v2.md`](docs/migration/airdrop-v1-to-v2.md).
45
- - Module docs (v2, unaudited): [`docs/fhe-airdrop/README.md`](docs/fhe-airdrop/README.md).
45
+ - Module docs (v2, external-audit fixes unmerged): [`docs/fhe-airdrop/README.md`](docs/fhe-airdrop/README.md).
46
46
  - Security issues: see [`SECURITY.md`](SECURITY.md).
47
47
  - Everything else: open an issue against `VestingLabs/tokenops-sdk`.
@@ -1,4 +1,4 @@
1
- import { ConfidentialVestingFactoryClient, confidentialVestingFactoryAbi } from './chunk-WRWRA4TN.js';
1
+ import { ConfidentialVestingFactoryClient, confidentialVestingFactoryAbi } from './chunk-TWU7L5H7.js';
2
2
 
3
3
  // src/fhe-vesting/advanced/factory-advanced.ts
4
4
  var ConfidentialVestingFactoryAdvancedClient = class extends ConfidentialVestingFactoryClient {
@@ -1,7 +1,8 @@
1
- import { createConfidentialAirdropFactoryClient, resolveEncryptor, createAirdropBaseClient, createMerkleAirdropClient, createComplianceManagerClient, createEcdsaAirdropClient } from './chunk-BXX5TETM.js';
2
- import { getFheAirdropFactoryAddress } from './chunk-6HCT4QIX.js';
1
+ import { createConfidentialAirdropFactoryClient, resolveEncryptor, createAirdropBaseClient, createMerkleAirdropClient, createComplianceManagerClient, createEcdsaAirdropClient } from './chunk-67Q6KSVV.js';
2
+ import { getFheAirdropFactoryAddress } from './chunk-GHGF65LN.js';
3
3
  import { DeploymentAddressUnavailableError } from './chunk-QBJ7O2B4.js';
4
4
  import { useRef, useMemo } from 'react';
5
+ import { isAddressEqual, zeroAddress } from 'viem';
5
6
  import { usePublicClient, useWalletClient } from 'wagmi';
6
7
 
7
8
  var FHE_AIRDROP_KEY = "tokenops-sdk";
@@ -37,6 +38,10 @@ function normalizeKeyArg(value) {
37
38
  if (typeof value === "string" && value.startsWith("0x")) return value.toLowerCase();
38
39
  return value;
39
40
  }
41
+ function staleUntilNonZeroAddress(query) {
42
+ const data = query.state.data;
43
+ return data !== void 0 && !isAddressEqual(data, zeroAddress) ? Infinity : 0;
44
+ }
40
45
  function tryResolveAirdropFactoryAddress(override, chainId) {
41
46
  if (override) return override;
42
47
  if (chainId === void 0) return void 0;
@@ -161,7 +166,7 @@ function useMemoEcdsaAirdropClient(opts) {
161
166
  publicClient,
162
167
  ...walletClient ? { walletClient } : {},
163
168
  address: opts.address,
164
- dedupMode: opts.dedupMode,
169
+ ...opts.dedupMode !== void 0 ? { dedupMode: opts.dedupMode } : {},
165
170
  ...chainId !== void 0 ? { chainId } : {},
166
171
  ...telemetry ? { telemetry } : {}
167
172
  });
@@ -174,6 +179,7 @@ var AIRDROP_FN = {
174
179
  // instance reads
175
180
  claimedAmount: "getClaimedAmount",
176
181
  paused: "paused",
182
+ dedupMode: "dedupMode",
177
183
  window: "window",
178
184
  config: "config",
179
185
  merkleRoot: "merkleRoot",
@@ -190,20 +196,29 @@ var AIRDROP_FN = {
190
196
  resolvedGasFee: "resolvedGasFee",
191
197
  customFee: "customFee",
192
198
  implementations: "implementations",
199
+ tokenOf: "tokenOf",
200
+ complianceManagerOf: "complianceManagerOf",
193
201
  compliancePolicy: "compliancePolicy",
194
202
  upgradeabilityPolicy: "upgradeabilityPolicy",
195
203
  factoryHasRole: "factoryHasRole",
196
204
  factoryRoleMembers: "factoryRoleMembers",
205
+ effectiveDelegateToCompliance: "effectiveDelegateToCompliance",
206
+ effectiveUpgradeable: "effectiveUpgradeable",
207
+ complianceDelegate: "complianceDelegate",
208
+ ecdsaInitCodeHash: "getEcdsaInitCodeHash",
209
+ predictEcdsaAddress: "predictEcdsaAirdropAddress",
210
+ merkleInitCodeHash: "getMerkleInitCodeHash",
211
+ predictMerkleAddress: "predictMerkleAirdropAddress",
197
212
  // compliance-manager reads
198
213
  complianceInfo: "complianceInfo",
199
214
  activeDelegate: "isActiveDelegate",
200
- clientDelegates: "clientDelegates",
201
215
  complianceHasRole: "complianceHasRole",
202
216
  complianceRoleMembers: "complianceRoleMembers"
203
217
  };
204
218
  var airdropKeys = {
205
219
  /** One account's cumulative delivered total. Keyed on the CLAIM IDENTITY, never the sender. */
206
220
  claimedAmount: (s, account) => airdropQueryKey({ ...s, fn: AIRDROP_FN.claimedAmount, args: [account] }),
221
+ dedupMode: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.dedupMode, args: [] }),
207
222
  paused: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.paused, args: [] }),
208
223
  window: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.window, args: [] }),
209
224
  config: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.config, args: [] }),
@@ -221,15 +236,17 @@ var airdropKeys = {
221
236
  isSignatureValidAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.isSignatureValid }),
222
237
  fees: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.fees, args: [] }),
223
238
  customFee: (s, creator) => airdropQueryKey({ ...s, fn: AIRDROP_FN.customFee, args: [creator] }),
224
- customFeeAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.customFee }),
225
239
  resolvedGasFee: (s, creator) => airdropQueryKey({ ...s, fn: AIRDROP_FN.resolvedGasFee, args: [creator] }),
226
240
  /** Every creator's resolved fee; the default and any override both move it. */
227
241
  resolvedGasFeeAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.resolvedGasFee }),
242
+ registry: (s, offset, limit) => airdropQueryKey({ ...s, fn: AIRDROP_FN.registry, args: [offset, limit] }),
228
243
  /** Every registry page, whatever offset/limit was queried. */
229
244
  registryAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.registry }),
230
245
  isAirdrop: (s, candidate) => airdropQueryKey({ ...s, fn: AIRDROP_FN.isAirdrop, args: [candidate] }),
231
246
  /** Every genuineness answer from this factory; a create adds one address. */
232
247
  isAirdropAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.isAirdrop }),
248
+ tokenOf: (s, airdrop) => airdropQueryKey({ ...s, fn: AIRDROP_FN.tokenOf, args: [airdrop] }),
249
+ complianceManagerOf: (s, airdrop) => airdropQueryKey({ ...s, fn: AIRDROP_FN.complianceManagerOf, args: [airdrop] }),
233
250
  implementations: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.implementations, args: [] }),
234
251
  compliancePolicy: (s, creator) => airdropQueryKey({ ...s, fn: AIRDROP_FN.compliancePolicy, args: [creator] }),
235
252
  compliancePolicyAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.compliancePolicy }),
@@ -238,16 +255,42 @@ var airdropKeys = {
238
255
  factoryHasRole: (s, role, holder) => airdropQueryKey({ ...s, fn: AIRDROP_FN.factoryHasRole, args: [role, holder] }),
239
256
  factoryHasRoleAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.factoryHasRole }),
240
257
  factoryRoleMembers: (s, role) => airdropQueryKey({ ...s, fn: AIRDROP_FN.factoryRoleMembers, args: [role] }),
241
- factoryRoleMembersAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.factoryRoleMembers }),
258
+ effectiveDelegateToCompliance: (s, creator) => airdropQueryKey({ ...s, fn: AIRDROP_FN.effectiveDelegateToCompliance, args: [creator] }),
259
+ /** Every creator's effective compliance default; the factory-wide default moves them all. */
260
+ effectiveDelegateToComplianceAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.effectiveDelegateToCompliance }),
261
+ effectiveUpgradeable: (s, creator) => airdropQueryKey({ ...s, fn: AIRDROP_FN.effectiveUpgradeable, args: [creator] }),
262
+ /** Every creator's effective upgradeability; the factory-wide default moves them all. */
263
+ effectiveUpgradeableAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.effectiveUpgradeable }),
264
+ complianceDelegate: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.complianceDelegate, args: [] }),
265
+ /** Keyed on `mode` only: `params` does not affect the hash (see `useEcdsaInitCodeHash`). */
266
+ ecdsaInitCodeHash: (s, mode) => airdropQueryKey({ ...s, fn: AIRDROP_FN.ecdsaInitCodeHash, args: [mode] }),
267
+ ecdsaInitCodeHashAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.ecdsaInitCodeHash }),
268
+ predictEcdsaAddress: (s, mode, deployer, userSalt) => airdropQueryKey({ ...s, fn: AIRDROP_FN.predictEcdsaAddress, args: [mode, deployer, userSalt] }),
269
+ predictEcdsaAddressAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.predictEcdsaAddress }),
270
+ merkleInitCodeHash: (s, mode) => airdropQueryKey({ ...s, fn: AIRDROP_FN.merkleInitCodeHash, args: [mode] }),
271
+ merkleInitCodeHashAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.merkleInitCodeHash }),
272
+ predictMerkleAddress: (s, mode, deployer, userSalt) => airdropQueryKey({
273
+ ...s,
274
+ fn: AIRDROP_FN.predictMerkleAddress,
275
+ args: [mode, deployer, userSalt]
276
+ }),
277
+ predictMerkleAddressAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.predictMerkleAddress }),
242
278
  complianceInfo: (s) => airdropQueryKey({ ...s, fn: AIRDROP_FN.complianceInfo, args: [] }),
243
279
  activeDelegate: (s, delegate) => airdropQueryKey({ ...s, fn: AIRDROP_FN.activeDelegate, args: [delegate] }),
244
- activeDelegateAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.activeDelegate }),
245
- clientDelegates: (s, index) => airdropQueryKey({ ...s, fn: AIRDROP_FN.clientDelegates, args: [index] }),
246
- clientDelegatesAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.clientDelegates }),
247
280
  complianceHasRole: (s, role, holder) => airdropQueryKey({ ...s, fn: AIRDROP_FN.complianceHasRole, args: [role, holder] }),
248
281
  complianceHasRoleAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.complianceHasRole }),
249
- complianceRoleMembers: (s, role) => airdropQueryKey({ ...s, fn: AIRDROP_FN.complianceRoleMembers, args: [role] }),
250
- complianceRoleMembersAll: (s) => airdropQueryKeyPrefix({ ...s, fn: AIRDROP_FN.complianceRoleMembers })
282
+ complianceRoleMembers: (s, role) => airdropQueryKey({ ...s, fn: AIRDROP_FN.complianceRoleMembers, args: [role] })
251
283
  };
284
+ function invalidateAfterCreate(queryClient, scope, airdrop) {
285
+ for (const queryKey of [
286
+ airdropKeys.registryAll(scope),
287
+ airdropKeys.isAirdropAll(scope),
288
+ airdropKeys.preflightCreateAll(scope),
289
+ airdropKeys.tokenOf(scope, airdrop),
290
+ airdropKeys.complianceManagerOf(scope, airdrop)
291
+ ]) {
292
+ void queryClient.invalidateQueries({ queryKey });
293
+ }
294
+ }
252
295
 
253
- export { AIRDROP_FN, airdropKeys, airdropQueryKey, airdropQueryKeyPrefix, useMemoAirdropBaseClient, useMemoAirdropFactoryClient, useMemoComplianceManagerClient, useMemoEcdsaAirdropClient, useMemoMerkleAirdropClient };
296
+ export { AIRDROP_FN, airdropKeys, airdropQueryKey, airdropQueryKeyPrefix, invalidateAfterCreate, staleUntilNonZeroAddress, useMemoAirdropBaseClient, useMemoAirdropFactoryClient, useMemoComplianceManagerClient, useMemoEcdsaAirdropClient, useMemoMerkleAirdropClient };