はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、ウォレットがDAppに対して実行権限を付与し、ユーザーの代わりにトランザクションを送信できる仕組みを提案しているERC7715についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

ERC7715は、DApp(分散型アプリケーション)がウォレットに対して 「特定の権限を付与してほしい」と要求するための、新しいJSON-RPCメソッドである wallet_grantPermissions を定義します。

ここでいうJSON-RPCは、DAppとウォレットが通信するための一般的なリモート呼び出し方式です。 Ethereumのウォレット連携では、すでに広く使われています。

ERC7715の目的は、「ユーザー本人が毎回トランザクションを署名しなくても、条件付きでウォレットが処理を許可できる仕組み」を標準化することです。

これにより、これまで実現が難しかった操作が可能になります。

想定されている2つのユースケース

ウォレット未接続ユーザーのトランザクション実行

ERC7715では、ユーザーが常にウォレットを接続していなくても、事前に許可された範囲内でトランザクションを実行できます。

これは、以下のようなケースを可能にします。

ユースケース 説明
サブスクリプション 定期的な支払いを、ユーザー操作なしで実行できます
積立・自動投資 あらかじめ決めたルールに従って自動実行できます
指値注文 価格条件を満たしたときに自動で取引できます

従来は、ウォレットが接続されていない状態ではトランザクションを送れませんでした。 ERC7715は、この制約を根本的に解消します。

権限でスコープされたウォレット接続

ERC7715では、「この操作だけ」、「このコントラクトだけ」、「この期間だけ」といった限定された権限(スコープ) をウォレットがDAppに付与できます。

その結果、DAppは付与された範囲内でのみトランザクションを実行できます。 これは、フル権限を常に渡す従来方式と比べて、安全性と使いやすさの両方を改善します。

動機

現在の一般的なウォレット承認フロー

現在、多くのDAppは以下のような流れで動作しています。

  1. ユーザーがDAppで操作を行います。
  2. ウォレットが毎回ポップアップします。
  3. ユーザーがトランザクション内容を確認し、署名します。
  4. トランザクションが送信されます。

この方式はシンプルですが、明確な問題があります。

ユーザー体験の問題

インタラクションが多いアプリケーション、特にゲームやソーシャル系DAppでは、 操作のたびにウォレット承認が必要になります。

その結果、以下の問題が発生します。

  • 操作が途切れやすくなります
  • ユーザーが疲れて離脱しやすくなります
ウォレット未接続時に何もできない問題

現状の仕組みでは、ウォレットが接続されていない限りトランザクションは一切送信できません。

そのため、以下のWeb2では当たり前の機能が実現できません。

  • 定期実行
  • 条件付き実行
  • バックグラウンド処理

この制約が、DAppの設計を大きく制限しています。

AA(Account Abstraction)における課題

AA(Account Abstraction)とは、EOA(秘密鍵1本で操作する従来のアカウント)ではなく、スマートコントラクトによってアカウントの挙動を柔軟に定義する考え方です。

AAの文脈では、セッションキーという仕組みがよく使われます。

セッションキーとは何か

セッションキーとは、以下の特徴を持つ鍵です。

  • 一時的に有効
  • 実行できる操作が限定されている

例えば、以下の使い方がされます。

  • ゲーム操作専用
  • 特定コントラクト専用
  • 有効期限付き
ベンダー依存の問題

現状では、セッションキーの実装方法がウォレットやサービスごとに異なります。

そのため、DApp側が統一的に「権限をください」と要求できず、ウォレットごとに個別対応が必要になります これは、エコシステム全体の拡張性を下げる原因になっています。

ERC7715が解決しようとしていること

ERC7715は、以下を標準化します。

  • 権限付与の要求方法
  • ウォレットが権限を承認するためのインターフェース

これにより、DAppはウォレットの実装差異を意識せずに権限を要求できます。 また、ウォレットは安全に権限管理を行えず、ユーザー体験が大きく改善されます。

結果として、Web2に近い操作感を持つDAppを、Web3の安全性を保ったまま実現できるようになります。

仕様

wallet_grantPermissions

ERC7715は、DAppがウォレットに対して「この範囲でユーザーの代わりに実行してよい」という権限をリクエストするために、JSON-RPCメソッド wallet_grantPermissions を定義します。

