ERC-6909とERC-1155の違い|ID別権限・batch・callbackを実装比較
ERC-6909とERC-1155を同じtoken ID・残高・送付順で実装し、operator、ID別allowance、batch、receiver callback、event、metadata、gasを比較します。

ERC-6909とERC-1155は、どちらも一つのcontractで複数のtoken IDを管理できます。ただし、ERC-6909をERC-1155の上位互換として置き換えることはできません。
選定時は、次の順で要件を固定します。
- 複数IDを一つの標準関数でまとめて移す必要があるか
- 送付先contractが受取を明示的に承認する必要があるか
- 全IDを動かせるoperatorだけでなく、IDと数量を限定したspender権限が必要か
- wallet、Explorer、indexer、marketplaceが、その標準とmetadataを実際に解釈できるか
以降は、残高、権限、複数ID移動、受取確認、イベント、表示情報、ガス、製品対応の順で仕様と実測を対応させます。
ERC-1155のcore interfaceはbatch transferとreceiver callbackを含み、権限は全IDを対象にするoperatorです。ERC-6909のcore interfaceはbatchと必須callbackを外し、全IDのoperatorとID別allowanceを併設します。機能数の大小ではなく、標準が要求する外部call・権限・logの境界が違います。
この記事はOpenZeppelin Contracts 5.6.1、Solidity 0.8.36、Cancun EVM、Foundry / Anvil 1.8.0、viem 2.56.0を固定したローカル検証環境で比較します。外部RPC、mainnet fork、browser wallet、公開transaction、実資産のtoken発行や販売は行いません。
まず必要条件を同じ表へ置く
| 比較軸 | ERC-1155 core | ERC-6909 core | 選定時の確認 |
|---|---|---|---|
| 残高 | balanceOf(owner, id) |
balanceOf(owner, id) |
ownerとIDを同時に固定する |
| 自分から送る | safeTransferFrom |
transfer |
contract宛ての扱いが異なる |
| 第三者が送る | 全ID operator | operatorまたはID別allowance | 権限の広さと失効方法を分ける |
| 複数IDの移動 | safeBatchTransferFromを標準化 |
coreにはない | 独自batchのABIを標準扱いしない |
| contract受取 | callbackとmagic valueを要求 | 必須callbackなし | 拒否できるか、誤送付を検出できるか |
| transfer event | TransferSingle / TransferBatch |
Transfer |
indexed項目と配列の復号を分ける |
| metadata | uri(id) extension |
name / symbol / decimals、content URI extensions | coreとextensionを区別する |
| core interface ID | 0xd9b67a26 |
0x0f632fb3 |
ERC-165で実装を照合する |
2026-09-01時点で、ERC-1155とERC-6909のEIP本文はいずれもFinalです。一方、OpenZeppelinのERC-6909 guideにはdraftとする古い説明が残っています。標準statusはEIP本文を正本とし、libraryが実装を提供する事実とは別に記録します。
同じ残高・ID・数量で移動する
ローカル検証では両contractへ同じ初期残高をmintします。
owner
ID 1: 100
ID 2: 50
1. ownerがID 1を10移動
2. operatorがID 2を7移動
3. ID 1とID 2を4・5ずつ別accountへ移動
direct transferとoperator transfer後の残高は、両標準で一致します。違いが出るのは、誰にどこまで移動権限を渡すか、複数IDを何回の標準callで動かすか、送付先contractを呼ぶかです。
operatorは両方とも全IDへ届く
ERC-1155のsetApprovalForAll(operator, true)は、そのownerが持つすべてのIDをoperatorが移動できるようにします。core interfaceにIDと数量を限定するallowanceはありません。
ERC-6909にもsetOperator(spender, true)があり、同じく全IDへ無制限に届きます。ID別allowanceが追加されたからといって、operatorの広い権限が消えるわけではありません。
| 状態 | ERC-1155 | ERC-6909 |
|---|---|---|
| operator無効 | owner本人だけ | owner本人、またはID allowanceを持つspender |
| operator有効 | 全IDを移動可能 | 全IDを移動可能 |
| ID 1だけ8許可 | coreでは表現できない | approve(spender, 1, 8) |
| ID 2への影響 | operatorなら届く | ID 1 allowanceだけなら届かない |
local testではERC-6909でID 1を8許可し、spenderが6移動した後のallowanceが2になることを確認しました。同じspenderがID 2を移動するとrevertします。
また、同じaccountにoperatorとID allowanceの両方がある場合、固定したOpenZeppelin 5.6.1実装はoperatorを先に確認し、ID allowanceを消費しません。これは今回のlibrary実装で観測した順序です。独自実装をreviewするときは、operator pathでallowanceを誤って減算しないかもtestします。
ERC-20のapprove・Permit・Permit2・temporary approval比較では、spender、保存先、期限、失効を同じ順で確認しています。ERC-6909でも「署名があるか」ではなく、owner・spender・ID・amount・operatorの5項目を分けます。
batchは一つのtransactionと同じ意味ではない
ERC-1155はsafeBatchTransferFromをcore interfaceに含めます。ID配列と数量配列の長さ・順序を合わせ、複数残高の更新とTransferBatchを一つの標準callで表現します。送付先がcontractならbatch receiver callbackも必要です。
ERC-6909 coreにbatch selectorはありません。実装が独自batchを追加することはできますが、calldata layout、event、callback、失敗時のatomicityはその実装の契約です。integratorは「ERC-6909対応」だけから独自batchの存在を推測できません。
同じ条件のローカル検証では、次の2方法で結果を作りました。
- ERC-1155: ID 1を4、ID 2を5、一つのbatch callで移動
- ERC-6909: ID 1とID 2を二つの
transfertransactionで移動
後者は一つ目だけ成功し二つ目が失敗する状態を取り得ます。両方を一つのtransactionへまとめたい場合は、呼び出し側のbatch contractまたは実装固有extensionが必要です。
receiver callbackは受取確認と外部callを同時に作る
ERC-1155では、送付先がcontractなら残高とeventを更新した後にreceiver hookを呼びます。hookが正しいmagic valueを返さない、またはrevertすればtransfer全体もrevertします。
ローカル実行のaccept caseでは、3を受け取るcallback内からreceiver自身の残高3を読めました。reject caseはreceiptがrevertedとなり、owner残高も元に戻りました。
callback中に別transferへ入れる
再現用receiverはcallback中、受け取ったID 1の1を別accountへsafeTransferFromしました。外側と内側で合計2件のtransfer eventが発生し、receiverの最終残高は0、転送先は1です。
これはERC-1155の欠陥を意味しません。仕様どおり、callback前に残高が更新されているため、receiverがその残高を使う別callを開始できるというcontrol-flow境界です。tokenを受け取った後に独自accountingを更新するcontractは、callbackで到達可能な別entry pointと不変条件を確認します。Reentrancyの分類とguard scopeで、receiver callback、別function、別contractを同じcall graphへ置く方法を扱っています。
callbackがなければ常に安全、ではない
ERC-6909で同じreceiver contractへ送るとtransferは成功し、ERC-1155 callbackの呼出回数は0でした。必須外部callがないことは、標準transferのcontrol flowを小さくします。
ただし、receiverは受取を拒否できません。tokenを戻す関数を持たないcontractへ送れば、残高がそのcontractに残ることがあります。また、独自extension、hook、fee処理、upgrade moduleが外部callを追加する可能性は別です。「ERC-6909だからreentrancyがない」ではなく、実際の実装のcall graphを確認します。
eventから残高を再構築する
indexerは、関数名ではなくeventのindexed項目とdataを標準ごとに復号します。
// ERC-1155
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 value
);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
// ERC-6909
event Transfer(
address caller,
address indexed sender,
address indexed receiver,
uint256 indexed id,
uint256 amount
);
ERC-1155はoperator、from、toをindexedにし、IDと数量をdataへ置きます。ERC-6909はsender、receiver、idをindexedにし、callerとamountをdataへ置きます。同じ4 topicsでも意味は同じではありません。
ローカル観測コードはfresh Anvilからmint、single transfer、batch、nested transfer、burnのlogを取得し、decodeEventLogで0から残高を再構築します。
| 標準 | 復号したtransfer log | batch event | 照合したaddress・ID組 | chain残高との一致 |
|---|---|---|---|---|
| ERC-1155 | single 8件 + batch 1件 | 1 | 9 | 一致 |
| ERC-6909 | 10件 | coreになし | 8 | 一致 |
mintはfrom / senderをzero address、burnはto / receiverをzero addressとして差分へ反映します。ApprovalForAll、OperatorSet、Approvalは権限履歴であり、token残高へ加算しません。
metadataはcoreとextensionを分ける
ERC-1155のmetadata URI extensionはuri(id)を定義し、{id}をclient側で置き換える形式を取れます。base contractが返す文字列にIDが展開済みとは限りません。
ERC-6909はoptional extensionとして、IDごとのname、symbol、decimals、contract / token content URI、total supplyを分けています。OpenZeppelinのbase ERC6909だけではmetadataやsupplyを持たず、必要なextensionを継承します。
比較環境では、ERC-1155に{id} URI、ERC-6909にID 1のname、symbol、18 decimals、個別token URIを設定しました。実運用では、表示clientがどのinterfaceを問い合わせるかまで確認します。
gasは固定した操作条件だけで読む
次はlocal Anvil receiptのgasUsedです。OpenZeppelin 5.6.1、Solidity 0.8.36、optimizer 200 runs、Cancun EVM、初回受取残高0という今回の条件だけに適用します。
| 操作 | ERC-1155 | ERC-6909 | 比較単位 |
|---|---|---|---|
| contract deploy | 1,180,046 | 1,063,391 | 比較用contract一つ |
| ID 1・2を別々にmint | 95,904 | 94,334 | 2 transaction合計 |
| ownerからID 1を10移動 | 56,920 | 52,396 | 1 transaction |
| operatorがID 2を7移動 | 59,198 | 55,276 | 1 transaction |
| ID allowanceで6移動 | coreになし | 43,747 | allowance作成gasを含めないtransfer本体 |
| ID 1・2を4・5移動 | batch 88,798 | 2 transfer合計104,792 | 最終残高が同じ操作 |
| receiverへ3移動 | callback込み106,415 | 必須callbackなし52,612 | contract recipient |
この表から「ERC-6909は常に安い」とは結論しません。独自batch、metadata、supply、access control、hookを加えればbytecodeと実行gasは変わります。ERC-1155 batchとERC-6909の二つのtransactionではatomicityも異なります。
Uniswap v4のPoolManagerとflash accountingでERC-6909化という選択肢が現れるのは、protocol内部の債権をIDで管理する文脈です。walletで収集品を受け取りmarketplaceへ出す要件と、同じintegration前提にはしません。
tooling対応は3段階で記録する
| 対象 | 状態 | 今回確認したこと |
|---|---|---|
| EIP status・required interface | 公式 | EIP-1155 / 6909本文、interface ID、method、event、callback規則 |
| OpenZeppelin・Foundry・viem | 実測 | 5.6.1をcompile、13項目成功、fresh Anvil logを復号、残高一致 |
| wallet・Explorer・indexer・marketplace表示 | 未確認 | 認証済み実UIをcaptureしていないため、対応済みと書かない |
ABIを復号できることと、製品UIが残高、画像、承認、送付を正しく表示することは別です。製品対応を要件にする場合は、対象version、network、contract、ID、送付・受取・失効の画面を実測します。
用途から選ぶdecision matrix
| 必要条件 | 第一候補 | 理由 | 残る確認 |
|---|---|---|---|
| 多数IDを標準batchでatomicに移す | ERC-1155 | coreにbatchがある | receiver batch callback、配列上限 |
| contractが受取拒否できることを必須にする | ERC-1155 | magic valueでacceptを確認 | callback reentrancy、gas、互換性 |
| IDと数量を限定したspenderを標準で持つ | ERC-6909 | ID別allowanceがcoreにある | operatorの全ID権限、allowance失効 |
| protocol内部の軽量な複数債権 | ERC-6909を検討 | batch / callbackをcoreから外す | 独自integration、回収path、tooling |
| wallet / NFT marketplace互換が最優先 | 実測して決める | 標準仕様だけでは製品対応を証明できない | 対象製品・version・network |
| custom batchやhookが必須 | 実装仕様を比較 | core標準外の差が支配的 | ABI、event、atomicity、外部call |
実装・review checklist
- owner、operator、spender、ID、amountを別々に列挙したか
- operatorが全IDへ届くことをUIと失効手順で示したか
- ERC-6909 allowanceが別IDへ漏れず、finite / infiniteを正しく扱うか
- batchの失敗が全件revertか、部分成功し得るかをtestしたか
- contract recipientのaccept、reject、誤送付、回収pathを確認したか
- callbackから到達できる別entry pointと共有invariantを確認したか
- eventのindexed項目、batch配列、mint / burnのzero addressを復号したか
- core interfaceとmetadata / supply / custom extensionを分けたか
- gasにcompiler、optimizer、EVM、library、操作条件を添えたか
- wallet、Explorer、indexer、marketplace対応を推測で埋めていないか
公開した固定検証JSONに実測値とevent再構築結果を保存し、GitHubの公開コードにFoundryとviemの再現手順を含めています。生成画像は概念説明であり、製品対応、gas、event、contract安全性の証拠には使いません。
確認した一次情報
- ERC-6909 Minimal Multi-Token Interface確認日: 2026/09/01
- ERC-1155 Multi Token Standard確認日: 2026/09/01
- OpenZeppelin ERC-6909 guide確認日: 2026/09/01
- OpenZeppelin ERC-1155 guide確認日: 2026/09/01
- OpenZeppelin Contracts 5.6.1確認日: 2026/09/01
- OpenZeppelin ERC6909 5.6.1 source確認日: 2026/09/01
- OpenZeppelin ERC1155 5.6.1 source確認日: 2026/09/01
- viem decodeEventLog確認日: 2026/09/01
- Foundry v1.8.0確認日: 2026/09/01



