> ## Documentation Index
> Fetch the complete documentation index at: https://cofhe-docs.fhenix.zone/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices

> Security, gas optimization, and implementation guidelines for FHERC20 tokens

## Overview

Building with FHERC20 requires understanding both traditional smart contract best practices and the unique considerations that come with fully homomorphic encryption. This guide provides actionable recommendations for secure, efficient, and privacy-preserving implementations.

***

## Security Best Practices

<AccordionGroup>
  <Accordion title="Operator Permissions - Full Access Risk" icon="triangle-exclamation" defaultOpen>
    **Risk:** Operators have unlimited access to a user's balance until expiration.

    **Recommendation:**

    ```solidity theme={null}
    // ✅ Use short expiration times for specific operations
    function authorizeSwap(address dex) external {
        // 10 minutes - just enough for the transaction
        token.setOperator(dex, uint48(block.timestamp + 10 minutes));
    }

    // ❌ Avoid indefinite permissions unless absolutely necessary
    function dangerousApproval(address spender) external {
        // This grants permanent access - very risky!
        token.setOperator(spender, type(uint48).max);
    }
    ```

    **Best practices:**

    * Grant operators only for the minimum necessary time
    * Use specific timeframes: 5-10 minutes for single transactions, 1 day for recurring operations
    * Document operator requirements clearly in your UI
    * Provide easy revocation by calling `setOperator(address, block.timestamp)`
  </Accordion>

  <Accordion title="Zero-Replacement Handling" icon="shield-check" defaultOpen>
    **Risk:** Insufficient balance transfers zero tokens instead of reverting, which can lead to unexpected behavior.

    **Recommendation:**

    ```solidity theme={null}
    // ✅ Check balance before operations that depend on success
    function safeSwap(address tokenIn, address tokenOut, uint64 amountIn) external {
        euint64 balance = IFHERC20(tokenIn).confidentialBalanceOf(msg.sender);

        // Request decryption to verify balance (in real implementation)
        // For demo, assume we have a way to verify

        // Only proceed if we can verify sufficient balance
        require(canVerifyBalance(balance, amountIn), "Insufficient balance");

        euint64 transferred = IFHERC20(tokenIn).confidentialTransfer(
            address(this),
            amountIn
        );

        // Perform swap logic...
    }

    // ❌ Don't assume transfers always succeed
    function dangerousSwap(address tokenIn, uint64 amountIn) external {
        // This might transfer zero tokens!
        IFHERC20(tokenIn).confidentialTransfer(address(this), amountIn);

        // Continuing as if transfer succeeded is dangerous
        _executeSwap(amountIn); // ❌ Wrong amount!
    }
    ```

    **Best practices:**

    * Always work with the returned `euint64 transferred` value
    * Implement balance checks when transfer success is critical
    * Use the transferred amount in subsequent operations, not the requested amount
    * Consider using transfer callbacks for atomic operations
  </Accordion>

  <Accordion title="Access Control Management" icon="lock" defaultOpen>
    **Risk:** Improper FHE access control can prevent users from accessing their own balances or expose data to unauthorized parties.

    **Recommendation:**

    ```solidity theme={null}
    // ✅ Grant appropriate access after balance updates
    function _updateBalanceWithAccess(address account, euint64 newBalance) internal {
        _confidentialBalances[account] = newBalance;

        // Contract needs access for operations
        FHE.allowThis(newBalance);

        // User needs access to query their balance
        FHE.allow(newBalance, account);
    }

    // ✅ Grant access to transferred amounts
    function confidentialTransfer(address to, euint64 value)
        external returns (euint64 transferred)
    {
        transferred = _transfer(msg.sender, to, value);

        // Both parties should be able to see what was transferred
        FHE.allow(transferred, msg.sender);
        FHE.allow(transferred, to);

        return transferred;
    }

    // ❌ Don't forget to grant access
    function badTransfer(address to, euint64 value) external {
        euint64 transferred = _transfer(msg.sender, to, value);
        // Forgot FHE.allow calls - users can't see transferred amount!
        return transferred;
    }
    ```

    **Best practices:**

    * Always call `FHE.allowThis()` for values the contract needs to use
    * Always call `FHE.allow(value, user)` for values users need to access
    * Grant access immediately after creating or modifying encrypted values
    * Review access control on all encrypted state changes
  </Accordion>

  <Accordion title="Reentrancy in Transfer Callbacks" icon="rotate" defaultOpen>
    **Risk:** The `onConfidentialTransferReceived` callback is executed during the transfer, creating reentrancy opportunities.

    **Recommendation:**

    ```solidity theme={null}
    // ✅ Use reentrancy guards
    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

    contract SafeReceiver is IERC7984Receiver, ReentrancyGuard {
        function onConfidentialTransferReceived(
            address operator,
            address from,
            euint64 amount,
            bytes calldata data
        ) external override nonReentrant returns (ebool) {
            // Protected against reentrancy attacks
            _processDeposit(from, amount);
            return FHE.asEbool(true);
        }
    }

    // ✅ Follow checks-effects-interactions pattern
    contract SecureStaking is IERC7984Receiver {
        mapping(address => euint64) public stakedBalances;

        function onConfidentialTransferReceived(
            address operator,
            address from,
            euint64 amount,
            bytes calldata data
        ) external override returns (ebool) {
            // Checks
            require(msg.sender == address(stakingToken), "Wrong token");

            // Effects (update state first)
            stakedBalances[from] = stakedBalances[from] + amount;
            FHE.allowThis(stakedBalances[from]);
            FHE.allow(stakedBalances[from], from);

            // Interactions (external calls last)
            if (data.length > 0) {
                _notifyReferral(from, data);
            }

            return FHE.asEbool(true);
        }
    }
    ```

    **Best practices:**

    * Use OpenZeppelin's ReentrancyGuard for all receiver implementations
    * Follow checks-effects-interactions pattern
    * Minimize external calls in callbacks
    * Keep callback logic simple and gas-efficient
  </Accordion>

  <Accordion title="Input Validation" icon="check" defaultOpen>
    **Risk:** Unvalidated inputs can lead to unexpected behavior or security vulnerabilities.

    **Recommendation:**

    ```solidity theme={null}
    // ✅ Validate all inputs
    function onConfidentialTransferReceived(
        address operator,
        address from,
        euint64 amount,
        bytes calldata data
    ) external override returns (ebool) {
        // Validate token source
        if (msg.sender != address(trustedToken)) {
            return FHE.asEbool(false);
        }

        // Validate sender (if needed)
        if (!isAllowedSender(from)) {
            return FHE.asEbool(false);
        }

        // Validate data length and content
        if (data.length > 0) {
            if (data.length != 64) {
                return FHE.asEbool(false); // Expected specific data format
            }

            // Safely decode with bounds checking
            (uint256 lockDuration, address referrer) = abi.decode(
                data,
                (uint256, address)
            );

            if (lockDuration > 365 days) {
                return FHE.asEbool(false); // Reject unreasonable durations
            }

            if (referrer == address(0)) {
                return FHE.asEbool(false); // Reject zero address
            }
        }

        return FHE.asEbool(true);
    }

    // ❌ Don't trust inputs blindly
    function dangerousReceiver(
        address operator,
        address from,
        euint64 amount,
        bytes calldata data
    ) external override returns (ebool) {
        // Accepts anything - very dangerous!
        _processData(data); // Could contain malicious data
        return FHE.asEbool(true);
    }
    ```

    **Best practices:**

    * Validate token source (`msg.sender`)
    * Validate transfer initiator (`operator`) and source (`from`) when needed
    * Check data length before decoding
    * Validate decoded parameters for reasonable bounds
    * Return `FHE.asEbool(false)` to reject invalid transfers
  </Accordion>
