EIP-7702の委任(delegation)を確認・解除する方法|ウォレットがスマートアカウント化されていないか調べる
Ethereumのアドレス(EOA)にEIP-7702の委任が設定されているかを、eth_getCodeを1回呼ぶだけで確認し、委任先が安全かを判断して、ゼロアドレスへの認可で解除する手順を解説します。依存なしの確認スクリプト、検証済みのviem解除コード、メインネット600ブロックの実測つき。
目次
EIP-7702の委任が自分のウォレットに設定されていないか、どう確認して、どう解除する?
自分のアドレスに対して eth_getCode を呼びます。結果が 0x なら委任はありません。0xef0100 で始まる23バイトが返ってきたら委任されています。残りの20バイトが委任先のコントラクトで、誰かがあなたのアドレスを呼ぶたびにそのコードが実行されます。解除するには、ゼロアドレスを指定した認可を type-4 トランザクションで送るか、ウォレットの「標準アカウントに戻す」設定を使います。解除するとコードは空に戻ります。ただし、資金の移動、トークン承認(Approve)の取り消し、秘密鍵が漏れている場合の救済にはなりません。
見覚えのない「スマートアカウント」「委任」の確認画面を見た人、ブロックエクスプローラーで「Delegated to」という表示を見つけた人、Pectraアップグレード以降の自分のEOAを点検したい人に向けた記事です。
委任とは何か
EIP-7702(Final。2025年5月のPectraでEthereumメインネットに導入)は、authorization_list を持つ新しいトランザクション種別 0x04 を追加しました。リストの各要素は [chain_id, address, nonce, y_parity, r, s] で、EOAが keccak(0x05 || rlp([chain_id, address, nonce])) に署名したものです。有効な認可が処理されると、EOAのコードは委任インジケーター 0xef0100 || address に置き換わります。以後、あなたのEOAへの呼び出しはすべて、あなたの残高とストレージを使って委任先コントラクトのコードとして実行されます。仕様は、委任先のコードがアカウントに「無制限にアクセスできる」とはっきり書いています。委任は別の認可で上書きまたは解除されるまで残り、有効期限はありません。
点検の観点では、次の3点が重要です。
- チェーンごとに設定される。 特定のchain IDで署名した認可は、そのチェーンでしか効きません。
chain_id = 0で署名すると7702対応のすべてのEVMチェーンで有効になるため、使っているネットワークはそれぞれ確認します。 - 秘密鍵はそのまま使える。 委任後も通常のトランザクションに署名できます。委任は機能を足すだけで、締め出されることはありません。
- 解除の方法が仕様に用意されている。
0x0000000000000000000000000000000000000000を指定した認可は、インジケーターを書き込む代わりにコードハッシュを空に戻します。
手順1:アドレスのコードを確認する
どのJSON-RPCエンドポイントでも確認できます。次のスクリプトは依存パッケージなしで動きます(fetch を使うのでNode.js 18以上)。読み取り専用の呼び出しだけで、秘密鍵は一切求めません。
// check-7702.mjs
// Usage: node check-7702.mjs <address> [rpcUrl]
const [address, rpc = 'https://ethereum-rpc.publicnode.com'] = process.argv.slice(2);
if (!/^0x[0-9a-fA-F]{40}$/.test(address ?? '')) {
console.error('usage: node check-7702.mjs <0x address> [rpcUrl]');
process.exit(1);
}
async function rpcCall(method, params) {
const res = await fetch(rpc, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
});
const json = await res.json();
if (json.error) throw new Error(json.error.message);
return json.result;
}
const [code, chainId] = await Promise.all([
rpcCall('eth_getCode', [address, 'latest']),
rpcCall('eth_chainId', []),
]);
const hex = code.toLowerCase();
if (hex === '0x') {
console.log(`chain ${Number(chainId)}: ${address} has no code (plain EOA, no delegation)`);
} else if (hex.startsWith('0xef0100') && hex.length === 2 + 46) {
console.log(`chain ${Number(chainId)}: ${address} is DELEGATED to 0x${hex.slice(8)}`);
} else {
console.log(
`chain ${Number(chainId)}: ${address} is a contract (${(hex.length - 2) / 2} bytes of code)`
);
}
2026-10-13にメインネットで実行した結果です。出る結果は次の3パターンです。EOAのアドレス2つは伏せ字にしています。3つ目はEOAでない例として、USDCのコントラクトを指定しています。
chain 1: 0x…(your EOA) has no code (plain EOA, no delegation)
chain 1: 0x…(a delegated EOA) is DELEGATED to 0x5a7fc11397e9a8ad41bf10bf13f22b0a63f96f6d
chain 1: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 is a contract (2186 bytes of code)
チェーンごとに、そのチェーンのRPC URLを第2引数に渡して実行してください。ブロックエクスプローラーのアドレスページでも同じ情報は見られますが、基準になるのは eth_getCode の生の結果です。エクスプローラーが7702の表示に対応していないチェーンでも使えます。
手順2:その委任が想定どおりかを判断する
委任されていること自体は問題ではありません。MetaMaskなどのウォレットは7702でアカウントを「スマートアカウント」に切り替えており、MetaMaskは新規ユーザーに対して自動で有効にしています。問題は、委任先に心当たりがあるか、なぜそこに委任されているかです。
| 確認結果 | 考えられる原因 | 対応 |
| --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| 使っている全チェーンで 0x | 委任なし | 何もしなくてよい。見慣れない署名画面に応じた後は再確認する。 |
| ウォレット提供元が公開している委任先(例:MetaMaskの EIP7702StatelessDeleGator。Etherscanでは「MetaMask: EIP-7702 Delegator」と表示) | 自分かウォレットがスマートアカウント機能を有効にした | バッチ実行やガス代肩代わりを使うなら残してよい。使わないなら標準アカウントに戻して攻撃面を減らす。 |
| 正体の分からないコントラクト、またはソースが検証されていないコントラクト | dapp、リンク、「アップグレード」ページで認可に署名した | 敵対的なものとして扱う。まず資産を新しいウォレットへ移し、その後で委任を解除する(手順3)。 |
| 既知のスイーパーコントラクト、または入金した直後に資金が抜かれる | 秘密鍵かシードフレーズが漏れている | 解除してもこのウォレットは守れない。攻撃者はいつでも新しい認可に署名できる。鍵ごと捨て、救える資産を移す。 |
| 使ったことのないチェーンで委任されている | chain_id = 0 の認可に署名した(nonceが一致するすべてのチェーンで有効になる) | そのチェーンでも解除し、どこで署名したかを突き止める。 |
表の3行目と4行目が別の問題であることは、次の2つの事例から分かります。
- 鍵の漏えいとスイーパー。 Wintermuteは2025年6月、その時点の7702委任の80%超が、コピペで量産された「スイーパー」コントラクトを指していると報告しました。鍵がすでに漏れているウォレットにETHが入ると、即座に抜き取る仕組みです。委任は症状で、原因は鍵の漏えいでした。
- 正規の委任先を悪用したフィッシング。 2025年5月24日、あるMetaMaskユーザーが約14万6000ドルを失いました。アカウントの委任先はMetaMaskの正規のコントラクトで、攻撃者はトークン承認と送金をまとめた1件のバッチトランザクションに署名させました。事後に委任を解除しても防げません。防げたのは、署名前にバッチの中身を読むことでした。
EIP本文もウォレット開発者に向けて、アプリケーションがユーザーに認可への署名を求められると想定しては「ならない」と明記しています。汎用的で安全な署名画面を作る方法がないためです。dappが7702の認可に直接署名させようとしてきたら、それだけで警戒すべきサインです。メールやDMのリンク経由ならなおさらです。
手順3:委任を解除する
方法A:ウォレットの設定から戻す
ウォレットが委任を設定した場合は、そのウォレットの切り替え機能を使います。MetaMask(拡張機能)では、アカウントメニューから Account details → Smart account を開き、ネットワークごとにトグルをオフにしてトランザクションを承認します。モバイル版にも Smart account に同じトグルがあります。ネットワークごとに別々のオンチェーントランザクションになり、それぞれガス代がかかります。
方法B:ゼロアドレスへの認可を自分で送る
切り替え機能のないウォレットの場合や、自分が管理するアカウントをスクリプトでまとめて片付けたい場合は、ゼロアドレスへの認可に署名して type-4 トランザクションで送ります。viem 2.xでは次のとおりです。
import { createWalletClient, http, zeroAddress } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { mainnet } from 'viem/chains';
const account = privateKeyToAccount(process.env.PRIVATE_KEY); // the delegated EOA
const client = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.RPC_URL),
});
// The EOA sends the transaction itself, so the authorization nonce must be current nonce + 1.
const authorization = await client.signAuthorization({
contractAddress: zeroAddress,
executor: 'self',
});
const hash = await client.sendTransaction({
authorizationList: [authorization],
to: account.address,
data: '0x',
});
console.log(hash);
ポイントは executor: 'self' です。仕様では、送信者のnonceは認可リストの処理より前に1つ進みます。そのため、同じEOAが自分で送る場合、現在のnonceで署名した認可は無視されます(エラーにもなりません)。署名部分は、オンチェーンのnonceが0の使い捨て鍵を新しく作り、オフラインで確認しました。
{ chainId: 1, address: '0x0000000000000000000000000000000000000000', nonce: 1 }
hash 0x397fc2d6f03e1be9ab7ff9b87528f3363ef0163fe3b36f42f5065980b6f357b3
signer ok: true
viemはnonceを0ではなく1に設定し、recoverAuthorizationAddress は使い捨てアカウントのアドレスを返しました。この鍵には入金していないため、トランザクションはブロードキャストしていません。メインネットのアカウントで実行する前に、Sepoliaで一連の流れを試してください。
ガス代を別のアカウント(リレイヤーや自分の別ウォレット)が払う場合は executor: 'self' を外します。その場合、認可のnonceはEOAの現在のnonceになります。
手順4:解除を確認し、残りを片付ける
- 全チェーンで
check-7702.mjsをもう一度実行し、has no codeになっていることを確かめる。 - トークン承認は別に見直す。委任とERC-20のallowanceは独立しており、片方を消してももう片方には影響しない。
- 手順2で鍵の漏えいが疑われた場合は、解除後もそのアドレスを使い続けない。
委任を変更しても、アカウントのストレージは消えません。EIP-7702は、ストレージの全消去はプロトコルの機能として提供されていないと説明しています。前の委任先が残した状態を、次の委任先が読めてしまうということです。主に委任先の実装を切り替える開発者が気にすべき点で、「ステートレス」設計の委任先コントラクトならこの問題を避けられます。
メインネットで観測したこと(独自サンプル)
委任と解除が実際にどれくらい行われているかを見るため、メインネットの連続600ブロック(26,179,309〜26,179,908、終端は2026-10-13 00:13 UTC)を、通常の eth_getBlockByNumber 呼び出しで走査しました。
| 指標 | 値 |
| -------------------------------------- | ------------------ |
| 走査したトランザクション | 150,974 |
| type-4(set code)トランザクション | 1,875(1.2%) |
| 含まれていた認可タプル | 2,963 |
| 委任先の種類 | 73 |
| 最も多い委任先の占有率 | 959タプル(32.4%) |
| MetaMaskの EIP7702StatelessDeleGator | 164タプル(5.5%) |
| ゼロアドレスへのリセット | 83タプル(2.8%) |
| chain_id = 0 の認可 | 0 |
これはブロックに含まれた認可の数で、EVMが受理した数ではありません。無効なタプルは、トランザクションを失敗させずに読み飛ばされるためです。また、約1時間分のスナップショットであり、傾向を示すものではありません。それでも、委任はすでに日常的なトラフィックになっていること、解除も一定数行われていてゼロアドレスによる解除が本番のウォレットで普通に使われていることは読み取れます。
参考資料
- EIP-7702: Set Code for EOAs(Ethereum Improvement Proposals)
- How to switch to or revert from a smart account in MetaMask(MetaMask Support)
- signAuthorization(viem documentation)
- Wintermute warns Pectra upgrade leaves Ethereum users at risk of automated attacks(The Block)
- Understanding EIP-7702 phishing attacks(GoPlus Security)
- MetaMask Delegation Framework audit, February 2025(Consensys Diligence)
内部リンク
免責事項
投資助言ではありません。一般的なセキュリティ情報の提供のみを目的としています。ウォレットの画面、委任先コントラクト、各チェーンのEIP-7702対応状況は頻繁に変わります。利用しているウォレットとネットワークで現在の挙動を確認し、スクリプトは実資金で使う前にテストネットで試してください。