For the complete documentation index, see llms.txt. This page is also available as Markdown.

Transfer privately

How to send confidential tokens so the amount stays hidden on-chain.

Confidential transfers encrypt the amount before it reaches the chain -- no one can see how much was sent. The SDK handles FHE encryption internally via token.confidentialTransfer(). In React, use the useConfidentialTransfer and useConfidentialTransferFrom hooks.

Steps

1. Create a token instance

Start from a configured SDK instance (see Configuration) and create a token pointing at your encrypted ERC-20 contract:

const token = sdk.createToken("0xEncryptedERC20Address");

2. Send a confidential transfer

Pass the recipient address and the plaintext amount. The SDK encrypts the amount using FHE before submitting the transaction.

By default, the SDK validates the confidential balance before submitting. If stored permits exist, it decrypts silently. If the balance is insufficient, it throws InsufficientConfidentialBalanceError before any transaction is sent. Pass skipBalanceCheck: true to bypass (e.g. for smart wallets that cannot produce EIP-712 signatures).

const { txHash } = await token.confidentialTransfer("0xRecipientAddress", 500n);
console.log("Transfer tx:", txHash);

The user sees a single wallet prompt. The encrypted amount is included in the transaction calldata -- it is unreadable to anyone without the FHE decryption key.

3. Send as an operator (transferFrom)

If an owner has approved you as an operator (via token.setOperator()), you can transfer on their behalf using confidentialTransferFrom:

const { txHash } = await token.confidentialTransferFrom(
  "0xOwnerAddress",
  "0xRecipientAddress",
  500n,
);

The operator must have been approved beforehand. Check approval status with token.isOperator("0xHolder", "0xOperator") or the useConfidentialIsOperator hook.

4. Handle the transaction result

Both the core SDK and React hooks resolve to a TransactionResult with the transaction txHash and its mined receipt. Use them to confirm the transaction or update your UI:

5. (React) Use the transfer hook in a component

Here is a complete component that wires up the transfer with loading and error states:

The matchZamaError helper maps SDK error codes to user-friendly messages. See the Error Handling guide for the full list of error types.

Transfer into a contract (receiver hook)

Sometimes the recipient is a contract that needs to react to the transfer — for example a confidential vault that credits a deposit, or a payment splitter that fans the amount out. confidentialTransferAndCall moves the encrypted amount and invokes the recipient's ERC-7984 receiver hook in a single transaction, so the deposit can never land without the contract being told about it.

Use token.confidentialTransferAndCall() (or the useConfidentialTransferAndCall hook). The third argument, data, is an opaque payload forwarded verbatim to the receiver's hook. The SDK never encodes, validates, or inspects it — its layout is defined by the receiving contract's ABI, not the token's. Encode it with viem's encodeAbiParameters to match what the contract expects.

This example deposits 500 confidential tokens into a vault, passing the depositor's account so the vault credits the right balance:

Like confidentialTransfer, this validates the confidential balance before submitting and throws InsufficientConfidentialBalanceError if it is too low — catching an over-deposit before you spend gas on a transfer the vault would revert. Leave the check on for ordinary wallets; only set skipBalanceCheck: true for smart wallets that cannot produce the EIP-712 signature the decrypt-and-compare requires. (This is the same escape hatch introduced for confidentialTransfer above, not a per-deposit toggle.)

To deposit on behalf of an owner who has approved you as an operator, use confidentialTransferFromAndCall(owner, vault, amount, data) — the operator-initiated counterpart, mirroring confidentialTransferFrom.

Next steps

Last updated