ここでいう「権限」は、トランザクション送信などの行動を、時間や範囲を区切って許可するものです。

権限スキーマ

型定義

wallet_grantPermissions の params は、PermissionRequest の配列です(複数チェーンや複数権限をまとめて申請できます)。

ts
type PermissionRequest = {
  chainId: Hex; // hex-encoding of uint256
  address?: Address;
  expiry: number; // unix timestamp
  signer: {
    type: string; // enum defined by ERCs
    data: Record<string, any>;
  };
  permissions: {
    type: string; // enum defined by ERCs
    data: Record<string, any>;
  }[];
}[];

用語も含めて、各フィールドを説明します。

フィールドの意味
項目名 フィールド 必須 説明
チェーンID chainId 必須 EIP155の chainId を指定します。ここで指定したチェーンに対する権限リクエストになり、以降のアドレスなどもこのチェーン文脈で解釈されます。
対象アカウント address 任意 権限を付与する対象アカウント(ユーザーのアカウント)です。すでにウォレット接続があり、複数アカウントが公開されている場合に「どのアカウントに対する許可か」を明確にできます。省略した場合は、ユーザーがウォレット側で選ぶ前提になります。
有効期限 expiry 必須 UNIX タイムスタンプ(秒)で、セッションが失効する時刻です。ERC7715では、このセッションはその時刻までに 必ず失効する必要があります。
署名者(サイナー) signer 必須 権限に紐づく「実行主体」を表します。キーやアカウントを指定する場合もあれば、ウォレットがセッション管理を引き受ける場合もあります。
権限一覧 permissions 必須 signer が対象アカウントの代わりに実行できる「許可された振る舞い」を配列で渡します。

EIP155については以下の記事を参考にしてください。

「hex-encoding of uint256」は「uint256(0〜2^256-1 の整数)を 16進表現(0x付き)で表す」という意味です。 仕様上 chainId: Hex になっていますが、例では数値 123 が置かれています。 ここは実装時に「要求どおりHexで送る」か「ウォレットが受け付ける表現に寄せる」かを揃える必要があります(仕様としてはHexを意図しています)。

リクエスト例

ERC7715では、wallet_grantPermissions の params に PermissionRequest の配列を渡します。

例は「特定アカウントに対し、ネイティブトークン送金(ETH のような通貨)の上限付き許可を期限付きで与える」イメージです。

js
[
  {
    chainId: 123,
    address: '0x...',
    expiry: 1577840461,
    signer: {
      type: 'account',
      data: {
        address:'0x016562aA41A8697720ce0943F003141f5dEAe006',
      }
    },
    permissions: [
      {
        type: 'native-token-transfer',
        data: {
          allowance: '0x1DCD6500'
        }
      }
    ],
  }
]

ここで出てくる allowance は一般的には「上限額」を意味しますが、ERC7715では意味の詳細は “Permissions” セクションに委ねられている前提です。そのためこの記事では「この permission の data に含まれる上限らしき値」という説明に留めます。

レスポンス仕様

型定義

返り値(result)は PermissionResponse の配列で、リクエストで送った内容をベースに、追加情報が付いた形になります。

ts
type PermissionResponse = PermissionRequest & {
  context: Hex;
  accountMeta?: {
    factory: `0x${string}`;
    factoryData: `0x${string}`;
  };
  signerMeta?: {
    // 7679 userOp building
    userOpBuilder?: `0x${string}`;
    // 7710 delegation
    delegationManager?: `0x${string}`;
  };
};
「リクエストと同じとは限らない」点が重要

ERC7715は「レスポンスには元のリクエストの全パラメータが含まれる」としつつ、「返ってきた値が、要求した値と同じである保証はない」と明言しています。

つまり、DApp は「要求した値」ではなく「返ってきた値」を正として扱う必要があります。 例えば、期限が延びたり短くなったり許可上限が調整されたりする可能性があります。

context

context は、付与された権限を識別するための「まとめID」のような役割です。 権限の取り消し(後述の wallet_revokePermissions)や userOp の送信などで参照できるようにするためのフィールドで、識別情報以外のデータが入ることもあります。

