Token Transfer Traces
Overview
This document explains the different ways a token can be transferred from an externally owned account (EOA) with the Ethereum protocol. It is a companion to the custom token transfer TLA+ spec. There is also a blog post that explains the TLA+ spec.
The document differentiates between custom and native tokens, but not between custom tokens (ERC-20, ERC-721, ERC-1155) and ignores the different transfer methods of contracts. Gas fees are not modelled.
Native Token Transfer
A native token transfer happens without contract execution.
flowchart TB
on_chain_sig[On-Chain Sig] --> eth_nodes[ETH Nodes]
eth_nodes --> native_token_transfer[Native Token Transfer]
Custom Token Transfer
The simplest form of transferring a custom token is with a single on-chain signature. The caller of the transfer method of the token contract must be the token owner EOA in this case.
flowchart TB
on_chain_sig[On-Chain Sig] --> eth_nodes[ETH Nodes]
eth_nodes --> token_contract[Token Contract]
token_contract --> custom_token_transfer[Custom Token Transfer]
Custom Token Approval
The token owner EOA can allow an other address (EOA or contract) to spend the token with an approval.
EOA spender
flowchart TB
on_chain_sig_approve[On-Chain Sig Approve]-->|Step 1|eth_nodes[ETH Nodes]
eth_nodes-->token_contract[Token Contract]
token_contract-->custom_token_approval[Custom Token Approval]
on_chain_sig_transfer[On-Chain Sig Transfer]-->|Step 2|eth_nodes
eth_nodes --> token_contract[Token Contract]
token_contract --> custom_token_transfer[Custom Token Transfer]
%% CSS-based class defs don't work
classDef green stroke:green;
classDef orange stroke:orange;
class on_chain_sig_approve green
class custom_token_approval green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
class on_chain_sig_transfer orange
class custom_token_transfer orange
linkStyle 3 stroke: orange
linkStyle 4 stroke: orange
linkStyle 5 stroke: orange
Contract Spender
flowchart TB
on_chain_sig_approve[On-Chain Sig Approve]-->|Step 1|eth_nodes[ETH Nodes]
eth_nodes-->token_contract[Token Contract]
token_contract-->custom_token_approval[Custom Token Approval]
on_chain_sig_spend[On-Chain Sig Tx]-->|Step 2|eth_nodes
eth_nodes-->spender_contract[Spender Contract]
spender_contract-->token_contract
token_contract-->custom_token_transfer[Custom Token Transfer]
%% CSS-based class defs don't work
classDef green stroke:green;
classDef orange stroke:orange;
class on_chain_sig_approve green
class custom_token_approval green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
class on_chain_sig_spend orange
class custom_token_transfer orange
linkStyle 3 stroke: orange
linkStyle 4 stroke: orange
linkStyle 5 stroke: orange
linkStyle 6 stroke: orange
Off-Chain Spend
Following an on-chain spender approval by the token owner, an off-chain signature by the owner can suffice to execute a transfer of the token. This pattern is typically used by exchanges and marketplaces such as CoW Swap and Seaport.
flowchart TB
on_chain_sig_approve[On-Chain Sig Approve]-->|Step 1|eth_nodes[ETH Nodes]
eth_nodes-->token_contract[Token Contract]
token_contract-->custom_token_approval[Custom Token Approval]
off_chain_sig_spend[Off-Chain Sig Tx]-->|Step 2|relayer
relayer[Relayer] --> eth_nodes
eth_nodes-->spender_contract[Spender Contract]
spender_contract-->token_contract
token_contract-->custom_token_transfer[Custom Token Transfer]
%% CSS-based class defs don't work
classDef green stroke:green;
classDef orange stroke:orange;
class on_chain_sig_approve green
class custom_token_approval green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
class off_chain_sig_spend orange
class relayer orange
class custom_token_transfer orange
class spender_contract orange
linkStyle 3 stroke: orange
linkStyle 4 stroke: orange
linkStyle 5 stroke: orange
linkStyle 6 stroke: orange
linkStyle 7 stroke: orange
Permit
If a token contract implements the permit extension defined in ERC-2612, the token owner EOA can grant a spender approval to an address (EOA or contract) with an off-chain signature. The permit message doesn't have to be passed by the EOA to the token contract. More info.
Permit Contract Spender
Permit Contract Spender Single Tx
flowchart TB
off_chain_sig_permit[Off-Chain Sig Permit] --> on_chain_sig_tx[On-Chain Sig Tx]
on_chain_sig_tx --> eth_nodes[ETH Nodes]
eth_nodes --> spender_contract[Spender Contract]
spender_contract-->|Call Permit|token_contract[Token Contract]
spender_contract-->|Call Transfer|token_contract[Token Contract]
token_contract --> custom_token_approval[Custom Token Approval]
token_contract --> custom_token_transfer[Custom Token Transfer]
Permit Contract Spender Multiple Tx
flowchart TB
off_chain_sig_permit[Off-Chain Sig Permit]-->|Step 1|on_chain_sig_tx[On-Chain Sig Tx]
on_chain_sig_tx --> eth_nodes[ETH Nodes]
eth_nodes --> spender_contract[Spender Contract]
spender_contract-->|Call Permit|token_contract[Token Contract]
token_contract --> custom_token_approval[Custom Token Approval]
on_chain_sig_spend[On-Chain Sig Spend]-->|Step 2|eth_nodes[ETH Nodes]
eth_nodes --> spender_contract
spender_contract -->|Call Transfer| token_contract
token_contract --> custom_token_transfer[Custom Token Transfer]
%% CSS-based class defs don't work
classDef green stroke:green;
classDef orange stroke:orange;
class off_chain_sig_permit green
class on_chain_sig_tx green
class on_chain_sig_approval green
class custom_token_approval green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
linkStyle 3 stroke: green
linkStyle 4 stroke: green
class custom_token_transfer orange
class on_chain_sig_spend orange
linkStyle 5 stroke: orange
linkStyle 6 stroke: orange
linkStyle 7 stroke: orange
linkStyle 8 stroke: orange
Permit EOA Spender
flowchart TB
off_chain_sig_permit[Off-Chain Sig Permit]-->|Step 1|on_chain_sig_tx[On-Chain Sig Tx]
on_chain_sig_tx-->eth_nodes[ETH Nodes]
eth_nodes-->token_contract[Token Contract]
token_contract-->custom_token_transfer[Custom Token Approval]
on_chain_sig_transfer[Spender On-Chain Sig Transfer]-->|Step 2|eth_nodes
eth_nodes-->token_contract
token_contract --> custom_token_transfer[Custom Token Transfer]
class off_chain_sig_permit green
class on_chain_sig_tx green
class on_chain_sig_approval green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
linkStyle 3 stroke: green
class off_chain_sig_transfer orange
class custom_token_transfer orange
linkStyle 4 stroke: orange
linkStyle 5 stroke: orange
linkStyle 6 stroke: orange
Permit2
Single spender contract used by all protocols. Advantage over ERC-20 Permit is that it doesn't need changes to the token contract. More info.
flowchart TB
on_chain_sig_approve[On-Chain Sig Approve]-->|Step 1|eth_nodes[ETH Nodes]
eth_nodes-->token_contract[Token Contract]
token_contract-->custom_token_approval[Custom Token Approval]
off_chain_sig_permit[Off-Chain Sig Permit]-->|Step 2|on_chain_sig_tx[On-Chain Sig Tx]
on_chain_sig_tx --> eth_nodes[ETH Nodes]
eth_nodes --> protocol_contract[Protocol Contract]
protocol_contract --> spender_contract[Spender Contract]
spender_contract --> token_contract
token_contract --> custom_token_transfer[Custom Token Transfer]
%% CSS-based class defs don't work
classDef green stroke:green;
classDef orange stroke:orange;
class on_chain_sig_approve green
class custom_token_approval green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
class off_chain_sig_permit orange
class on_chain_sig_tx orange
class on_chain_sig_spend orange
class custom_token_transfer orange
class protocol_contract orange
class spender_contract orange
linkStyle 3 stroke: orange
linkStyle 4 stroke: orange
linkStyle 5 stroke: orange
linkStyle 6 stroke: orange
linkStyle 7 stroke: orange
linkStyle 8 stroke: orange
Meta Transaction
With meta transactions (ERC-2771), the token implementation trusts a forwarder contract to feed it transactions to save gas fees for the EOA. The token contract treats method calls from the forwarder as if they were called by the EOA directly.
It is assumed that the forwarder contract verifies off-chain signatures by the user, but it's not verified by the token contract. If the relayer fails to verify the owner's signature, we treat that as a vulnerability of the token contract, since it's the token contract that chooses to trust the relayer.
Meta Custom Token Transfer
flowchart TB
off_chain_sig[Off-Chain Sig Transfer] --> relayer[Relayer]
relayer --> eth_nodes[ETH Nodes]
eth_nodes --> trusted_forwarder_contract[Trusted Forwarder Contract]
trusted_forwarder_contract --> token_contract[Token Contract]
token_contract --> custom_token_transfer[Custom Token Transfer]
Meta Custom Token Approval
Meta EOA spender
Token approval with meta transaction where the spender is an EOA. On the 2/A path, the approved spender transfers the token through a meta-transaction. On the 2/B path, the approved spender EOA transfers the token via a normal transaction.
flowchart TB
off_chain_sig[Off-Chain Sig Approve] -->|Step 1| relayer[Relayer]
relayer --> eth_nodes[ETH Nodes]
eth_nodes --> trusted_forwarder_contract[Trusted Forwarder Contract]
trusted_forwarder_contract --> token_contract[Token Contract]
token_contract --> custom_token_approval[Custom Token Approval]
off_chain_sig[Off-Chain Sig] -->|Step 2/A| relayer[Relayer]
relayer -->|2/A| eth_nodes[ETH Nodes]
eth_nodes -->|2/A| trusted_forwarder_contract
eth_nodes -->|2/B| token_contract[Token Contract]
trusted_forwarder_contract -->|2/A| token_contract
token_contract --> custom_token_transfer[Custom Token Transfer]
on_chain_sig_transfer[On-Chain Sig Transfer] -->|Step 2/B| eth_nodes[ETH Nodes]
%% CSS-based class defs don't work
classDef green stroke:green;
classDef orange stroke:orange;
class on_chain_sig_approve green
class custom_token_approval green
class trusted_forwarder_contract green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
linkStyle 3 stroke: green
linkStyle 4 stroke: green
class on_chain_sig_transfer orange
class custom_token_transfer orange
linkStyle 5 stroke: orange
linkStyle 6 stroke: orange
linkStyle 7 stroke: orange
linkStyle 8 stroke: orange
linkStyle 9 stroke: orange
linkStyle 10 stroke: orange
linkStyle 11 stroke: orange
Meta Contract Spender
Token approval with a meta transaction where the spender is a contract. On the 2/A path the spender contract itself allows meta transactions, so the spender transfers the token with a meta transaction. On the 2/B path, the spender transfers the token with a normal transaction.
flowchart TB
off_chain_sig[Off-Chain Sig Approve] -->|Step 1| relayer[Relayer]
relayer --> eth_nodes[ETH Nodes]
eth_nodes --> trusted_forwarder_contract[Trusted Forwarder Contract]
trusted_forwarder_contract --> token_contract[Token Contract]
token_contract --> custom_token_approval[Custom Token Approval]
off_chain_sig[Off-Chain Sig] -->|Step 2/A| relayer[Relayer]
relayer -->|2/A| eth_nodes[ETH Nodes]
eth_nodes -->|2/A| trusted_forwarder_contract
eth_nodes -->|2/B| spender_contract[Spender Contract]
trusted_forwarder_contract -->|2/A| spender_contract
spender_contract --> token_contract
token_contract --> custom_token_transfer[Custom Token Transfer]
on_chain_sig_transfer[On-Chain Sig Transfer] -->|Step 2/B| eth_nodes[ETH Nodes]
%% CSS-based class defs don't work
classDef green stroke:green;
classDef orange stroke:orange;
class on_chain_sig_approve green
class custom_token_approval green
class trusted_forwarder_contract green
linkStyle 0 stroke: green
linkStyle 1 stroke: green
linkStyle 2 stroke: green
linkStyle 3 stroke: green
linkStyle 4 stroke: green
class on_chain_sig_transfer orange
class custom_token_transfer orange
class spender_contract orange
linkStyle 5 stroke: orange
linkStyle 6 stroke: orange
linkStyle 7 stroke: orange
linkStyle 8 stroke: orange
linkStyle 9 stroke: orange
linkStyle 10 stroke: orange
linkStyle 11 stroke: orange
linkStyle 12 stroke: orange