npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tokenops/sdk

v1.0.0

Published

Typed viem-first SDK for TokenOps FHEVM contracts (confidential vesting, confidential airdrops, confidential disperse) — consumed by tokenops-app (Next.js) and tokenops-api (Express).

Readme

@tokenops/sdk

npm version license

Typed viem-first SDK for TokenOps FHEVM contracts — confidential vesting, confidential airdrops, and confidential disperse. Works with viem publicClient / walletClient directly; no framework required.

Deployed factories only. The SDK calls into pre-deployed factories / singletons; there are no deployFactory helpers. Currently deployed: @tokenops/sdk/fhe-vesting (factory live on Sepolia), @tokenops/sdk/fhe-airdrop (factory live on Sepolia), @tokenops/sdk/fhe-disperse (singleton live on mainnet + Sepolia).

Install

pnpm add @tokenops/sdk viem @zama-fhe/sdk@^3
# React hook subpaths additionally need:
pnpm add wagmi react react-dom @tanstack/react-query @zama-fhe/react-sdk@^3

Node >= 22 is required (constraint from @zama-fhe/sdk).

Subpath layout

All subpaths are independently tree-shakeable. The /react paths keep React deps out of the server bundle.

| Subpath | Status | Description | | ------------------------------------------ | ----------------------------------- | ---------------------------------------------------------------------------------- | | @tokenops/sdk | stable | Root re-exports (version, core error types) | | @tokenops/sdk/telemetry | stable | Telemetry sink adapters (NoopTelemetry, ConsoleTelemetry, TokenOpsTelemetry) | | @tokenops/sdk/fhe | stable | FHE utility helpers (ratio scaling, encrypted-input types, operator helpers) | | @tokenops/sdk/fhe/react | stable | React hooks for shared FHE utilities | | @tokenops/sdk/fhe-vesting | factory live on Sepolia | Confidential vesting (LibClone + packed immutable args) | | @tokenops/sdk/fhe-vesting/react | stable | React/wagmi hooks for confidential vesting | | @tokenops/sdk/fhe-vesting/advanced | stable | Pre-mine address prediction (predictManagerAddress) | | @tokenops/sdk/fhe-vesting/advanced/react | stable | React hooks for advanced vesting flows | | @tokenops/sdk/fhe-airdrop | factory live on Sepolia | ConfidentialAirdrop (EIP-712 gated confidential claims) | | @tokenops/sdk/fhe-airdrop/react | stable | React/wagmi hooks for confidential airdrops | | @tokenops/sdk/fhe-airdrop/advanced | stable | Pre-mine address prediction (predictAirdropAddress) | | @tokenops/sdk/fhe-airdrop/advanced/react | stable | React hooks for advanced airdrop flows | | @tokenops/sdk/fhe-disperse | singleton live on mainnet + Sepolia | DisperseConfidential (singleton + per-user wallet-pair clones) | | @tokenops/sdk/fhe-disperse/react | stable | React/wagmi hooks for confidential disperse |

The canonical exports map is in package.json under "exports".

Quickstart — confidential vesting

import { createPublicClient, createWalletClient, http, parseEventLogs } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { RelayerNode, SepoliaConfig } from "@zama-fhe/sdk/node";
import {
  createConfidentialVestingFactoryClient,
  createConfidentialVestingManagerClient,
  confidentialVestingManagerAbi,
  erc7984OperatorAbi,
  FeeType,
} from "@tokenops/sdk/fhe-vesting";

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({ chain: sepolia, transport: http(process.env.RPC_URL) });
const walletClient = createWalletClient({
  account,
  chain: sepolia,
  transport: http(process.env.RPC_URL),
});

// Build the Zama encryptor for Sepolia.
// SepoliaConfig provides all required contract addresses and the public relayer URL.
// Replace process.env.RPC_URL with a private endpoint for production traffic.
const encryptor = new RelayerNode({
  transports: { [SepoliaConfig.chainId]: { ...SepoliaConfig, network: process.env.RPC_URL! } },
  getChainId: async () => sepolia.id,
});

const factory = createConfidentialVestingFactoryClient({ publicClient, walletClient });

