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

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.