2.0 is a major version because /fhe-airdrop now targets a different contract system. Everything else is the 1.x surface minus the names 1.6 already deprecated, plus the port to @zama-fhe/sdk 3.6. If you do not import /fhe-airdrop, expect a short upgrade: fix what typecheck reports, then re-check the encryptor and decryptor wiring.
Install and support#
The 2.0 line is published on the next dist-tag (v2.0.0-rc.1 today). latest stays on 1.6.0 until 2.0.0 ships, so a plain pnpm add @tokenops/sdk still installs 1.x.
pnpm add @tokenops/sdk@next
# Only if you encrypt or decrypt (the Zama peers are optional):
pnpm add @zama-fhe/sdk@~3.6.0
pnpm add @zama-fhe/react-sdk@~3.6.0 # React hooks only| Line | Support |
|---|---|
| 1.x | Critical security and correctness fixes only, for 90 days after 2.0.0 reaches latest. No feature work since 2026-09-05. Nothing in 1.x breaks or is withdrawn when 2.0.0 ships, and the v1 contracts stay live on-chain. |
| 2.0 release candidates | Published on next. The API is frozen: a later candidate carries fixes, not API changes, except the receipt-free and Safe create surface of /fhe-airdrop, which is @beta. |
| 2.0.0 and later | Moves to latest after the release candidates, then follows normal SemVer. |
Still running a v1 airdrop campaign? A 2.x client cannot address a v1 instance, so keep that code on 1.x until the campaign closes:
# Keep operating a v1 airdrop campaign on the 1.x line
pnpm add @tokenops/sdk@^1.6.0What breaks, module by module#
| Subpath | What breaks | Where to go |
|---|---|---|
/fhe-airdrop | Replaced. v2 is a different contract system behind the same subpath: separate ECDSA and Merkle clients, a new factory, a new EIP-712 claim signature signed by SIGNER_ROLE, renamed create params, and four new DEPLOYED_ADDRESSES keys. The v1 module is deleted. | Airdrop v1 to v2 |
/fhe-vesting | useGet* encrypted-view hooks removed; CreateVestingPreflightReport.blockers removed; PredictManagerArgs moved to /advanced(/react); PartialClaimArgs discriminated by feeType; role hooks name the grantee holder; create and split hooks resolve Mined | Pending. | Below |
/fhe-disperse | useGetEncryptedFeeReserve removed; PreflightReport.blockers removed; a missing approval is OperatorNotApprovedError; transferredHandle always set; role hooks take holder; register, disperse and withdraw hooks resolve Mined | Pending. | Below |
/fhe | EncryptedInput64, EncryptedHandle and ExternalInputProof removed; createSepoliaEncryptorWeb moved to /fhe/web; Encryptor and UserDecryptor follow Zama 3.6; useDecryptedHandle drops relayerParams. | Zama SDK 3.0 to 3.6 |
@tokenops/sdk | DisperseId, asDisperseId, ClientPair and ContractRef removed. | Below |
/testnet-faucet | No removals. Hooks return a constructor error as resolutionError instead of throwing during render, and the unsupported-chain error names Sepolia. Additions: gas headroom, query options, telemetry on hooks. | Testnet Faucet 2.0 |
/fhe-airdrop#
There is no in-place upgrade: v1 instances stay on v1 contracts, and a v2 campaign is created through the canonical v2 factory. The symbol-by-symbol delta, the new claim signature and the address registry reshape are in Migrating to airdrop v2. useIsOperator and useEnsureOperator are unaffected: they live on @tokenops/sdk/fhe/react.
/fhe-vesting#
useGetVestedAmount,useGetClaimableAmount,useGetTotalAllocation,useGetSettledAmountand theirUseGet*Argsare removed. Use theuseAccess*names; they are the same functions.CreateVestingPreflightReport.blockersis removed. UseblockerErrors;readyis unchanged.PredictManagerArgsmoved from/fhe-vesting/reactto/fhe-vesting/advanced/react(still exported from/fhe-vesting/advanced).PartialClaimArgsis discriminated byfeeType:{ feeType: FeeType.Gas, value }on a gas-fee manager,{ feeType: FeeType.DistributionToken }otherwise. A mismatchedmsg.valuenow fails to compile.useHasRole,useGrantRoleanduseRevokeRoletakeholder.account/accountTargetstill work and are deprecated.useCreateManager,useCreateManagerAndGetAddressanduseSplitVestingresolveMined | Pending; narrow before reading a receipt-derived field. Headless calls that never passwaitForReceiptkeep the mined result type. See Receipt-free writes.- The
fhe-vesting.react.hook.firedtelemetry event is gone. preflightClaimreports a claimant whose ETH balance is below the gas fee asInsufficientBalanceError(TOKENOPS_INSUFFICIENT_BALANCE,balanceKind: "eth"), notInsufficientFeeError. Typecheck does not catch this; update any branch onTOKENOPS_INSUFFICIENT_FEE.batchCreateVestingtakes at most 32 items, one input proof (MAX_EUINT64_PER_INPUT_PROOF), and refuses a larger batch withInvalidArgumentErrorbefore encrypting. Split bigger batches yourself.
// Before (1.x, deprecated in 1.6)
const access = useGetClaimableAmount({ address, encryptor });
// After (2.0): same mutation, the useAccess* name
const access = useAccessClaimableAmount({ address, encryptor });
access.mutate({ vestingId });const create = useCreateManager();
const { data } = create;
// Receipt-derived fields type as X | undefined, because the hook accepts
// waitForReceipt: false. Narrow before reading them.
if (data && !data.pending) {
console.log(data.manager);
}/fhe-disperse#
useGetEncryptedFeeReserveis removed. UseuseAccessEncryptedFeeReserve, the same function.PreflightReport.blockersis removed. UseblockerErrors.preflightDispersechecks the singleton approval in every mode and reports a missing one asOperatorNotApprovedError(TOKENOPS_OPERATOR_NOT_APPROVED).SingletonNotApprovedErroris deprecated and never raised, andPreflightReport.hasApprovedSingletonnarrows fromboolean | nulltoboolean, so an=== nullcheck is dead code.preflightDispersereports a sender whose ETH balance is below the disperse fee asInsufficientBalanceError(TOKENOPS_INSUFFICIENT_BALANCE), notInsufficientFeeError. Typecheck does not catch this; update any branch onTOKENOPS_INSUFFICIENT_FEE.dispersetakes at most 32 recipients in"direct"mode and 30 in"wallet"/"wallet-token-fee", one input proof (MAX_EUINT64_PER_INPUT_PROOF). A larger batch is refused withInvalidArgumentErrorbefore encrypting, andpreflightDispersereports it as a blocker onrecipientswithbatchOk: false.WithdrawTokenFeeResult.transferredHandleis always set;disperse()and the disclosure methods throwReceiptEventNotFoundErrorfor a reverted transaction instead of resolving empty.- Role hooks take
holder;useRegister,useDisperseanduseWithdrawTokenFeeresolveMined | Pending.
// Before (1.x)
report.blockers.forEach((msg) => show(msg));
// After (2.0)
report.blockerErrors.forEach((err) => show(err.message)); // or branch on err.code/fhe and the Zama peers#
The optional @zama-fhe/sdk and @zama-fhe/react-sdk peers move from ^3.0.0 to ~3.6.0. Upstream 3.6 removes RelayerNode, RelayerWeb and the SepoliaConfig / MainnetConfig presets in favour of createConfig plus ZamaSDK, and the SDK's Encryptor and UserDecryptor contracts follow. Product client signatures and the encryptUint64 outputs are unchanged.
createSepoliaEncryptorWebmoved to@tokenops/sdk/fhe/web;createSepoliaEncryptorandcreateMockEncryptorstay in/fhe.createSepoliaEncryptordropspoolSize,fheArtifactStorageandfheArtifactCacheTTL, gainsrpcUrlandauth;SepoliaEncryptorStorageis removed.useDecryptedHandletakesuserDecryptor: () => sdk.decryptionandaccount, and dropsrelayerParams.EncryptedInput64,EncryptedHandleandExternalInputProofare removed from/fhe. UseEncryptedInputandHexfrom viem.- A mined-but-reverted transaction is no longer a success.
setOperator(and sorevokeOperator,ensureOperatoranduseEnsureOperator) andmintMockERC7984throwTokenOpsContractErroron a reverted receipt instead of resolving. An out-of-rangedeadline/amountis nowInvalidArgumentError, a missing accountMissingAccountErrorand a rejected promptWalletRejectedError, where all three used to beTokenOpsContractError(TOKENOPS_CONTRACT_REVERT).
Every before/after is in Upgrading the Zama peers to 3.6; the 2.0 encryptors are documented in Encryptors and Decryption.
Root and cross-cutting#
DisperseIdandasDisperseIdare removed; the disperse singleton emits nobytes32id. UseHexfrom viem for an identifier of your own.ClientPairandContractRefare removed; nothing in the SDK took or returned them. Declare the shape locally.- Gas headroom. Every write sends its gas estimate padded by
DEFAULT_GAS_HEADROOM_PERCENT(25%), adjustable per client or hook withgasHeadroomPercentand per call withgas. See Gas headroom. - Wallet on the wrong chain.Every write refuses a wallet whose chain is not the public client's, with
WalletChainMismatchError, before estimating. - Error codes.
TokenOpsSdkErrorCodegains codes, includingTOKENOPS_MISSING_PEER_DEPENDENCYand the airdrop codes. An exhaustiveswitch (err.code)with anevercheck needs the new cases. - No render-time throws.The vesting, disperse and faucet hooks return a client constructor's error (a malformed
address, a badgasHeadroomPercent) asresolutionErrorinstead of throwing during render: queries stay disabled and mutations reject with it. An error boundary that caught the throw no longer sees it. - Faucet chain error. On an unsupported chain,
TestnetFaucetClientand the faucet hooks throwUnsupportedChainErrorwith a message that the faucet runs on Sepolia only, pluscontext.methodandcontext.hint. - Immutable reads cache forever.The vesting manager's immutable reads and the disperse
useWalletImplementation/useDeploymentBlockNumberdefault tostaleTime: Infinity. Passquery: { staleTime: 0 }for the old behaviour. - Read hooks take query on every product. See Read-hook query options.
Upgrade checklist#
- Decide what stays on 1.x: anything that operates a v1 airdrop campaign.
- Install
@tokenops/sdk@nextand move the Zama peers to~3.6.0together. - Run typecheck. Every removal and rename above fails to compile, so the error list is your work list. The error-code, batch-size and render-time changes do not fail to compile; check those by hand.
- Re-wire the encryptor and decryptor to the 3.6 shape and move any
createSepoliaEncryptorWebimport to/fhe/web. - Port
/fhe-airdropcode with the airdrop guide, against the canonical v2 factory. - Re-check any
err.codeswitch (TOKENOPS_INSUFFICIENT_FEE,TOKENOPS_CONTRACT_REVERT), any UI that readblockers, and any error boundary around a vesting, disperse or faucet hook.
Untyped JavaScript#
report.blockers is simply undefined. Search for the removed names listed on this page before shipping.