また context は、ERC7679やERC7710で定義されるコンテキストを入れてよい(MAY)とされています。

ERC7679については以下の記事を参考にしてください。

userOp はERC4337系で使うUser Operationのことで、通常のトランザクションとは別フォーマットの実行リクエストです(AA でよく出ます)。

ERC4337については以下の記事を参考にしてください。

accountMeta

accountMeta は任意ですが、存在する場合は factory と factoryData が必須で、ERC4337に沿った形で返す必要があります。 2つは「両方ある」か「両方ない」かのどちらかです。

そして重要なのが以下です。

  • アカウントがまだデプロイされていない場合、ウォレットは必ず accountMeta を返します。
  • その場合、DApp は MUST(必ず) factory コントラクトを factoryData を calldata として呼び出して、アカウントをデプロイしなければなりません。

「factory」はアカウントを作るコントラクト、「factoryData」はその呼び出しに必要なデータ(calldata)です。 calldata は「コントラクト呼び出し時に渡す入力データ」のことです。

signerMeta

signerMeta はアカウントタイプ次第で必要になる追加情報です。

signer.type の想定 signerMeta の要件 何のためか
wallet 不要 ウォレットがセッション管理を担うので追加メタがいらない、という扱いです。
key または keys userOpBuilder が必須 ERC7679 で定義される userOp 構築情報が必要になります。
account delegationManager が必須 ERC7710の委任(delegation)管理に必要な情報が要求されます。

ERC7679については以下の記事を参考にしてください。

ERC7710については以下の記事を参考にしてください。

「delegation」は「あるアカウントが別の主体に権限を委任して実行させる」考え方です。

エラー(リクエスト不正・拒否)

リクエストが壊れている、またはウォレットが権限付与できない/したくない場合、ウォレットはERC1193で定義されたコードのエラーを返さなければなりません。

ERC1193については以下の記事を参考にしてください。

ERC1193は、Ethereum Provider(ブラウザの window.ethereum など)周りの標準的なエラー形式を定義している規格です。

wallet_grantPermissions のレスポンス例

レスポンスは PermissionResponse 配列で、例では以下がポイントです。

  • expiry がリクエストより後ろ(1577840461 → 1577850000)に変更されています。
  • allowance が大きい値に変更されています。
  • context が追加されています。
js
[
  {
    // original request with modifications
    chainId: 123,
    address: '0x...',
    expiry: 1577850000,
    signer: {
      type: 'account',
      data: {
        address:'0x016562aA41A8697720ce0943F003141f5dEAe006',
      }
    },
    permissions: [
      {
        type: 'native-token-transfer',
        data: {
          allowance: '0x1DCD65000000'
        }
      },
    ],
    // response-specific fields
    context: "0x0x016562aA41A8697720ce0943F003141f5dEAe0060000771577157715"
  }
]

この例は、DAppが「自分の要求どおり」と決め打ちせず、ウォレットが返した expiry や allowance をそのまま採用すべきことをわかりやすく示しています。

wallet_revokePermissions

ERC7715は、付与された権限を取り消すためのメソッド wallet_revokePermissions も定義します。 DAppがこのメソッドを呼ぶと、ウォレットは成功時に空のレスポンスを返します。

リクエスト仕様

params は以下の形です。

ts
type RevokePermissionsRequestParams = {
  permissionContext: "0x{string}";
};

permissionContext は、wallet_grantPermissions のレスポンスで返ってきた context を渡す用途です。 つまり「どの権限を取り消すか」を context で指定します。

レスポンス仕様

成功時の result は空です。

ts
type RevokePermissionsResponseResult = {};

空レスポンスが「成功」を意味する設計なので、DApp側は「例外が返ってこない=成功」として扱う実装になることが多いです。

全体の流れ

理解しやすいように、付与と取り消しの流れを図にします。

sequenceDiagram
  participant DApp
  participant Wallet
  participant Chain as Blockchain

  DApp->>Wallet: wallet_grantPermissions(params: PermissionRequest[])
  Wallet-->>DApp: PermissionResponse[] (context, 返却値は変更され得る)

  alt accountMeta が返った(未デプロイ)
    DApp->>Chain: factory(factoryData) を呼び出してアカウントをデプロイ
  end

  Note over DApp,Wallet: DApp は以降、返ってきた expiry/permissions を正として扱う

  DApp->>Wallet: wallet_revokePermissions({permissionContext: context})
  Wallet-->>DApp: {}(空レスポンス)

