Skip to main content

ERC-20 Transfer Module

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

Helper functions for moving ERC-20 tokens and accessing ERC-20 storage

Key Features
  • transfer moves msg.sender's own tokens. It cannot move tokens for any other account.
  • transferFrom spends the allowance that _from granted to msg.sender.
  • Neither helper performs a role check, and none is needed because both are bound to msg.sender.
Module Usage

Use helper functions from Compose using your own custom facets. See Facets & Modules for more information.

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

getStorage

Returns a pointer to the ERC20Storage struct.

function getStorage() pure returns (ERC20Storage storage s);

Returns:

PropertyTypeDescriptionsERC20Storage storageThe struct in storage.

transfer

Moves _value tokens from the caller to _to.

Inside a diamond, your facet runs through delegatecall, so msg.sender here is the account that called the diamond, not the diamond itself. Transferring 0 is allowed and still emits Transfer.

function transfer(address _to, uint256 _value) 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 msg.sender.

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) 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).ERC20InsufficientAllowanceerrormsg.sender'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 msg.sender'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

Integration Notes

Import the module under a namespace and call it from your facet:

Calling transfer from a custom facet
import "src/token/ERC20/Transfer/ERC20TransferMod.sol" as ERC20TransferMod;

contract MyFacet {
function transferTokens(address _to, uint256 _value) external returns (bool) {
return ERC20TransferMod.transfer(_to, _value);
}
}

ERC20Storage lives at keccak256("erc20") inside the diamond. That is the same slot ERC20DataFacet, ERC20TransferFacet, and the other ERC-20 contracts use, so balances written here are what balanceOf() reports.

Every balance subtraction sits inside unchecked, but only after an explicit comparison has proved the balance or allowance is large enough, so it cannot underflow. Keep that ordering if you adapt the code.

Last updated:

Newsletter

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

No spam. Unsubscribe anytime.