// Deploy the clone and read its address from the ManagerCreated event.
// The factory packs the deployment block number into the clone's immutable
// args, so predict-then-deploy is unreliable on a live chain — use the
// receipt-parsing helper instead.
const token = process.env.ERC7984_TOKEN_ADDRESS as `0x${string}`;
const userSalt = "0x0000000000000000000000000000000000000000000000000000000000000001" as const;
const { manager: managerAddress } = await factory.createManagerAndGetAddress({
  token,
  userSalt,
});

// Authorise the manager clone to spend the funder's confidential tokens.
// ERC-7984's setOperator uses a uint48 unix deadline (not uint256) — passing the
// wrong type encodes silently. Use the exported ABI to avoid the footgun.

const FAR_FUTURE: number = 2_000_000_000; // ~year 2033, uint48 deadline
await walletClient.writeContract({
  address: token,
  abi: erc7984OperatorAbi,
  functionName: "setOperator",
  args: [managerAddress, FAR_FUTURE],
});

// Create a vesting schedule. Pass a plaintext bigint — the SDK encrypts it.
// amount is raw token units: 1_000_000n = 1 USDC at 6 decimals, 1e18 for 18-decimal tokens.
const manager = createConfidentialVestingManagerClient({
  publicClient,
  walletClient,
  address: managerAddress,
  encryptor,
});

const vestingHash = await manager.createVesting({
  params: {
    recipient: "0xRecipientAddress" as `0x${string}`,
    startTimestamp: Math.floor(Date.now() / 1000),
    endTimestamp: Math.floor(Date.now() / 1000) + 365 * 86400,
    cliffSeconds: 90 * 86400,
    releaseIntervalSecs: 86400,
    timelockSeconds: 0,
    initialUnlockBps: 0,
    cliffAmountBps: 0,
    isRevocable: true,
  },
  amount: 1_000_000n,
});

// Extract the vestingId from the VestingCreated event in the receipt.
// vestingId is a bytes32 (Hex) used in all subsequent calls: claim, split, disclose, etc.
const receipt = await publicClient.waitForTransactionReceipt({ hash: vestingHash });
const events = parseEventLogs({
  abi: confidentialVestingManagerAbi,
  eventName: "VestingCreated",
  logs: receipt.logs,
});
const vestingId = events[0]!.args.vestingId; // `0x${string}` — save this for later calls

// Claim vested tokens (recipient calls this).
// `ClaimArgs` is discriminated by the clone's `feeType` (read once via
// `manager.feeType()`):
//   - FeeType.Gas               → include `value: <wei>` (per-claim gas fee).
//   - FeeType.DistributionToken → omit `value` (token fee deducted on-chain).
await manager.claim({ vestingId, feeType: FeeType.DistributionToken });

See Confidential Vesting on docs.tokenops.xyz for the full API reference.

Quickstart — confidential vesting (React hooks)

Uses @tokenops/sdk/fhe-vesting/react. Requires wagmi, @tanstack/react-query, and @zama-fhe/react-sdk as additional peer deps.

import { useQueryClient } from "@tanstack/react-query";
import { useZamaSDK } from "@zama-fhe/react-sdk";
import {
  useCreateManagerAndGetAddress,
  useCreateVesting,
  useClaim,
  useManagerFeeInfo,
  FeeType,
  type VestingParams,
} from "@tokenops/sdk/fhe-vesting/react";

// Wrap your app in wagmi's <WagmiProvider> + <QueryClientProvider> +
// @zama-fhe/react-sdk's <ZamaProvider> before rendering this component.

