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

# Transfers and disclosure

> Send FHERC20 tokens from a wallet or a contract, read what moved, and make an encrypted amount public

This page covers the FHERC20 transfer functions and the disclosure flow that turns an encrypted amount into a public, verified one. It assumes you know what an [FHERC20 token](/cofhe/fhe-library/confidential-contracts/fherc20/overview) stores.

## Which transfer function do you call?

Each transfer comes in two forms. Pick by who holds the amount:

| Caller | Amount parameter | ethers signature |
| - | - | - |
| A wallet | `externalEuint64 encryptedAmount, bytes inputProof` | `confidentialTransfer(address,bytes32,bytes)` |
| A contract | `sharedEuint64 sharedAmount` | `confidentialTransfer(address,bytes32)` |
| A wallet, as an [operator](/cofhe/fhe-library/confidential-contracts/fherc20/operators) | `externalEuint64 encryptedAmount, bytes inputProof` | `confidentialTransferFrom(address,address,bytes32,bytes)` |
| A contract, as an operator | `sharedEuint64 sharedAmount` | `confidentialTransferFrom(address,address,bytes32)` |

Both encrypted types are `bytes32` in the ABI, so ethers cannot pick the overload by argument types. Index the contract with the full signature, as in the table.

The `AndCall` versions of both functions also notify the recipient. See [transfer callbacks](/cofhe/fhe-library/confidential-contracts/fherc20/transfer-callbacks).

## How do you send from a wallet?

Encrypt the amount with the SDK. Name the token as the consuming contract, and encrypt with the client of the account that sends the transaction:

```typescript theme={null}
import { Encryptable } from '@cofhe/sdk';

const [amount, inputProof] = await client
  .encryptInputs([Encryptable.uint64(25_000_000n)])
  .setConsumingContract(await token.getAddress())
  .execute();

await token['confidentialTransfer(address,bytes32,bytes)'](recipient, amount, inputProof);
```

The proof is bound to both the consuming contract and the sender. If either differs, the transaction reverts with `InvalidSigner`. That includes encrypting as one account and submitting from another, so an operator encrypts with its own client. See [encrypting inputs](/cofhe/client-sdk/guides/encrypting-inputs) for client setup.

## How do you send from a contract?

A contract that already holds an encrypted amount shares it with the token, then reads back what moved:

```solidity theme={null}
sharedEuint64 moved = token.confidentialTransfer(recipient, FHE.shareEuint64(amount, address(token)));
euint64 sent = FHE.receiveEuint64FromCall(moved, address(token));
```

The `sharedEuint64` overloads accept only a share that the direct caller created for the token. An account that passes a bare handle gets `NotShared`, so a wallet cannot use them. [Call a token from your contract](/cofhe/fhe-library/confidential-contracts/calling-from-contracts) walks through a full example.

## What happens when the balance is too low?

The transfer succeeds and moves zero. The token cannot revert on an encrypted comparison without revealing its result. Instead it moves the requested amount if the balance covers it, and zero otherwise.

Every transfer function returns that amount as a `sharedEuint64` directed at the caller. A contract caller reads it with `FHE.receiveEuint64FromCall` and should act on it, not on the amount it requested. A wallet can find the same handle in the `ConfidentialTransfer` event.

The token grants persistent access to the moved amount to the sender, the recipient, and the token itself. Either party can decrypt it with `decryptForView`.

The one case that does revert is a sender that has never held the token. Its balance handle does not exist, so the transfer fails with `FHERC20ZeroBalance`.

## What does a transfer emit?

| Event | Arguments |
| - | - |
| <code style={{ whiteSpace: "nowrap" }}>Transfer</code> | `from`, `to`, and a fixed value that reads as `7984.0001`. Present for ERC-20 indexers; the value is not the amount |
| <code style={{ whiteSpace: "nowrap" }}>ConfidentialTransfer</code> | `from`, `to`, and the handle of the amount that moved, all indexed |

Mints emit both events with `from` set to the zero address, and burns with `to` set to the zero address.

All transfer functions are `nonReentrant`. A callback that tries to start a second transfer on the same token in the same call reverts.

## Errors

| Error | When |
| - | - |
| <code style={{ whiteSpace: "nowrap" }}>FHERC20InvalidReceiver</code> | `to` is the zero address, or a callback recipient reverted without a reason. See [transfer callbacks](/cofhe/fhe-library/confidential-contracts/fherc20/transfer-callbacks) |
| <code style={{ whiteSpace: "nowrap" }}>FHERC20InvalidSender</code> | `from` is the zero address |
| <code style={{ whiteSpace: "nowrap" }}>FHERC20ZeroBalance</code> | `from` has never held the token |
| <code style={{ whiteSpace: "nowrap" }}>FHERC20UnauthorizedSpender</code> | The caller of a `From` function is not an operator for `from` |
| <code style={{ whiteSpace: "nowrap" }}>FHERC20IncompatibleFunction</code> | The caller used `transfer`, `transferFrom`, `approve`, or `allowance` |
| <code style={{ whiteSpace: "nowrap" }}>InvalidSigner</code> | The input proof was signed for a different consuming contract or sender, or a decryption result does not match its handle. See [common errors](/cofhe/fhe-library/core-concepts/common-errors) |
| <code style={{ whiteSpace: "nowrap" }}>NotShared</code> | A `sharedEuint64` argument was not shared with the token by the caller |

## How do you make an amount public?

Disclosure publishes a decrypted amount onchain together with a proof that it is correct. Use it when an amount has to become public, such as a payment that a third party must verify.

<Steps>
  <Step title="Request disclosure">
    Call `requestDiscloseEncryptedAmount(handle)` from an account that has access to the handle, such as the holder of a balance. The token makes the handle publicly decryptable and emits `AmountDiscloseRequested`. A caller without access gets `FHERC20UnauthorizedUseOfEncryptedAmount`.
  </Step>

  <Step title="Decrypt it">
    Anyone can now decrypt the handle with `decryptForTx`, without an ACP:

    ```typescript theme={null}
    const { decryptedValue, signature } = await client.decryptForTx(handle).withoutACP().execute();
    ```
  </Step>

  <Step title="Publish the result">
    Call `discloseEncryptedAmount(handle, decryptedValue, signature)`. The token verifies the signature and emits `AmountDisclosed(handle, amount)`. A value that does not match the handle reverts with `InvalidSigner`.
  </Step>
</Steps>

`discloseEncryptedAmount` checks only the proof. It does not need a prior request, so you can publish any handle you hold a valid decryption proof for.

<Note>
  Disclosing a balance handle reveals that balance at that moment and cannot be undone. Later balance changes create new handles, which stay private.
</Note>

## Related pages

* [Operators](/cofhe/fhe-library/confidential-contracts/fherc20/operators)
* [Decrypt to transaction](/cofhe/client-sdk/guides/decrypt-to-tx)
* [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.