SignerとPermissionのタイプについて

ERC7715は、DAppがウォレットに「どの主体(Signer)が」、「何をしてよいか(Permission)」を伝えるために、よく使われそうな signer / permission のタイプをいくつか定義しています。

ただし、ここで定義されているのは「よく使う想定の代表例」で、網羅リストではありません。 ウォレットが進化すれば新しいタイプが増える前提なので、DAppとウォレットの両方が対応するなら、そのタイプは有効です。

一方で、同じ type 名が別物を指してしまうと事故が起きます。 例えば、DAppは「Aの意味で type: "key"」を要求したのに、ウォレットが「Bの意味の type: "key"」として許可してしまう、というズレが起こりえます。 これを避けるために、signer / permission の type 名は重複しないことが重要で、新しいタイプはERC7715の改訂として、または別のERCとして仕様化するべきだとしています。

Signers

Signerは「この権限を使って実行する主体は誰か」を表します。 ERC7715では、代表的に以下の4種類を定義しています。

型定義
ts
// A wallet is the signer for these permissions
// `data` is not necessary for this signer type as the wallet is both the signer and grantor of these permissions
type WalletSigner = {
  type: "wallet";
  data: {};
};

// The types of keys that are supported for the following `key` and `keys` signer types.
type KeyType = "secp256r1" | "secp256k1" | "ed25519" | "schnorr";

// A signer representing a single key.
// "Key" types are explicitly secp256r1 (p256) or secp256k1, and the public keys are hex-encoded.
type KeySigner = {
  type: "key";
  data: {
    type: KeyType;
    publicKey: `0x${string}`;
  };
};

// A signer representing a multisig signer.
// Each element of `publicKeys` are all explicitly the same `KeyType`, and the public keys are hex-encoded.
type MultiKeySigner = {
  type: "keys";
  data: {
    keys: {
      type: KeyType;
      publicKey: `0x${string}`;
    }[];
  };
};

// An account that can be granted with permissions as in ERC-7710.
type AccountSigner = {
  type: "account";
  data: {
    address: `0x${string}`;
  };
};
署名者タイプ type data の中身 どういう時に使うか
ウォレットが署名者 "wallet" {}(不要) ウォレット自身が「署名」と「権限付与」をまとめて管理します。DApp側が鍵を持つ必要がなく、ウォレット主導のセッションになります。
単一鍵 "key" type(鍵種別)と publicKey(公開鍵) 1本の鍵でセッションを表します。公開鍵は 0x 付き16進です。
複数鍵(マルチシグ的) "keys" 同じ KeyType の公開鍵配列 複数の鍵で signer を表します。配列の各要素は同一の鍵種別である必要があります。
アカウント "account" address(アカウントアドレス) ERC7710 の委任(delegation)として権限を扱いたいときに使います。

補足ですが、KeyType に出てくる用語は以下の意味です。

用語 説明
secp256k1 EthereumのEOAが一般に使う楕円曲線鍵です。
secp256r1(p256) WebAuthn / Passkey などでよく見る p256 系です。
ed25519 EdDSA系の代表で、別のチェーンやシステムでもよく使われます。
schnorr Schnorr署名方式を指します(曲線や詳細はここでは固定しません)。

Permissions

Permissionは「このセッション(または signer)がユーザーの代わりに何をしてよいか」を表します。 ERC7715は代表例として、送金系とセッション消費量を制限する系を定義しています。

型定義
ts
// Native token transfer, e.g. ETH on Ethereum
type NativeTokenTransferPermission = {
  type: "native-token-transfer";
  data: {
    allowance: "0x..."; // hex value
  };
};

// ERC20 token transfer
type ERC20TokenTransferPermission = {
  type: "erc20-token-transfer";
  data: {
    address: "0x..."; // erc20 contract
    allowance: "0x..."; // hex value
  };
};

// ERC721 token transfer
type ERC721TokenTransferPermission = {
  type: "erc721-token-transfer";
  data: {
    address: "0x..."; // erc721 contract
    tokenIds: "0x..."[]; // hex value array
  };
};

