ABI・function selector・calldata・event logsを手で読む方法
ABI、4-byte function selector、calldata、event topic、logsの構造を、固定したERC-20 transferの例とviem APIで解説します。

ExplorerでInput DataやLogsを開くと、長い16進数が並びます。最初は意味のない文字列に見えますが、区切り方を知れば「どの関数を、誰に対して、どの数量で実行したか」を確認できます。
イメージは、宛名や金額が決まった書類を、コンピュータが読める決まった幅へ並べ直したものです。ABIは、その書類の各欄をどう読むか示す説明書です。
ABIはデータを読むための型情報
ABI形式のデータは、それだけでは各値の意味を説明してくれません。同じ32バイト列でも、uint256、address、bytes32のどれとして読むかで意味が変わります。
その読み方を決めるのがABIです。関数の目印を検索した結果だけで断定せず、コントラクトの検証済みソースコードや、現在の実装に対応するABIと照合します。
function selectorは先頭4バイトの目印
関数名と引数の型を決められた書式で並べ、Keccak-256ハッシュの先頭4バイトを使います。この4バイトが、呼び出す関数の目印(function selector)です。
transfer(address,uint256)
↓ Keccak-256
0xa9059cbb...
↓ first 4 bytes
0xa9059cbb
ERC-20のtransfer(address,uint256)を呼び出す入力データ(calldata)は、次の形になります。
0xa9059cbb
0000000000000000000000001111111111111111111111111111111111111111
000000000000000000000000000000000000000000000000000000000001e240
0xa9059cbb: 呼び出す関数の目印- 1つ目の32バイト: 送金先の
address。20バイトの値を左側からゼロで埋めている - 2つ目の32バイト: 送る数量。この例では
0x1e240 = 123456
つまり、この例は「0x1111...1111へ123456単位を送る」という指示です。小数点以下の桁数はトークンごとに違うため、人が見る数量へ直すときはdecimalsも確認します。
viemで入力データをデコードする
import { decodeFunctionData } from 'viem'
const decoded = decodeFunctionData({
abi: erc20Abi,
data,
})
console.log(decoded.functionName, decoded.args)
手で読む方法は検算に向いています。ただし、tuple、array、string、bytesなどの可変長データでは、別の位置を指すoffsetとデータ長も追う必要があります。
実装ではABIライブラリを使い、手読みは「関数の目印、32バイトの境界、アドレス、整数が不自然でないか」を確認する用途に限定します。
動的型はheadとtailに分かれる
stringや可変長bytesは、その位置に実データを直接置きません。最初の部分(head)には実データの位置を示すoffsetを置き、後ろの部分(tail)へ長さとデータを置きます。
head: offset to tail
...
tail: length | data | padding
たとえば、申込書の欄へ長い文章を直接書かず、「詳細は別紙のこの位置」と示すイメージです。
offsetは入力データ全体の先頭ではなく、ABI引数ブロックの先頭を基準にする点に注意してください。
event logsは「処理中に発行された記録」
event logsは、コントラクトが処理中に発行する記録です。送金や交換が行われたときに、関係するアドレスや数量を後から検索しやすい形で残します。
non-anonymous eventでは、topics[0]にevent署名のKeccak-256ハッシュが入り、indexedに指定された引数が続きます。indexedでない引数はdataへABI形式で格納されます。
ERC-20のTransfer(address indexed from, address indexed to, uint256 value)なら、次の形です。
topics[0] = keccak256("Transfer(address,address,uint256)")
topics[1] = from
topics[2] = to
data = value
import { parseEventLogs } from 'viem'
const transfers = parseEventLogs({
abi: erc20Abi,
logs: receipt.logs,
eventName: 'Transfer',
})
indexedの動的型は元データへ戻せない
stringやbytesなどの動的型をindexedにすると、topicには元の値ではなく、特殊な形式で作ったKeccakハッシュが入ります。そのため、topicだけから元の文字列を復元することはできません。
logsだけで成功を断定しない
ログが見つかっただけで、取引全体の成功を断定しないでください。最終的には、実行結果(transaction receipt)のstatus、コントラクトアドレス、ブロック番号、log indexも確認します。
別コントラクトが同名のeventを発行している、proxy経由で実行された、誤ったABIで読んでいる、といった可能性があるためです。
トランザクション全体の解析は実行結果・入力データ・エラーの確認順、エラーの目印はcustom errorのデコードへ進んでください。直コンで書き込みを行う前にも、送信先、関数の目印、引数を確認する習慣が役立ちます。
小さな例で検算する
上のERC-20送金例では、次の4点が一致すれば読み方は合っています。
- 先頭4バイトが
transfer(address,uint256)の目印である - 1つ目の32バイトから送金先
0x1111...1111を復元できる - 2つ目の32バイトが10進数の
123456になる Transferログの送信先と数量が、実行結果の内容と矛盾しない
最初から長いデータをすべて手で読む必要はありません。まず、関数の目印、アドレス、数量の3点だけをライブラリのデコード結果と見比べると、入力先や桁数の間違いに気づきやすくなります。
確認した一次情報
- Solidity Contract ABI Specification確認日: 2026/8/28
- viem decodeFunctionData確認日: 2026/8/28
- viem parseEventLogs確認日: 2026/8/28
- ethereum.org トランザクション確認日: 2026/8/28