</AccordionGroup>

***

## Operators Best Practices

### When to Use Operators

<AccordionGroup>
  <Accordion title="Standard User Interactions" defaultOpen>
    **Use operators when:**

    * User directly approves via wallet transaction
    * Simple on-chain permission grants

    ```solidity theme={null}
    // User directly approves DEX
    await token.connect(user).setOperator(
        dexAddress,
        Math.floor(Date.now() / 1000) + 3600
    );

    // DEX can now swap on behalf of user
    await dex.swap(tokenIn, tokenOut, amountIn);
    ```

    **Advantages:**

    * Simple, direct approach
    * No signature complexity
    * Immediate effect
  </Accordion>

  <Accordion title="Long-Lived Permissions" defaultOpen>
    **Use operators when:**

    * Granting recurring permissions

    ```solidity theme={null}
    // Grant 30-day subscription access
    await token.setOperator(
        subscriptionContract,
        uint48(block.timestamp + 30 days)
    );
    ```
  </Accordion>
</AccordionGroup>

### Comparison Table

| Factor                | Operator (setOperator) |
| --------------------- | ---------------------- |
| **Gas Cost**          | User pays gas          |
| **Transaction Count** | 2 (approve + action)   |
| **Complexity**        | Simple                 |
| **User Experience**   | Standard wallet flow   |
| **Use Case**          | General permissions    |
| **Implementation**    | Direct on-chain        |

***

## Common Pitfalls and Solutions