// ERC1155 token transfer
type ERC1155TokenTransferPermission = {
  type: "erc1155-token-transfer";
  data: {
    address: "0x..."; // erc1155 contract
    allowances: {
      [tokenId: string]: "0x..."; // hex value
    };
  };
};

// The maximum gas limit spent in the session in total
type GasLimitPermission = {
  type: "gas-limit";
  data: {
    limit: "0x..."; // hex value
  };
};

// The number of calls the session can make in total
type CallLimitPermission = {
  type: "call-limit";
  data: {
    count: number;
  };
};

// The number of calls the session can make during each interval
type RateLimitPermission = {
  type: "rate-limit";
  data: {
    count: number; // the number of times during each interval
    interval: number; // in seconds
  };
};
権限タイプ type 何を許すか data の意味
ネイティブトークン送金 "native-token-transfer" 例:EthereumのETHのようなネイティブ通貨の送金 allowance は 16進の上限値です。
ERC20 トークン送金 "erc20-token-transfer" 特定の ERC20 トークンの送金 address はERC20コントラクト、allowance は上限値です。
ERC721 トークン送金 "erc721-token-transfer" 特定のERC721NFTの送付 address は ERC721 コントラクト、tokenIds は許可するトークンID配列です。
ERC1155 トークン送金 "erc1155-token-transfer" 特定のERC1155の送付 address はERC1155コントラクト、allowances は tokenId ごとの上限マップです。
ガス上限 "gas-limit" セッション全体で消費してよいガスの総量を制限 limit は 16進の上限値です。
呼び出し回数上限 "call-limit" セッション全体で許される呼び出し回数を制限 count が回数です。
レート制限 "rate-limit" 一定間隔ごとに許される呼び出し回数を制限 interval(秒)あたり count 回まで、という意味です。

ERC20については以下の記事を参考にしてください。

ERC721については以下の記事を参考にしてください。

ERC1155については以下の記事を参考にしてください。

ウォレット管理セッション

Signerが type: "wallet" の場合は、ウォレット自身がセッションを管理します。

このときERC7715は、ウォレットがリクエストを承認したなら、ERC5792の wallet_sendCalls を「permissions capability付き」で受け付けなければならないとしています。 さらに、その capability に permissionsContext を含めてもよいとしています。

wallet_sendCalls は「複数のコールをまとめて送る」ための呼び出しで、capabilities.permissions.context を渡すことで「このセッション権限で実行してほしい」を示します。

例
js
[
  {
    version: "1.0",
    chainId: "0x01",
    from: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    calls: [
      {
        to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        value: "0x9184e72a",
        data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
      },
      {
        to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        value: "0x182183",
        data: "0xfbadbaf01",
      },
    ],
    capabilities: {
      permissions: {
        context: "<permissionContext>",
      },
    },
  },
];
ウォレット側の期待動作

このリクエストを受け取ったウォレットは、要求された permissions に従って call を送信する必要があります。 そして、追加のトランザクション確認をユーザーに求めるべきではありません。

ここでの permissionsContext は、前の節で出てきた wallet_grantPermissions のレスポンス内 context と同種の「権限の識別子」として扱われています。

理解用に流れを図にすると以下です。

sequenceDiagram
  participant DApp
  participant Wallet
  participant Chain as Blockchain

  DApp->>Wallet: wallet_grantPermissions(signer.type="wallet")
  Wallet-->>DApp: context / permissionsContext を返す

  DApp->>Wallet: wallet_sendCalls(capabilities.permissions.context を付与)
  Wallet->>Chain: permissions に従って calls を送信(MUST)
  Note over Wallet: 追加の確認は求めない(SHOULD NOT)

Capabilities

ウォレットがERC5792をサポートする場合、wallet_getCapabilities に対して permissions キーを使って応答することが推奨されています。

さらに、ウォレットが対応している signer / permission のタイプを、レスポンス内に含めることが推奨されています(SHOULD)。これにより、DApp は「このウォレットは何ができるか」を事前に判断できます。