function VestingManager({ managerAddress }: { managerAddress: `0x${string}` }) {
  const queryClient = useQueryClient();
  const zamaSDK = useZamaSDK();

  // Read the fee configuration once — needed to pass `value` on Gas-fee claims.
  const { data: feeInfo } = useManagerFeeInfo({ address: managerAddress });

  // Create a vesting schedule. Pass a plaintext bigint — the SDK encrypts it.
  const create = useCreateVesting({
    address: managerAddress,
    // Lazy factory: the SDK calls this per-encryption, picking up the live
    // ZamaSDK context at submit time rather than a stale mount-time capture.
    // `useZamaSDK()` returns `ZamaSDK`; the encrypt-capable interface lives
    // on `.relayer` (a `RelayerSDK`).
    encryptor: () => zamaSDK.relayer,
  });

  // Claim vested tokens. For Gas-fee managers, attach `value = fee`.
  const claim = useClaim({ address: managerAddress });

  const params: VestingParams = {
    recipient: "0xRecipient" as `0x${string}`,
    startTimestamp: Math.floor(Date.now() / 1000),
    endTimestamp: Math.floor(Date.now() / 1000) + 365 * 86400,
    cliffSeconds: 0,
    releaseIntervalSecs: 86400,
    timelockSeconds: 0,
    initialUnlockBps: 0,
    cliffAmountBps: 0,
    isRevocable: false,
  };

  return (
    <div>
      <button
        onClick={() =>
          create.mutate(
            { params, amount: 1_000_000n }, // 1 USDC at 6 decimals
            {
              onSuccess: () =>
                queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-vesting"] }),
            },
          )
        }
        disabled={create.isPending}
      >
        Create vesting
      </button>

      <button
        onClick={() =>
          claim.mutate({
            vestingId: "0xYourVestingId" as `0x${string}`,
            value: feeInfo?.feeType === FeeType.Gas ? feeInfo.fee : undefined,
          })
        }
        disabled={claim.isPending}
      >
        Claim
      </button>
    </div>
  );
}

// --- Deploy a manager clone first ---

function DeployManager({
  token,
  onDeployed,
}: {
  token: `0x${string}`;
  onDeployed: (manager: `0x${string}`) => void;
}) {
  const queryClient = useQueryClient();

  // Factory address resolves automatically from DEPLOYED_ADDRESSES on Sepolia/Mainnet.
  const create = useCreateManagerAndGetAddress();

  return (
    <button
      onClick={() =>
        create.mutate(
          {
            token,
            userSalt:
              "0x0000000000000000000000000000000000000000000000000000000000000001" as `0x${string}`,
          },
          {
            onSuccess: ({ manager }) => {
              queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-vesting"] });
              onDeployed(manager);
            },
          },
        )
      }
      disabled={create.isPending}
    >
      {create.isPending ? "Deploying…" : "Deploy manager"}
    </button>
  );
}

See Confidential Vesting — React hooks on docs.tokenops.xyz for the full hook catalogue, encryptor wiring, address overrides, and query-key reference.

Quickstart — confidential airdrop

import { createPublicClient, createWalletClient, http } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { RelayerNode, SepoliaConfig } from "@zama-fhe/sdk/node";
import {
  createConfidentialAirdropFactoryClient,
  createConfidentialAirdropClient,
  encryptUint64,
  signClaimAuthorization,
} from "@tokenops/sdk/fhe-airdrop";

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const rpcUrl = process.env.RPC_URL!;
const publicClient = createPublicClient({ chain: sepolia, transport: http(rpcUrl) });
const walletClient = createWalletClient({ account, chain: sepolia, transport: http(rpcUrl) });

const encryptor = new RelayerNode({
  transports: { [SepoliaConfig.chainId]: { ...SepoliaConfig, network: rpcUrl } },
  getChainId: () => Promise.resolve(sepolia.id),
});

// Factory address resolves automatically from DEPLOYED_ADDRESSES on Sepolia.
const factory = createConfidentialAirdropFactoryClient({
  publicClient,
  walletClient,
  encryptor,
});

const now = Math.floor(Date.now() / 1000);
const userSalt = "0x0000000000000000000000000000000000000000000000000000000000000001" as const;
const token = process.env.TOKEN as `0x${string}`;
const params = {
  token,
  startTimestamp: now + 60,
  endTimestamp: now + 30 * 86400,
  canExtendClaimWindow: false,
  admin: account.address,
};

// Deploy and read the clone address from the ConfidentialAirdropCreated event.
// (predictAirdropAddress also works — airdrop immutable args don't pack
// block.number, so the predicted address is stable across blocks.)
const { airdrop: airdropAddress } = await factory.createConfidentialAirdropAndGetAddress({
  params,
  userSalt,
});

