Skip to main content
When your contract moves confidential tokens, it passes encrypted amounts to the token and gets encrypted amounts back. Both directions use sharedEuint64, a handle tagged with who shared it and with whom. This page shows the pattern on a vault, and it applies to FHERC20 and ERC20Confidential alike.

Why amounts cross as shared values

FHE operations check the permission of the contract performing them, not of whoever called it. Suppose a token accepted a bare euint64 from any caller. That caller could pass a handle only the token may use, such as someone’s balance, and get back a value derived from it. A share records the sharer and the intended receiver, and lasts one transaction, so the receiver can check who handed the value over. Passing encrypted values between contracts covers the mechanism. For a token call, that means two steps around every call:
  1. Share the amount with the token: FHE.shareEuint64(amount, address(token)).
  2. Receive the result from the token: FHE.receiveEuint64FromCall(result, address(token)).
Name the token in both. In receiveEuint64FromCall, the address must be the contract you called in that same expression. Naming any other trusted address checks who created the share, not who handed it to you.

A vault that holds deposits

This vault takes deposits with confidentialTransferFrom and pays out with confidentialTransfer. Each deposit is kept as an encrypted balance the depositor can decrypt:
ConfidentialVault.sol
Three details carry the design:
  • Credit what moved, not what was asked. A transfer that exceeds the sender’s balance moves zero instead of reverting. The vault credits received, the amount the token returns, so a failed deposit credits nothing.
  • Clamp before you send. withdraw uses FHE.select to replace an over-deposit request with zero. The contract cannot revert on an encrypted comparison without revealing it.
  • Persist with allowThis. A received handle carries access for this transaction only. Values you store must get FHE.allowThis, and FHE.allow for the account that should decrypt them. See access control.
IERC7984 is the interface both token families implement, so the vault works with either. The shared overloads of confidentialTransfer and confidentialTransferFrom are selected by the sharedEuint64 argument type.

Call the vault from your app

confidentialTransferFrom runs with the vault as msg.sender, so the depositor must first make the vault an operator. Without it, the deposit reverts with FHERC20UnauthorizedSpender on FHERC20, or ERC20ConfidentialUnauthorizedSpender on ERC20Confidential. Encrypt the amount with the vault as the consuming contract, because the vault calls FHE.asEuint64, not the token:
An input encrypted for the token instead of the vault fails with InvalidSigner. The same happens when one account encrypts and another sends the transaction: encrypt with the client of the account that calls the vault. See encrypting inputs.

What goes wrong

These errors come from the TaskManager. See common errors.

Receive tokens with a callback

To react when tokens arrive, rather than pulling them, implement IERC7984Receiver. The token calls it during confidentialTransferAndCall with the amount as a sharedEuint64, which you receive with FHE.receiveEuint64Param. See transfer callbacks.