wallet_getCapabilities の例
json
{
  "0x123": {
    "permissions": {
      "supported": true,
      "signerTypes": ["keys", "account"],
      "keyTypes": ["secp256k1", "secp256r1"],
      "permissionTypes": ["erc20-token-transfer", "erc721-token-transfer"]
    }
  }
}

ここではチェーンごと(例:"0x123")に、permissions がサポートされているか、どの signerTypes / permissionTypes / keyTypes に対応しているかを返しています。

CAIP-25を使う場合

ウォレットがCAIP25 authorizationを使っている場合、sessionProperties に permissions キーを含めることが推奨されています。加えて、対応タイプをカンマ区切り文字列で入れる追加キーも推奨されています。

CAIP25については以下の記事を参考にしてください。

CAIP25は「ウォレットセッションの権限や性質をメタデータとして表現する」ための枠組み、と捉えると読みやすいです。

例は以下です。

json
{
  "sessionProperties": {
    "permissions": "true",
    "signerTypes": "keys,account",
    "permissionTypes": "erc20-token-transfer,erc721-token-transfer"
  }
}

セッションを使ったトランザクション送信

ここからは、「権限を付与してもらったあと実際にどう送るか」の話です。ERC7715は大きく2ルートを示しています。

  • ERC7679 key / keys signer を使うルート(ERC4337 の userOp を組み立てる)
  • ERC7710 account signerを使うルート(委任を redeem して実行する)

ERC7679

signer.type が key または keys のとき、wallet_grantPermissions のレスポンスには以下が返る想定です。

  • permissionsContext(権限コンテキスト。前段の context と同じ役割として扱われています)
  • signerMeta.userOpBuilder(UserOp Builderのコントラクトアドレス)

DAppはこれらを使って、ERC7679が提供する方法でERC4337の userOp を構築します。

また ERC7679のUserOp Builderコントラクトは、すべてのメソッドに bytes calldata context を持ちます。 これは wallet_grantPermissions で返った permissionsContext と同等だと説明されています。

userOp 署名フォーマット例
js
const getSignature = async ({
  address,
  userOp,
  permissionsContext,
}: GetSignatureArgs) => {
  return readContract(config, {
    abi: BUILDER_CONTRACT_ABI,
    address: BUILDER_CONTRACT_ADDRESS,
    functionName: "getSignature",
    args: [address, userOp, permissionsContext],
  });
};

この例では、Builderコントラクトの getSignature に permissionsContext を渡して、セッション権限に紐づく署名を作っています。

flowchart LR
  A[DApp: wallet_grantPermissions] --> B[Wallet: permissionsContext と userOpBuilder を返す]
  B --> C[DApp: userOp を作る]
  C --> D[DApp: userOpBuilder に context を渡して署名を作る]
  D --> E[UserOp を送る(ERC-4337)]

ERC-7710

signer.type が account のとき、返ってきたデータはERC7710のインターフェースで redeem(償還)できる形になっています。

やりたいことはシンプルで、「権限を受け取った側が、ユーザーのアカウントに このメッセージを発行して実行してと依頼する」です。 送金でも、userOp でも、支払い/リレーのインフラは受け取り側が好きに選べる、と説明されています。

具体的には、permissions.signerMeta.delegationManager に対して、以下の関数を呼び出します。

  • redeemDelegation(bytes calldata _data, Action calldata _action)

ここで _data は permissions.permissionsContext を渡します。 _action は「ユーザーアカウントにやらせたい処理」を表します。

Action 構造体
js
struct Action {
    address to;
    uint256 value;
    bytes data;
}

to は呼び出し先、value は送金額、data は calldata です。

疑似コード例
js
// Alice requests a permission from Bob
const permissionsResponse = await bob.request({
  method: 'wallet_grantPermissions',
  params: [{
    address: bob.address,
    chainId: 123,
    signer: {
      type: 'account',
      data: {
        id: alice.address
      }
    },
    permissions: [
      {
        type: 'native-token-transfer',
        data: {
          allowance: '0x0DE0B6B3A7640000'
        },
      },
      {
        type: 'gas-limit';
        data: {
          limit: '0x0186A0',
        },
      },
    ],
    expiry: Math.floor(Date.now() / 1000) + 3600 // 1 hour from now
  }]
});