// Admin: issue a signed claim authorization for a recipient. Bind the input
// proof to the RECIPIENT — Zama proofs commit to (contractAddress, userAddress)
// at encrypt time, and `FHE.fromExternal` on-chain rejects any other binding.
// amount is raw token units; ERC-7984 uses 6 decimals, so 1_000_000n = 1 token.
const recipient = "0xRecipientAddress" as `0x${string}`;
const encrypted = await encryptUint64({
  encryptor,
  contractAddress: airdropAddress,
  userAddress: recipient,
  value: 1_000_000n,
});
const signature = await signClaimAuthorization({
  walletClient,
  airdropAddress,
  recipient,
  encryptedAmountHandle: encrypted.handle,
});
// Deliver { encryptedInput: encrypted, signature } to the recipient via API / email / etc.

// User: claim tokens. The SDK submits the admin-issued pair verbatim — no
// re-encryption. (GAS_FEE() is fetched and attached as msg.value automatically.)
const airdrop = createConfidentialAirdropClient({
  publicClient,
  walletClient,
  address: airdropAddress,
});
await airdrop.claim({ signature, encryptedInput: encrypted });

See Confidential Airdrop on docs.tokenops.xyz for the full API reference and claim flow diagram.

Quickstart — confidential airdrop (React hooks)

Uses @tokenops/sdk/fhe-airdrop/react. Requires wagmi, @tanstack/react-query, and @zama-fhe/react-sdk as additional peer deps.

import { useQueryClient } from "@tanstack/react-query";
import { useZamaSDK } from "@zama-fhe/react-sdk";
import {
  useCreateConfidentialAirdropAndGetAddress,
  useSignClaimAuthorization,
  useClaim,
  encryptUint64,
} from "@tokenops/sdk/fhe-airdrop/react";

// Wrap your app in wagmi's <WagmiProvider> + <QueryClientProvider> +
// @zama-fhe/react-sdk's <ZamaProvider> before rendering these components.

// --- Admin: deploy a campaign and issue a claim authorization ---

function AdminPanel({
  token,
  recipient,
  amount,
  onCampaignReady,
}: {
  token: `0x${string}`;
  recipient: `0x${string}`;
  amount: bigint;
  onCampaignReady: (
    airdrop: `0x${string}`,
    payload: {
      encryptedInput: { handle: `0x${string}`; inputProof: `0x${string}` };
      signature: `0x${string}`;
    },
  ) => void;
}) {
  const queryClient = useQueryClient();
  const zamaSDK = useZamaSDK();

  // Factory address resolves automatically from DEPLOYED_ADDRESSES on Sepolia.
  const create = useCreateConfidentialAirdropAndGetAddress();

  // walletClient is pulled from wagmi's useWalletClient() internally.
  const sign = useSignClaimAuthorization();

  const now = Math.floor(Date.now() / 1000);

  async function handleLaunch() {
    const { airdrop } = await create.mutateAsync({
      params: {
        token,
        startTimestamp: now + 60,
        endTimestamp: now + 30 * 86400,
        canExtendClaimWindow: false,
        admin: "0xYourAdminAddress" as `0x${string}`,
      },
      userSalt:
        "0x0000000000000000000000000000000000000000000000000000000000000001" as `0x${string}`,
    });

    queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-airdrop"] });

    // Encrypt the allocation for the recipient.
    const encrypted = await encryptUint64({
      encryptor: zamaSDK.relayer,
      contractAddress: airdrop,
      userAddress: recipient,
      value: amount,
    });

    // Sign the EIP-712 claim authorization.
    const signature = await sign.mutateAsync({
      airdropAddress: airdrop,
      recipient,
      encryptedAmountHandle: encrypted.handle,
    });

    // Deliver { encryptedInput: encrypted, signature } to the recipient via your API / email.
    onCampaignReady(airdrop, { encryptedInput: encrypted, signature });
  }

  return (
    <button onClick={handleLaunch} disabled={create.isPending || sign.isPending}>
      {create.isPending ? "Deploying…" : sign.isPending ? "Signing…" : "Launch airdrop"}
    </button>
  );
}

