Transfer event parser for ERC-20, ERC-721, and ERC-1155 token standards.
Parses raw Ethereum logs into normalized %Transfer{} structs — the "follow
the money" primitive for wallet analytics. Every token movement on Ethereum
emits a Transfer event; this module decodes all three standards.
Does
- Parse raw log maps into
%Transfer{}structs (parse_log/1,parse_logs/1) - Fetch and parse transfer logs in one call (
fetch/2) - Expose topic hashes for filter building (
transfer_topics/0)
Does Not
- Index or store transfers (see rexex for durable indexing)
- Track balances over time (compose with
Onchain.ERC20for snapshots) - Handle non-standard transfer events (custom names, non-indexed params)
Token Standard Detection
ERC-20 and ERC-721 share the same Transfer(address,address,uint256) topic hash.
Distinguished by topic count:
- 3 topics
[topic0, from, to]+ value indata→ ERC-20 - 4 topics
[topic0, from, to, tokenId]+ empty/no data → ERC-721
ERC-1155 uses separate TransferSingle and TransferBatch signatures.
Error Format
- Non-transfer log:
{:error, {:unknown_event, :not_a_transfer}} - Decode errors: propagated from
Onchain.Log.decode_event/2
Functions
| Function | Purpose |
|---|---|
parse_log/1 | Single raw log → Transfer struct(s) |
parse_log!/1 | Same, raises on error |
parse_logs/1 | List of raw logs → Transfer structs (skips non-Transfer) |
parse_logs!/1 | Same, raises on error |
fetch/2 | eth_get_logs + parse_logs convenience |
fetch!/2 | Same, raises on error |
transfer_topics/0 | The 3 topic0 hashes for filter building |
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
fetch! | 2 | Fetch transfer logs from chain and parse into structs. Raises on error. | filter: value, opts: value |
fetch | 2 | Fetch transfer logs from chain and parse into structs. | filter: value, opts: value |
parse_logs! | 1 | Parse a list of raw logs, skipping non-Transfer events. Raises on error. | logs: value |
parse_logs | 1 | Parse a list of raw logs, skipping non-Transfer events. | logs: value |
parse_log! | 1 | Parse a single raw log map into Transfer struct(s). Raises on error. | log: value |
parse_log | 1 | Parse a single raw log map into Transfer struct(s). | log: value |
transfer_topics | 0 | Returns the 3 topic0 hashes for ERC-20/721/1155 Transfer events. | - |
Summary
Functions
Fetch transfer logs from chain and parse into structs.
Fetch transfer logs from chain and parse into structs. Raises on error.
Parse a single raw log map into Transfer struct(s).
Parse a single raw log map into Transfer struct(s). Raises on error.
Parse a list of raw logs, skipping non-Transfer events.
Parse a list of raw logs, skipping non-Transfer events. Raises on error.
Returns the 3 topic0 hashes for ERC-20/721/1155 Transfer events.
Types
@type t() :: %Onchain.Transfer{ amount: non_neg_integer() | nil, block_number: non_neg_integer(), from: String.t(), log_index: non_neg_integer(), operator: String.t() | nil, to: String.t(), token: String.t(), token_id: non_neg_integer() | nil, token_standard: :erc20 | :erc721 | :erc1155, transaction_hash: String.t() }
Functions
Fetch transfer logs from chain and parse into structs.
Parameters
filter- Filter map for eth_get_logs (e.g. %{from_block: 18_000_000, to_block: 18_000_100, address: "0x..."}) (value)opts- Options: :rpc_url, :timeout (default:[], value)
Returns
Parsed transfer structs from matching logs ({:ok, [t()]} | {:error, term})
# descripex:contract
%{
params: %{
opts: %{
default: [],
description: "Options: :rpc_url, :timeout",
kind: :value
},
filter: %{
description: "Filter map for eth_get_logs (e.g. %{from_block: 18_000_000, to_block: 18_000_100, address: \"0x...\"})",
kind: :value
}
},
returns: %{
type: "{:ok, [t()]} | {:error, term}",
description: "Parsed transfer structs from matching logs"
}
}
Fetch transfer logs from chain and parse into structs. Raises on error.
Parameters
filter- Filter map (see fetch/2) (value)opts- Options: :rpc_url, :timeout (default:[], value)
Returns
Parsed transfer structs ([t()])
# descripex:contract
%{
params: %{
opts: %{
default: [],
description: "Options: :rpc_url, :timeout",
kind: :value
},
filter: %{description: "Filter map (see fetch/2)", kind: :value}
},
returns: %{type: "[t()]", description: "Parsed transfer structs"}
}
Parse a single raw log map into Transfer struct(s).
Parameters
log- Raw log map with :topics, :data, :address, :block_number, :transaction_hash, :log_index (value)
Returns
Single struct for ERC-20/721/1155-Single, list for 1155-Batch ({:ok, t()} | {:ok, [t()]} | {:error, term})
# descripex:contract
%{
params: %{
log: %{
description: "Raw log map with :topics, :data, :address, :block_number, :transaction_hash, :log_index",
kind: :value
}
},
returns: %{
type: "{:ok, t()} | {:ok, [t()]} | {:error, term}",
description: "Single struct for ERC-20/721/1155-Single, list for 1155-Batch"
}
}
Parse a single raw log map into Transfer struct(s). Raises on error.
Parameters
log- Raw log map (see parse_log/1) (value)
Returns
Transfer struct(s) (t() | [t()])
# descripex:contract
%{
params: %{log: %{description: "Raw log map (see parse_log/1)", kind: :value}},
returns: %{type: "t() | [t()]", description: "Transfer struct(s)"}
}
Parse a list of raw logs, skipping non-Transfer events.
Parameters
logs- List of raw log maps from eth_get_logs (value)
Returns
Flat list of Transfer structs (batches expanded, non-transfers skipped) ({:ok, [t()]})
# descripex:contract
%{
params: %{
logs: %{description: "List of raw log maps from eth_get_logs", kind: :value}
},
returns: %{
type: "{:ok, [t()]}",
description: "Flat list of Transfer structs (batches expanded, non-transfers skipped)"
}
}
Parse a list of raw logs, skipping non-Transfer events. Raises on error.
Parameters
logs- List of raw log maps (value)
Returns
Flat list of Transfer structs ([t()])
# descripex:contract
%{
params: %{logs: %{description: "List of raw log maps", kind: :value}},
returns: %{type: "[t()]", description: "Flat list of Transfer structs"}
}
@spec transfer_topics() :: [String.t()]
Returns the 3 topic0 hashes for ERC-20/721/1155 Transfer events.
Returns
List of 0x-prefixed keccak256 hashes: [Transfer, TransferSingle, TransferBatch] ([String.t()])
# descripex:contract
%{
returns: %{
type: "[String.t()]",
description: "List of 0x-prefixed keccak256 hashes: [Transfer, TransferSingle, TransferBatch]"
}
}