// Extract the permissionsContext and delegationManager
const permissionsContext = permissionsResponse.permissionsContext;
const delegationManager = permissionsResponse.signerMeta.delegationManager;

// Alice forms the action she wants Bob's account to take
const action = {
  to: alice.address,
  value: '0x06F05B59D3B20000'
  data: '0x'
};

// Alice sends the transaction by calling redeemDelegation on Bob's account
const tx = await bob.sendTransaction({
  to: delegationManager,
  data: bob.interface.encodeFunctionData('redeemDelegation', [
    permissionsContext,
    action
  ])
});

この例は「AliceがBobから permission をもらい、Bobのアカウントに自分(Alice)宛の送金をさせる」という流れです。 permission の中に native-token-transfer と gas-limit があり、さらに expiry で 1時間に制限されています。

なお、Signer の型定義では data が { address: ... } ですが、この例では id になっています。 ここは、実装時に「どのキー名で何を渡すか」を仕様側と揃える必要があるポイントです(原文内で表記がずれています)。

理解用に流れを図にすると以下です。

sequenceDiagram
  participant Alice
  participant BobWallet as BobのWallet/Provider
  participant DelegationManager
  participant Chain as Blockchain

  Alice->>BobWallet: wallet_grantPermissions(signer.type="account")
  BobWallet-->>Alice: permissionsContext と signerMeta.delegationManager を返す

  Alice->>DelegationManager: redeemDelegation(permissionsContext, Action)
  DelegationManager->>Chain: Bobのアカウントが Action を実行

補足

ERC7715が狙っているのは、これまで一般的だった「トランザクションを提案する → 承認する → 送信する」という流れが持つ限界を避けることです。 この流れは、一見わかりやすいのですが、DAppの設計とユーザー体験の両方を強く縛ります。

まず、ユーザーがオンラインでないと送信できない点が大きな制約です。 ウォレットが接続されておらず、ユーザーがその場にいないと、DAppはユーザーの代わりにトランザクションを出せません。 その結果、サブスクリプションのような定期実行や、自動売買のような条件付き実行が成立しません。

次に、すべての操作で毎回ユーザー承認が必要になる点です。 ゲームのように操作回数が多い DAppでは、ウォレットのポップアップと承認が頻繁に入り、体験が途切れます。 「本当は滑らかに進めたい操作」が「署名作業の連続」になってしまいます。

ERC7715は、DAppがウォレットに対して権限付与をリクエストできるようにし、ウォレットが許可した範囲内でDAppがユーザーの代わりに実行できるようにします。 これによって、オンライン必須と毎回承認必須という2つの問題を回避できるようにしています。

permissionsContext

ERC7715は「DAppとウォレットのやり取り」を標準化しますが、「ウォレットが権限をどう強制するか」までは規定しません。 ウォレットによって権限の実現方法が違ってもよい、という設計です。

ただ、そのままだとDApp側が困ります。 DAppは、ウォレットが許可した権限を反映した形でトランザクションや userOp を組み立てる必要があります。 しかし、権限の実現方式がウォレットごとに違うなら、DAppに渡す「手がかり」が必要です。

そこで ERC7715は permissionsContextを用意します。

permissionsContext の性質

permissionsContext は「中身をDAppが解釈できるとは限らない不透明な文字列」です。 つまり、DAppにとっては「意味を決め打ちせず、ウォレットが渡した値をそのまま別の呼び出しに渡すもの」として扱うのが基本です。

この不透明さが重要で、ウォレットは権限方式に応じて自由に情報を詰められます。ERC7715は、その利用先として特に3つの方式を想定しています。

想定される3つの使い方
想定スキーム DAppが permissionsContext を使う場所 どういう意味で使うか
ERC7679を使う UserOp Builderとやり取りする context 引数 userOp を組み立てる時に「この権限セッションである」ことを埋め込むためのコンテキストになります。
ERC7710を使う delegation managerの _data 引数 redeemDelegation などで委任を償還するためのデータとして、そのまま渡します。
アプリ内セッション(in-app sessions)を使う ERC5792の wallet_sendCalls のセッション識別子 wallet_sendCalls 実行時に「どのセッション権限でやるか」を示す識別子として扱います。