// --- Recipient: claim tokens ---

function RecipientClaim({
  airdropAddress,
  claimPayload,
}: {
  airdropAddress: `0x${string}`;
  // The admin-issued payload — encryption bound to this recipient, handle
  // committed to by the signature. The recipient submits it verbatim.
  claimPayload: {
    encryptedInput: { handle: `0x${string}`; inputProof: `0x${string}` };
    signature: `0x${string}`;
  };
}) {
  const queryClient = useQueryClient();
  const claim = useClaim({ address: airdropAddress });

  return (
    <button
      onClick={() =>
        claim.mutate(claimPayload, {
          onSuccess: () =>
            queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-airdrop"] }),
        })
      }
      disabled={claim.isPending}
    >
      {claim.isPending ? "Claiming…" : "Claim tokens"}
    </button>
  );
}

See Confidential Airdrop — React hooks on docs.tokenops.xyz for the full hook catalogue, encryptor wiring, address overrides, and query-key reference.

Quickstart — confidential disperse

Confidential bulk payouts: see Confidential Disperse on docs.tokenops.xyz for the full API reference.

import { createPublicClient, createWalletClient, http } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { RelayerNode, SepoliaConfig } from "@zama-fhe/sdk/node";
import { createConfidentialDisperseClient } from "@tokenops/sdk/fhe-disperse";

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const rpcUrl = process.env.RPC_URL!;
const publicClient = createPublicClient({ chain: sepolia, transport: http(rpcUrl) });
const walletClient = createWalletClient({ account, chain: sepolia, transport: http(rpcUrl) });

const encryptor = new RelayerNode({
  transports: { [SepoliaConfig.chainId]: { ...SepoliaConfig, network: rpcUrl } },
  getChainId: () => Promise.resolve(sepolia.id),
});

// Singleton address resolves automatically from DEPLOYED_ADDRESSES on Sepolia and mainnet.
const client = createConfidentialDisperseClient({
  publicClient,
  walletClient,
  encryptor,
});

const token = process.env.TOKEN as `0x${string}`;

// Register once to deploy your dedicated wallet pair.
const isRegistered = await client.isRegistered(account.address);
if (!isRegistered) {
  await client.register({ token });
}

const recipients = ["0xRecipient1" as `0x${string}`, "0xRecipient2" as `0x${string}`];
const amounts = [1_000_000n, 500_000n]; // raw token units; ERC-7984 uses 6 decimals

// Preflight checks all five failure modes in one call.
const report = await client.preflightDisperse({
  user: account.address,
  token,
  recipients,
  amounts,
  mode: "wallet",
});
if (!report.ready) {
  // `report.blockerErrors` is `TokenOpsSdkError[]` — branch on `error.code`
  // (e.g. `TOKENOPS_USER_NOT_REGISTERED`) for typed UI, or use `.message` for
  // a quick string render. (The older `report.blockers: string[]` is still
  // present for back-compat but will be removed in the next major.)
  throw new Error(report.blockerErrors.map((e) => e.message).join("; "));
}

// Encrypt + disperse. The SDK encrypts amounts and subtotals, attaches the ETH gas fee.
const { hash } = await client.disperse({ token, mode: "wallet", recipients, amounts });
console.log("Disperse tx:", hash);

Quickstart — confidential disperse (React hooks)

Uses @tokenops/sdk/fhe-disperse/react. Requires wagmi, @tanstack/react-query, and @zama-fhe/react-sdk as additional peer deps.

import { useQueryClient } from "@tanstack/react-query";
import { useZamaSDK } from "@zama-fhe/react-sdk";
import {
  useIsRegistered,
  useRegister,
  usePreflightDisperse,
  useDisperse,
} from "@tokenops/sdk/fhe-disperse/react";

// Wrap your app in wagmi's <WagmiProvider> + <QueryClientProvider> +
// @zama-fhe/react-sdk's <ZamaProvider> before rendering these components.

// Singleton address resolves automatically from DEPLOYED_ADDRESSES on Sepolia + mainnet.

