3MIKAN
仮想通貨直コン

ERC-6909とERC-1155の違い|ID別権限・batch・callbackを実装比較

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

3MIKANのブランドキャラクターが一つの複数token棚を、batchと受取gateを持つ方式、ID別権限札を持つ方式に分けて比較する記事画像

ERC-6909とERC-1155は、どちらも一つのcontractで複数のtoken IDを管理できます。ただし、ERC-6909をERC-1155の上位互換として置き換えることはできません。

選定時は、次の順で要件を固定します。

  1. 複数IDを一つの標準関数でまとめて移す必要があるか
  2. 送付先contractが受取を明示的に承認する必要があるか
  3. 全IDを動かせるoperatorだけでなく、IDと数量を限定したspender権限が必要か
  4. 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が実装を提供する事実とは別に記録します。

同じ複数token棚を中心に、ERC-1155の全ID operator gateと、ERC-6909の全ID operatorおよびID別数量札を比較する補助図
権限の正確な関数名と範囲は画像ではなく、次の表と再現結果を正本にします。

同じ残高・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を二つのtransfer transactionで移動

後者は一つ目だけ成功し二つ目が失敗する状態を取り得ます。両方を一つの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残高も元に戻りました。

ERC-1155の複数token trayがreceiver gateでacceptまたはrevertされる経路と、ERC-6909が必須gateを通らず個別に届く経路を比較する補助図
callbackがない経路は外部callを一つ減らしますが、送付先contractがtokenを扱える証明にはなりません。

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として差分へ反映します。ApprovalForAllOperatorSetApprovalは権限履歴であり、token残高へ加算しません。

metadataはcoreとextensionを分ける

ERC-1155のmetadata URI extensionはuri(id)を定義し、{id}をclient側で置き換える形式を取れます。base contractが返す文字列にIDが展開済みとは限りません。

ERC-6909はoptional extensionとして、IDごとのnamesymboldecimals、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安全性の証拠には使いません。

確認した一次情報