FHERC20 supports safe transfers with callbacks. When you use the ...AndCall functions, the recipient contract is notified of the incoming transfer and can accept or reject it. This enables atomic operations where token transfers and contract logic execute together.
Atomic Operations
Transfer tokens and execute contract logic in a single transaction, ensuring both succeed or both fail.
Recipient Validation
Recipients must explicitly accept transfers by implementing IFHERC20Receiver, preventing tokens from being locked in incompatible contracts.
Custom Logic
Recipients can execute arbitrary logic upon receiving tokens, enabling complex DeFi interactions.
Data Passing
Pass arbitrary data along with transfers to provide context or instructions to the recipient.
The function must return an ebool (encrypted boolean):
FHE.asEbool(true): Accept the transfer
FHE.asEbool(false): Reject the transfer (tokens will be returned to sender)
If onConfidentialTransferReceived returns encrypted false, the entire transfer is reversed. The tokens return to the sender as if the transfer never happened.
// 1. Recipient is an EOA (not a contract)await token.confidentialTransferAndCall(eoaAddress, amount, data);// 2. Recipient is a contract implementing IFHERC20Receiver that returns truefunction onConfidentialTransferReceived(...) external override returns (ebool) { return FHE.asEbool(true);}
The callback happens after the balance transfer but before the transaction completes. Implement reentrancy guards if your receiver makes external calls.
Copy
Ask AI
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract SafeReceiver is IFHERC20Receiver, ReentrancyGuard { function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override nonReentrant returns (ebool) { // Protected against reentrancy return FHE.asEbool(true); }}
Gas Limits
Callback execution is subject to gas limits. Keep logic simple:
Copy
Ask AI
function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data) external override returns (ebool) { // ✅ Simple logic is fine stakedBalances[from] += amount; // ❌ Avoid complex operations // for (uint i = 0; i < 1000; i++) { ... } return FHE.asEbool(true);}
Untrusted Senders
Your receiver will be called by anyone who transfers tokens to you. Validate the sender:
Copy
Ask AI
function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data) external override returns (ebool) { // Validate token if (msg.sender != address(trustedToken)) { return FHE.asEbool(false); } // Validate sender if needed if (!isAllowedSender(from)) { return FHE.asEbool(false); } return FHE.asEbool(true);}
Data Validation
Always validate the data parameter before using it:
Copy
Ask AI
function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data) external override returns (ebool) { // Validate data length if (data.length > 0) { // Safely decode with try-catch try this.decodeData(data) returns (uint256 value) { // Use decoded value _processValue(value); } catch { // Invalid data, reject transfer return FHE.asEbool(false); } } return FHE.asEbool(true);}function decodeData(bytes calldata data) external pure returns (uint256) { return abi.decode(data, (uint256));}