// --- Step 1: register (one-time per user) ---

function RegisterButton({ token, user }: { token: `0x${string}`; user: `0x${string}` }) {
  const queryClient = useQueryClient();
  const { data: isRegistered } = useIsRegistered({ user });
  const register = useRegister();

  return (
    <button
      onClick={() =>
        register.mutate(
          { token },
          {
            onSuccess: () =>
              queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-disperse"] }),
          },
        )
      }
      disabled={isRegistered === true || register.isPending}
    >
      {isRegistered ? "Registered" : register.isPending ? "Registering…" : "Register"}
    </button>
  );
}

// --- Step 2: preflight then disperse ---

function DispersePanel({
  token,
  recipients,
  amounts,
  user,
}: {
  token: `0x${string}`;
  recipients: `0x${string}`[];
  amounts: bigint[];
  user: `0x${string}`;
}) {
  const queryClient = useQueryClient();

  // Preflight: enabled when all args are present.
  const { data: report } = usePreflightDisperse({
    user,
    token,
    recipients,
    amounts,
    mode: "wallet",
  });

  // Lazy encryptor: SDK calls () => zamaSDK.relayer per-encryption so
  // the live Zama React SDK context is always used (CLAUDE.md Pitfall #3).
  const zamaSDK = useZamaSDK();
  const disperse = useDisperse({
    encryptor: () => zamaSDK.relayer,
  });

  return (
    <div>
      {report && !report.ready && (
        <ul>
          {report.blockerErrors.map((err) => (
            // `err.code` is the stable machine-readable key (e.g.
            // `TOKENOPS_USER_NOT_REGISTERED`); `err.message` is the prose.
            <li key={err.code + err.message}>{err.message}</li>
          ))}
        </ul>
      )}
      <button
        onClick={() =>
          disperse.mutate(
            { token, mode: "wallet", recipients, amounts },
            {
              onSuccess: () =>
                queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-disperse"] }),
            },
          )
        }
        disabled={!report?.ready || disperse.isPending}
      >
        {disperse.isPending ? "Dispersing…" : "Disperse tokens"}
      </button>
    </div>
  );
}

See Confidential Disperse — React hooks on docs.tokenops.xyz for the full hook catalogue, encryptor wiring, address overrides, usePreflightDisperse gating pattern, useGetEncryptedFeeReserve encrypted-view flow, and query-key reference.

Peer dependencies

| Package | Range | Required for | | ----------------------- | -------- | ------------------------------------------------------------------------------ | | viem | ^2.47 | all subpaths (hard required) | | @zama-fhe/sdk | ^3.0.0 | encryption + decryption + FHE write flows on every FHE subpath (optional peer) | | @zama-fhe/react-sdk | ^3.0.0 | /react hook subpaths that submit encrypted inputs (optional peer) | | react | >=18 | /react hook subpaths (optional peer) | | wagmi | ^2 | /react hook subpaths (optional peer) | | @tanstack/react-query | ^5 | /react hook subpaths (optional peer) |

All peers except viem are marked optional via peerDependenciesMeta so read-only / ABI-only consumers can install the package without pulling them in. Install @zama-fhe/sdk explicitly the first time you encrypt, decrypt, or submit an FHE write; install the React peers if you use any /react hook subpath.

Design

  • Single package, subpath exports. No companion @tokenops/react-sdk — hooks live at @tokenops/sdk/<product>/react.
  • Deployed factories only. No deployFactory helpers. Addresses live in src/core/addresses.ts.
  • Zama SDK v3 native. FHE modules import from @zama-fhe/sdk/viem or /node directly. The SDK does not re-wrap the Zama surface.
  • Encapsulated FHE complexity. Where a contract demands encrypted inputs, KMS proofs, scaled integers, and ACL grants, the SDK accepts natural plaintext inputs and handles the rest.

Docs

Development

pnpm install
pnpm typecheck   # tsc --noEmit
pnpm build       # tsup → dist/ (esm + cjs + dts per subpath)
pnpm test        # vitest run
pnpm lint        # eslint + prettier --check

License

BSD-3-Clause-Clear. See LICENSE.