Skip to main content

ERC-20 Transfer Facet

@perfect-abstractions/compose/token/ERC20/Transfer/ERC20TransferFacet.sol

Move tokens between accounts, directly or by spending an allowance

Key Features
  • transfer moves the caller's own tokens. transferFrom moves someone else's using an allowance.
  • Both write balances in ERC20Storage at erc8042:erc20.
  • An allowance of type(uint256).max is treated as unlimited and is never decremented.

Storage

State Variables

PropertyTypeDescriptionSTORAGE_POSITIONbytes32ERC-20 storage position within the diamond (Value: keccak256("erc20"))

ERC20Storage

Definition
/** @custom:storage-location erc8042:erc20 */
struct ERC20Storage {
mapping(address owner => uint256 balance) balanceOf;
uint256 totalSupply;
mapping(address owner => mapping(address spender => uint256 allowance)) allowance;
}

Functions

transfer

Moves _value tokens from the caller to _to.

Transferring 0 is allowed and still emits Transfer. Sending to address(0) is rejected, so use ERC20BurnFacet to destroy tokens.

function transfer(address _to, uint256 _value) external returns (bool);

Parameters:

PropertyTypeDescription_toaddressThe address receiving the tokens. Cannot be address(0)._valueuint256The number of tokens to move. Must not exceed the caller's balance.

Returns:

PropertyTypeDescription-boolAlways true. Failures revert instead of returning false.

Reverts:

PropertyTypeDescriptionERC20InvalidReceivererror_to is address(0).ERC20InsufficientBalanceerrorThe caller holds fewer than _value tokens.

transferFrom

Moves _value tokens from _from to _to, spending the allowance that _from granted to the caller.

The allowance is checked before the balance. If both are insufficient, the call reverts with ERC20InsufficientAllowance. When the allowance is exactly type(uint256).max it is left untouched, otherwise it is reduced by _value.

function transferFrom(address _from, address _to, uint256 _value) external returns (bool);

Parameters:

PropertyTypeDescription_fromaddressThe address the tokens are taken from. Cannot be address(0)._toaddressThe address receiving the tokens. Cannot be address(0)._valueuint256The number of tokens to move. Must not exceed the caller's allowance or _from's balance.

Returns:

PropertyTypeDescription-boolAlways true. Failures revert instead of returning false.

Reverts:

PropertyTypeDescriptionERC20InvalidSendererror_from is address(0).ERC20InvalidReceivererror_to is address(0).ERC20InsufficientAllowanceerrorThe caller's allowance over _from is below _value.ERC20InsufficientBalanceerror_from holds fewer than _value tokens.

Events

Emitted by both transfer and transferFrom, including when _value is 0.

Signature:
event Transfer(address indexed _from, address indexed _to, uint256 _value);
Parameters:
PropertyTypeDescription_fromaddressThe address the tokens left. msg.sender for transfer, _from for transferFrom._toaddressThe address that received the tokens._valueuint256The number of tokens moved.

Errors

Thrown when the account sending tokens holds less than _value.

Signature:
error ERC20InsufficientBalance(address _sender, uint256 _balance, uint256 _needed);
Parameters:
PropertyTypeDescription_senderaddressThe account the tokens would have left._balanceuint256That account's current balance._neededuint256The amount the call required.

Thrown by transferFrom when _from is the zero address.

Signature:
error ERC20InvalidSender(address _sender);
Parameters:
PropertyTypeDescription_senderaddressThe rejected sender. Always address(0).

Thrown by transfer and transferFrom when _to is the zero address.

Signature:
error ERC20InvalidReceiver(address _receiver);
Parameters:
PropertyTypeDescription_receiveraddressThe rejected receiver. Always address(0).

Thrown by transferFrom when the caller's allowance over _from is below _value.

Signature:
error ERC20InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed);
Parameters:
PropertyTypeDescription_spenderaddressThe account trying to spend. Always msg.sender._allowanceuint256The current allowance._neededuint256The amount the call required.

Best Practices

Security Considerations

Balance arithmetic is safe. Every subtraction sits inside unchecked, but only after an explicit comparison has proved the balance or allowance is large enough, so it cannot underflow.

Spending an allowance emits no Approval event. transferFrom lowers the stored allowance and emits only Transfer. Across all of Compose, Approval is emitted solely by the Approve and Permit contracts, so an indexer that tracks allowances from events alone will drift. Read allowance() for the current value.

There are no external calls and no receiver hooks. Neither function calls into _to, so there is no reentrancy surface here, and no ERC-721 style check that the recipient can handle tokens. Tokens sent to a contract that cannot move them are stuck permanently.

Last updated:

Newsletter

Get notified about releases, feature announcements, and technical deep-dives on building smart contracts with Compose.

No spam. Unsubscribe anytime.