> ## 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.

# Transfer callbacks

> Send FHERC20 tokens to a contract and let it accept or refund them in the same transaction

A plain transfer to a contract gives the contract tokens but no chance to react. The `AndCall` functions transfer, then call the recipient, which can record the deposit or send the tokens back. Use them when a contract must act on what it received, such as crediting a deposit.

## Which functions make the callback?

| ethers signature | Caller |
| - | - |
| `confidentialTransferAndCall(address,bytes32,bytes,bytes)` | A wallet, with `to`, the encrypted amount, `inputProof`, and `data` |
| `confidentialTransferAndCall(address,bytes32,bytes)` | A contract, with `to`, a `sharedEuint64`, and `data` |
| `confidentialTransferFromAndCall(address,address,bytes32,bytes,bytes)` | A wallet [operator](/cofhe/fhe-library/confidential-contracts/fherc20/operators), with `from`, `to`, the encrypted amount, `inputProof`, and `data` |
| `confidentialTransferFromAndCall(address,address,bytes32,bytes)` | A contract operator, with `from`, `to`, a `sharedEuint64`, and `data` |

In the wallet forms, `inputProof` comes right after the encrypted amount and before `data`. Both are `bytes`, so swapping them compiles and fails at runtime. `data` is passed to the recipient unchanged.

```typescript theme={null}
const [amount, inputProof] = await client
  .encryptInputs([Encryptable.uint64(30_000_000n)])
  .setConsumingContract(await token.getAddress())
  .execute();

await token['confidentialTransferAndCall(address,bytes32,bytes,bytes)'](box, amount, inputProof, '0x');
```

## How does the callback work?

The token moves the amount first. If `to` has code, it then calls `onConfidentialTransferReceived` on it:

1. The token shares the moved amount with `to` as a `sharedEuint64` and calls the receiver with the operator (the caller of the token function), `from`, that amount, and `data`.
2. The receiver returns a `sharedEbool`: true to keep the tokens, false to refuse them.
3. The token moves back whatever the receiver refused: the full amount on false, zero on true.

If `to` has no code, there is no callback and the tokens stay with `to`.

The function returns `sent - refund` as a `sharedEuint64`. That is zero when the receiver refused, and also zero when the sender's balance was too low.

## How do you implement a receiver?

Implement `IERC7984Receiver`. This receiver accepts deposits up to a cap and refuses larger ones:

```solidity CappedDepositBox.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import { FHE, ebool, euint64, sharedEbool, sharedEuint64 } from "@fhenixprotocol/cofhe-contracts/FHE.sol";
import { IERC7984Receiver } from "fhenix-confidential-contracts/contracts/interfaces/IERC7984Receiver.sol";

contract CappedDepositBox is IERC7984Receiver {
    address public immutable token;
    euint64 private _cap;
    mapping(address => euint64) private _deposits;

    error UnexpectedToken(address caller);

    constructor(address token_, uint64 cap) {
        token = token_;
        _cap = FHE.asEuint64(cap);
        FHE.allowThis(_cap);
    }

    function onConfidentialTransferReceived(
        address,
        address from,
        sharedEuint64 sharedAmount,
        bytes calldata
    ) external returns (sharedEbool) {
        // Any contract can share a value it made up and call this function.
        if (msg.sender != token) revert UnexpectedToken(msg.sender);
        euint64 amount = FHE.receiveEuint64Param(sharedAmount);

        ebool accepted = FHE.lte(amount, _cap);
        euint64 credited = FHE.add(_deposits[from], FHE.select(accepted, amount, FHE.asEuint64(0)));
        FHE.allowThis(credited);
        FHE.allow(credited, from);
        _deposits[from] = credited;

        return FHE.shareEbool(accepted, msg.sender);
    }

    function depositOf(address account) external view returns (euint64) {
        return _deposits[account];
    }
}
```

Each step in the callback has a reason:

* **Check `msg.sender`.** `FHE.receiveEuint64Param` accepts any share whose sharer is the caller. A contract you do not know can share an amount it invented and call you directly, so only trust the token you expect.
* **Unwrap with `FHE.receiveEuint64Param`.** It reverts with `NotShared` or `UnexpectedSharer` unless the caller shared the amount with this contract in this transaction.
* **Call `FHE.allowThis` on what you store.** The received amount carries access for this transaction only. Values you derive from it and keep need a persistent grant.
* **Return `FHE.shareEbool(result, msg.sender)`.** The token accepts the result only as a share from the address it called.

Decide in encrypted form. A receiver that branches on the amount in plaintext would have to decrypt it, which it cannot do inside the callback.

<Warning>
  Do not move the tokens back yourself and also return false. The token refunds on false, so a receiver that does both pays twice. If the receiver no longer holds the amount when it returns false, the refund moves zero and the sender gets nothing back.
</Warning>

## When does the transfer revert?

| Receiver behavior | Result |
| - | - |
| Returns a shared true or false | The transfer completes, with a refund on false |
| Has code but no `onConfidentialTransferReceived` | Reverts with `FHERC20InvalidReceiver(to)` |
| Reverts with no reason | Reverts with `FHERC20InvalidReceiver(to)` |
| Reverts with a reason | Reverts with the receiver's error |
| Returns an `ebool` it did not share with the token | Reverts with `NotShared` |

## What does a callback transfer emit?

The refund step runs on every `AndCall`, even when it refunds zero or `to` has no code. Each call therefore emits `Transfer` and `ConfidentialTransfer` twice: once from `from` to `to`, then once from `to` back to `from`.

The two steps also cancel out in the [indicators](/cofhe/fhe-library/confidential-contracts/fherc20/overview#what-do-the-erc-20-functions-return). A callback transfer leaves both indicators where they were, except that a first-time recipient moves from `0` to `7984.0000`.

## Related pages

* [Call a token from your contract](/cofhe/fhe-library/confidential-contracts/calling-from-contracts)
* [Transfers and disclosure](/cofhe/fhe-library/confidential-contracts/fherc20/core-features)
* [Best practices](/cofhe/fhe-library/confidential-contracts/fherc20/best-practices)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.