<AccordionGroup>
  <Accordion title="Pitfall: Forgetting to Grant FHE Access" icon="key">
    **Problem:**

    ```solidity theme={null}
    function badMint(address to, uint64 amount) internal {
        euint64 value = FHE.asEuint64(amount);
        _confidentialBalances[to] = _confidentialBalances[to] + value;

        // ❌ Forgot to grant access!
        // User and contract can't read the balance
    }
    ```

    **Solution:**

    ```solidity theme={null}
    function goodMint(address to, uint64 amount) internal {
        euint64 value = FHE.asEuint64(amount);
        _confidentialBalances[to] = _confidentialBalances[to] + value;

        // ✅ Grant necessary access
        FHE.allowThis(_confidentialBalances[to]); // Contract can use it
        FHE.allow(_confidentialBalances[to], to);  // User can query it
    }
    ```
  </Accordion>

  <Accordion title="Pitfall: Not Handling Zero Transfers" icon="circle-0">
    **Problem:**

    ```solidity theme={null}
    function badSwap(address tokenIn, uint64 amountIn) external {
        // Transfer might return zero!
        token.confidentialTransfer(address(this), amountIn);

        // ❌ Assumes transfer succeeded with full amount
        _executeSwap(amountIn); // Wrong if transfer was zero!
    }
    ```

    **Solution:**

    ```solidity theme={null}
    function goodSwap(address tokenIn, uint64 amountIn) external {
        // Use the actual transferred amount
        euint64 transferred = token.confidentialTransfer(
            address(this),
            amountIn
        );

        // ✅ Work with what was actually transferred
        _executeSwap(transferred);

        // Or check balance first if exact amount is critical
    }
    ```
  </Accordion>

  <Accordion title="Pitfall: Incorrect Claim Management" icon="inbox">
    **Problem:**

    ```solidity theme={null}
    function badUnshield(uint64 amount) external {
        wrapper.unshield(msg.sender, msg.sender, amount);

        // ❌ Trying to claim without decryption proof!
        wrapper.claimUnshielded(someClaim, amount, "");
    }
    ```

    **Solution:**

    ```typescript theme={null}
    // ✅ Correct flow
    // 1. Request unshield
    const tx = await wrapper.unshield(userAddress, userAddress, amount);
    await tx.wait();

    // 2. Get the claim's ctHash
    const claims = await wrapper.getUserClaims(userAddress);
    const latestClaim = claims[claims.length - 1];

    // 3. Decrypt off-chain to get plaintext + proof
    const decryptResult = await client
      .decryptForTx(latestClaim.ctHash)
      .withoutPermit()
      .execute();

    // 4. Claim with proof
    await wrapper.claimUnshielded(
      decryptResult.ctHash,
      decryptResult.decryptedValue,
      decryptResult.signature
    );
    ```
  </Accordion>

  <Accordion title="Pitfall: Operator Expiration Timing" icon="clock">
    **Problem:**

    ```javascript theme={null}
    // ❌ Expiration in milliseconds (wrong!)
    await token.setOperator(
        spender,
        Date.now() + 3600000 // JavaScript timestamp
    );
    ```

    **Solution:**

    ```javascript theme={null}
    // ✅ Expiration in seconds (Unix timestamp)
    const expirationTime = Math.floor(Date.now() / 1000) + 3600;
    await token.setOperator(
        spender,
        expirationTime
    );

    // ✅ Or use block.timestamp from contract
    const currentBlock = await ethers.provider.getBlock('latest');
    const until = currentBlock.timestamp + 3600;
    await token.setOperator(spender, until);
    ```
  </Accordion>

  <Accordion title="Pitfall: Not Validating Callback Returns" icon="shield-check">
    **Problem:**

    ```solidity theme={null}
    function badReceiver(
        address operator,
        address from,
        euint64 amount,
        bytes calldata data
    ) external override returns (ebool) {
        _processTokens(from, amount);

        // ❌ Always returns true, even if processing failed!
        return FHE.asEbool(true);
    }
    ```

    **Solution:**

    ```solidity theme={null}
    function goodReceiver(
        address operator,
        address from,
        euint64 amount,
        bytes calldata data
    ) external override returns (ebool) {
        // ✅ Validate before accepting
        if (msg.sender != address(trustedToken)) {
            return FHE.asEbool(false);
        }

        if (!isValidSender(from)) {
            return FHE.asEbool(false);
        }

        // Process with error handling
        try this._processTokens(from, amount, data) {
            return FHE.asEbool(true);
        } catch {
            return FHE.asEbool(false);
        }
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Related Topics

* Review [Core Features](/fhe-library/confidential-contracts/fherc20/core-features) for fundamental concepts
* Learn about [Operators](/fhe-library/confidential-contracts/fherc20/operators) for permission management
* Explore [Transfer Callbacks](/fhe-library/confidential-contracts/fherc20/transfer-callbacks) for safe transfers