「アプリ内セッション」は、DAppがアプリ側でセッション概念を持ち、ウォレットに対しては「このセッションで送って」という合図だけ渡す形を指しています。

理解用に、permissionsContext の流れを図にします。

flowchart TB
  A[DApp: wallet_grantPermissions] --> B[Wallet: permissionsContext を返す]

  B --> C1[ERC-7679: UserOp Builder の context に渡す]
  B --> C2[ERC-7710: delegationManager の _data に渡す]
  B --> C3[ERC-5792: wallet_sendCalls の session識別子として渡す]

SignerとPermissionの非網羅リスト

ERC7715が signer / permission の型を網羅しないのは、ウォレット技術の進化で新しい型が増える前提だからです。 つまり、将来的に「こういう権限が欲しい」、「こういう署名主体が必要」という要求が必ず出てくるので、最初から閉じたリストにしない方が現実的です。

ただし前提として、DAppとウォレットの両方がその型をサポートする意思があることが必要です。 片方だけが知っている型では動きません。

衝突回避についての考え方

型名の衝突を避けるために「UUIDを必須にする」案も検討したと書かれています。 UUIDにすると、型名の重複が起きにくいからです。

しかし ERC7715は最終的に、より単純な方針を選びました。 つまり「signer / permission の type はERCで定義することを必須にする」というやり方です。 ERCとして公開されれば、型名の重複を避けやすく、実装者も参照しやすいという判断です。

互換性

ERC7715に対応していないウォレットが wallet_grantPermissions を呼ばれた場合、エラーを返すことが推奨されています。

つまりDApp側の現実的な挙動としては、wallet_grantPermissions を試し、エラーが返ったら従来フロー(毎回署名)にフォールバックする、という形になります。

ここは「未対応でも黙って無視しないで、明確に失敗として返してほしい」という期待です。 DAppが「対応していない」ことを判定しやすくなります。

セキュリティ

ERC7715は「ユーザーの代わりに実行できる」仕組みなので、設計を間違えると権限が強すぎて危険になります。

権限スコープの制限

DAppは、必要な権限だけを要求し、妥当な期限を設定するべきです。 これは「何でもできる長期権限」を避けるという話です。

また、ウォレットは権限を正しく強制する必要があります。 最終的にはユーザーは自分のウォレット実装を信頼するしかなく、権限 enforcement はウォレット実装の一部として扱われます。

ここは責任分界がはっきりしていて、DAppがいくら「制限したつもり」でも、ウォレットが enforcement を誤れば意味がありません。 逆に、ウォレットが正しく enforcement するなら、DAppが要求した範囲以上のことは起きにくくなります。

権限スコープの考え方を整理すると以下です。

観点 期待されること
DApp側 必要最小限の permission を要求し、短すぎず長すぎない expiry を付けます。
ウォレット側 許可した範囲を正しく守らせます。権限 enforcement はウォレット品質そのものです。
フィッシング攻撃

悪意あるDApp が、正規アプリのふりをしてユーザーに広い権限を与えさせる可能性があります。 これは「署名を騙し取る」よりも強烈で、広い権限を一度与えるとその範囲内では継続して悪用されうるからです。

そのため ERC7715は、ウォレットがユーザーに対して権限内容を明確に表示し、危険な権限に対して警告を出さなければならないとしています。

ここでの「明確に表示」とは、少なくとも以下がユーザーに伝わる状態を指すと考えるのが自然です。

  • 何が許可されるのか(例:送金、特定トークン移転、回数制限など)
  • どの範囲か(上限、対象コントラクト、対象 tokenId など)
  • いつまで有効か(expiry)

引用

Luka Isailovic (@lukaisailovic), Derek Rein (@arein), Dan Finlay (@danfinlay), Derek Chiang (@derekchiang), Fil Makarov (@filmakarov), Pedro Gomes (@pedrouid), Conner Swenberg (@ilikesymmetry), Lukas Rosario (@lukasrosario), "ERC-7715: Grant Permissions from Wallets [DRAFT]," Ethereum Improvement Proposals, no. 7715, May 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7715.

最後に

今回は「ウォレットがDAppに対して実行権限を付与し、ユーザーの代わりにトランザクションを送信できる仕組みを提案しているERC7715」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!