# [ERC7699] ERC20送金に支払い参照を持たせる仕組みを理解しよう！

> ERC7699は、ERC20トークンの送金に「支払い参照情報」を付与できるようにする拡張仕様です。ここでいう支払い参照情報とは、「この送金が何の支払いなのか」を示す短いメッセージや識別情報のことです。

- URL: https://cardene.dev/articles/8aeed08a9ffa827109b8
- 公開日: 2026-01-03
- 更新日: 2026-01-03
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、ERC20送金に「**何の支払いか**」を示す参照情報を付与できる仕組みを提案している**ERC7699**についてまとめていきます！

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

https://eips.ethereum.org/EIPS/eip-7699

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

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ERC7699**は、**ERC20**トークンの送金に「**支払い参照情報**」を付与できるようにする拡張仕様です。
ここでいう支払い参照情報とは、「**この送金が何の支払いなのか**」を示す短いメッセージや識別情報のことです。
請求書番号や注文番号、取引の目的などを想定しています。

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

[\[ERC20\] ERC20を1からわかりやすく解説](/articles/aeefe2d89dd5332c9f43)

現在の**ERC20**では、`transfer` や `transferFrom` を使ってトークンを送金することはできますが、その送金に対して公式に定義された「**メッセージ**」や「**用途**」を一緒に渡す仕組みがありません。
そのため、ブロックチェーン上のトランザクションだけを見ても、「**この送金がどの請求書に対応しているのか**」、「**どの注文の支払いなのか**」が分からない状態になります。

**ERC7699**では、この問題に対して最小限の変更で対応しています。
具体的には、既存の**ERC20**の仕組みを壊さずに、送金時に参照情報を付けられるメソッドを追加することで、送金と業務データ（注文・請求など）を結び付けられるようにします。

これにより、以下のようなことが可能になります。

* 利用者は「**何の支払いか**」を明示した上でトークンを送金できる
* 受取側（店舗やサービス提供者）は、送金と注文・請求情報を簡単に対応付けられる
* 会計処理や入金消込（どの請求に対する入金かを確定する作業）がしやすくなる

この拡張は、ユーザー体験の向上だけでなく、実務でのトークン利用を現実的にするための基盤として位置付けられています。

## 動機

**ERC7699**が提案された主な理由は、**ERC20**が実務的な支払い用途において不十分だからです。

#### 現状の課題

現在、**ERC20**トークンで支払いを行う場合、支払いの目的や請求書番号などの情報はオンチェーンには残りません。
そのため、多くのプロジェクトでは次のような対応を取っています。

* オフチェーン（データベースや管理画面）で送金と注文を紐付ける
* 独自のスマートコントラクト（支払い用プロキシ）を実装する

これらの方法は実現可能ですが、以下の問題があります。

* 実装が複雑になりやすい
* プロジェクトごとに仕様がバラバラになる
* 標準化されていないため、ツールやサービス側で汎用的に扱えない

#### 伝統的な金融システムとの比較

一方で、銀行送金などの伝統的な金融システムでは、「**支払い参照情報**」は当たり前に使われています。
以下は、代表的な例です。

| システム  | 項目名  | 内容       |
| --------------- | ----- | --------------- |
| SWIFT MT103     | Field 70（Remittance Information）         | 「**INVOICE 998877の支払い**」などの用途説明 |
| SWIFT MT103     | Field 72（Sender to Receiver Information） | 送金者から受取者への補足情報               |
| ISO 20022（SEPA） | RmtInf（Remittance Information）           | 支払いの目的や請求情報                  |

これらの仕組みがあることで、企業や個人は送金データだけを見て、どの取引に対する支払いかを簡単に判断できます。

#### ERC7699が目指すもの

**ERC7699**は、こうした伝統的な金融システムの利便性を、分散型金融（DeFi）やトークン送金の世界にも持ち込むことを目的としています。

**ERC20**に支払い参照情報を追加できるようにすることで、次の効果が期待されます。

* トークン送金を「**単なる資産移動**」から「**意味のある支払い**」にできる
* 商取引や請求業務にトークンを使いやすくなる
* オンチェーンとオフチェーンの情報を無理なく接続できる

**ERC7699**は、ブロックチェーンと既存の金融実務との間にあるギャップを埋めるための、実務寄りで現実的な拡張仕様です。
これにより、トークン決済がより自然に日常的な取引へ組み込まれることを目指しています。

## 仕組み

### 対象となるコントラクト

**ERC7699**は、**ERC20**に準拠しているトークンコントラクトを拡張する前提で設計されています。

そのため、**ERC20**に準拠したコントラクトが**ERC7699**に対応する場合、後述するインターフェースを必ず実装する必要があります。

### インターフェース

**ERC7699**で追加されるインターフェースは以下です。

```js
// The EIP-165 identifier of this interface is 0x1522573a

interface IERC7699 {

function transfer(address to, uint256 amount, bytes calldata transferReference) external returns (bool);

function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference) external returns (bool);

event TransferReference(bytes32 indexed loggedReference);

}
```

#### ERC165インターフェース識別子

このインターフェースは**ERC165**に対応しており、識別子は `0x1522573a` です。
これにより、外部コントラクトやツールは「**このトークンがERC7699に対応しているか**」を動的に判定できます。

:::message
**ERC165**については以下の記事を参考にしてください。
:::

[\[ERC165\] コントラクトが特定のインターフェースをサポートしているか確認する仕組みを理解しよう！](/articles/541184977a5cba51b759)

#### `transfer` / `transferFrom` の拡張内容

**ERC7699**では、**ERC20**の `transfer` と `transferFrom` を拡張しています。

##### 追加された引数

両関数には、以下の引数が追加されています。

* `transferReference`
    * 型は `bytes`
    * 送金の参照情報（支払い目的、請求番号など）を表します。

この関数は、通常の**ERC20**と同じ送金処理を行った上で、参照情報をイベントとして記録する点が特徴です。

#### `TransferReference` イベントの発行ルール

##### 基本ルール

`transfer` および `transferFrom` が呼び出された場合、通常の送金処理に加えて、必ず `TransferReference` イベントを発行する必要があります。

```js
event TransferReference(bytes32 indexed loggedReference);
```

このイベントは、送金に関連する参照情報をログとして残すためのものです。

##### ERC20の `Transfer` イベントとの順序

* `TransferReference` イベントは、**ERC20**の `Transfer` イベントよりも先に発行する必要があります。
* 実装上は、できる限り直後に `Transfer` イベントを発行することが望ましいとされています。

これにより、クライアントやインデクサーは、両イベントを関連付けて処理しやすくなります。

#### `loggedReference` と `transferReference` の関係

`loggedReference` は、`transferReference` と必ずしも同一である必要はありません。

#### 許可されている挙動

* `transferReference` が `32` バイト以下の場合
    そのまま `bytes32` にしてログ出力してもよい
* `transferReference` が長い、または構造化されている場合
    ハッシュ化や加工した値を `loggedReference` として出力してもよい

そのため、`transferReference` と `loggedReference` が同じであると期待してはいけません。
実装方法はコントラクト開発者に委ねられています。

#### `loggedReference` に格納できるデータ

`loggedReference` は `bytes32` 型であり、その中身に制限はありません。

* 任意の `32` バイトデータを格納できます。
* 意味付けは、アプリケーションや運用ルールに依存します。

#### `transferReference` が空の場合の扱い

`transferReference` は空であっても構いません。

ただし、この場合のみ、特別なルールがあります。

* `transferReference` が空の場合
  → **TransferReference イベントを発火してはいけません**

この挙動により、**ERC20**の通常の `transfer` と同じ振る舞いを再現できます。
つまり、参照情報が不要な場合は、既存の**ERC20**と全く同じ使い方が可能です。

#### `transferReference` の長さとガスコスト

`transferReference` の長さには、仕様上の制限はありません。

ただし、以下の理由から利用者には短い参照情報を使うことが推奨されています。

* `calldata` サイズが増える
* 実行時のガスコストが増える

#### `TransferReference` イベントの宣言方法

`TransferReference` イベントは、`anonymous` 指定をしてはいけません。

これは、以下の特性を維持するためです。

* イベントシグネチャがログに含まれる
* フィルタリングやインデックス検索が可能になる

#### 送金量が `0` の場合の扱い

送金量が `0` の場合でも、通常の送金と同じように扱う必要があります。

つまり、残高は変わらなくても `transferReference` が空でなければ `TransferReference` イベントは必ず発行されます。

## 補足

#### パラメータ名

**ERC7699**で追加されたパラメータ名が `transferReference` になっているのは、銀行送金などの伝統的な金融（TradFi）で一般的な「**支払い参照情報（payment reference）**」の考え方に合わせるためです。

銀行の世界では、送金に「**これは何の支払いか**」を示す参照情報を付けるのが普通です。
これにより、受取側は「**どの注文**」、「**どの請求書**」に対応する入金なのかを結び付けて、入金確認や帳簿処理をやりやすくしています。
**ERC7699**も同じ目的を持っているため、名前を `transferReference` にして、用途が一目で伝わるようにしています。

また、金融業界で確立した用語を使うことで、**ERC20**拡張としての理解が進み、採用されやすくなることも狙いです。

#### パラメータ型

**ERC7699**の `transferReference` の型は `bytes` です。

ここは最初 `bytes32` も検討されていました。
理由は以下の2つです。

* 参照情報は短い方が一般的なので、短い参照を自然に促せる
* 長い参照が必要なら、参照内容をKeccak-256（Ethereumでよく使うハッシュ関数）でハッシュ化して `bytes32` に収める設計にしやすい

ただ最終的には、`bytes32` に固定すると使い方が狭くなると判断され、`bytes` に決まりました。
`bytes` なら長さに制限がなく、構造化データをそのまま渡せます。

ここでいう「**構造化データ**」は、例えば「**参照情報＋署名**」のように、複数の情報を1つのバイト列にまとめたものです。
署名を含められると、受取側が追加の検証処理を行うような設計も可能になります。

#### イベントで出すデータ

**ERC7699**のイベント側の `loggedReference` の型は `bytes32` です。

ここでは「**イベントにも `bytes calldata` をそのまま載せる案**」も検討されました。
ただし、イベント購読（event subscription filter）で使うインデックス付きトピック（indexed topic）の仕組み上、参照情報はログにそのまま載るのではなく、結果として `bytes32` 形式のトピックになります。

つまり、イベントでフィルタリング可能な形にすると、最終的に `bytes32` になる事情があります。
そこで最初から `bytes32` として `loggedReference` を定義しておく方が、現実のログ処理と合います。

さらに `bytes32` にすると、32バイト以内であれば「**ハッシュ化されていない、人が読める参照情報**」をそのままログに載せることもできます。
短い請求番号や注文番号のような用途なら、この方が扱いやすい場面があります。

## 互換性

**ERC7699**は、既存の**ERC20**と完全に後方互換（backwards compatible）です。
後方互換とは、既存の仕組みや既存の利用方法を壊さずに、新しい機能を追加できる性質です。

#### 既存の transfer / transferFrom と共存できる理由

**ERC7699**は、既存の**ERC20**の `transfer` / `transferFrom` を置き換えるのではなく、「**参照情報付きの別バージョン**」を追加します。
そのため、従来通りの送金も引き続き使えますし、必要なときだけ参照情報付き送金を使えます。

#### アップグレード可能なERC20への影響

**ERC7699**は、既存のアップグレード可能（upgradeable）な**ERC20**トークンにも組み込みやすいように設計されています。
具体的には、ストレージレイアウト（storage layout）に影響を与えずに新機能を追加できると説明されています。

ストレージレイアウトとは、コントラクトの状態変数がどのスロットに格納されるかという配置のことです。
アップグレード可能な設計では、この配置が崩れると既存データが壊れる危険があります。
**ERC7699**はその点に影響しないため、アップグレードで追加しやすいという位置付けです。

#### 新規ERC20トークンでの採用

新しく作る**ERC20**トークンは、用途に応じて参照情報機能を採用するかどうかを選べます。
つまり、必須の機能として押し付けるのではなく、「**支払い参照が必要なトークンだけが実装すればよい**」という柔軟な考え方です。

#### 仕様の意図が分かる対応表

| 観点  | **ERC7699**の判断      | ねらい                |
| -------- | ------------------- | ------------------ |
| 追加パラメータ名 | `transferReference` | 銀行の「**支払い参照情報**」と同じ感覚で理解できるようにする             |
| 追加パラメータ型 | `bytes`  | 短文にも構造化データにも対応できるようにする                   |
| イベントの型   | `bytes32`           | ログのフィルタリング事情と整合させつつ、32バイト以内なら可読な参照も載せられる |
| 後方互換 | 完全に互換  | 既存の**ERC20**運用を壊さず段階的に導入できる              |

## 参考実装

```js:IERC7699.sol
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.4 <0.9.0;

import {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol";

interface IERC7699 {
    /**
     * @notice Emitted when a non-empty `transferReference` is added to the `transfer` call.
     */
    event TransferReference(bytes32 indexed loggedReference);

    /**
     * @notice Moves `amount` tokens from the caller's account to `to` with `transferReference`.
     *
     * @dev Returns a boolean value indicating whether the operation succeeded.
     *
     * MUST emit this ERCS's {TransferReference} event followed by a corresponding {ERC20.Transfer} event
     * (to comply with ERC-20).
     */
    function transfer(address to, uint256 amount, bytes calldata transferReference) external returns (bool);

    /**
     * @notice Moves `amount` tokens from `from` to `to` with `transferReference` using the
     * allowance mechanism. `amount` is then deducted from the caller's allowance.
     *
     * @dev Returns a boolean value indicating whether the operation succeeded.
     *
     * MUST emit this ERCS's {TransferReference} event followed by a corresponding {ERC20.Transfer} event
     * (to comply with ERC-20).
     */
    function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference)
        external
        returns (bool);
}

/**
 * @dev Implementation of the ERC20 transfer reference extension.
 */
contract ERC20TransferReference is ERC20, IERC7699 {
    constructor() ERC20("ERC20 Transfer Reference Example", "TXRE") {
        _mint(msg.sender, 987654321 * 1e18);
    }

    /**
     * @dev Emits `TransferReference` event with derived `loggedReference` data
     */
    function _logReference(bytes calldata transferReference) internal virtual {
        // MUST NOT emit when transferReference is empty
        if (transferReference.length > 0) {
            // Effectively extract first 32 bytes from transferReference calldata bytes
            // Note: This is the example. Derivation of the loggedReference is fully up to the implementation.
            // E.g. keccak hash of the whole transferReference, etc.
            bytes32 loggedReference;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                loggedReference := calldataload(transferReference.offset)
            }

            emit TransferReference(loggedReference);
        }
    }

    /**
     * @notice A standard ERC20 token transfer with an optional transfer reference
     * @dev The underlying `transfer` function is assumed to handle the actual token transfer logic.
     * @param to The address of the recipient where the tokens will be sent.
     * @param amount The number of tokens to be transferred.
     * @param transferReference A bytes field to include a transfer reference, reference signature or other relevant
     * reference data.
     * @return A boolean indicating whether the transfer was successful.
     */
    function transfer(address to, uint256 amount, bytes calldata transferReference) public virtual returns (bool) {
        _logReference(transferReference);

        // ERC20.Transfer event is emitted immediately after TransferReference event
        return transfer(to, amount);
    }

    /**
     * @notice A delegated token transfer with an optional transfer reference
     * @dev Requires prior approval from the token owner. The underlying `transferFrom` function is assumed to handle
     * allowance and transfer logic.
     * @param from The address of the token owner who has authorized the transfer.
     * @param to The address of the recipient where the tokens will be sent.
     * @param amount The number of tokens to be transferred.
     * @param transferReference A bytes field to include a transfer reference, reference signature or other relevant
     * reference data.
     * @return A boolean indicating whether the transfer was successful.
     */
    function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference)
        public
        virtual
        returns (bool)
    {
        _logReference(transferReference);

        // ERC20.Transfer event is emitted immediately after TransferReference event
        return transferFrom(from, to, amount);
    }
}
```

## セキュリティ

### プライバシーに関する考慮事項

#### 参照データのプライバシー

**ERC7699**で送金参照情報を含めると、取引そのものや関係者についてのセンシティブな情報が外部に見える可能性があります。
ここでの重要点は、参照情報が「**オンチェーンに残り公開される**」前提で扱われることです。

そのため、実装者と利用者は、参照情報に機微情報が含まれていないかを慎重に考える必要があります。
機微情報の例としては、「**請求書番号から顧客や取引状況が推測できる**」、「**支払い用途が特定される**」などが該当します。

元文では、このリスクを下げる方法として、参照情報データを保護するために暗号化などのプライバシー強化技術を検討できる、とされています。
ここでの「**暗号化**」は、参照情報をそのまま読める形で残さず、第三者が内容を推測しにくくする工夫のことです。

##### 例（公開ログから意味が推測されるケース）

参照情報として `0x20240002` がログに残ると、その値の意味が推測されてしまう場合があります。

元文の例では、この参照が公開されることで「**受取人にとって2024年の2件目の請求書に関係する支払い**」であることが外部に見えてしまいます。
つまり、参照情報が短くても、運用ルール次第では取引の文脈が漏れます。

### 支払い参照情報の改ざん・悪用

#### 参照データの検証が標準で定義されていない

**ERC7699**は、参照情報の内容に対して「**この形式であるべき**」、「**この値は正しいべき**」といった検証ルールを定めていません。
そのため、攻撃者が参照情報を悪意ある内容にして送金することで、利用者・加盟店・サービス提供者を誤解させる可能性があります。

#### 起こり得る問題

元文では、参照情報の悪用により発生し得る影響として、以下の2つを挙げています。
内容を省略せずに整理すると以下です。

| リスク種別  | どういうことが起こるか | 受取側（beneficiary）への影響|
| ------- | ------- | --------------- |
| 法的リスク（Legal risks）          | 攻撃者が不正資金を使い、参照情報を使って受取側を「**関与者**」に見せかける、または不正行為（マネーロンダリング等）に関与しているように見える状況を作る | 法務・コンプライアンス上のリスクが増え、疑いをかけられる、調査対応が必要になる可能性がある |
| 紛争・返金（Disputes and refunds） | 送金後に「**自分は支払っていない**」と利用者が気付き、返金要求や異議申し立て（dispute）を行う                          | 受取側の事務作業が増え、対応コストが発生する     |

### 緩和策

元文では、リスクを下げるために実装者が検討できる方向性として、以下の考え方を示しています。

* 正しい送金者を識別できる方法を用意する
* 一意で検証可能な参照情報を生成する仕組みを検討する

## 引用

Radek Svarz (@radeksvarz), "ERC-7699: ERC-20 Transfer Reference Extension [DRAFT]," Ethereum Improvement Proposals, no. 7699, April 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7699.

## 最後に

今回は「ERC20送金に「**何の支払いか**」を示す参照情報を付与できる仕組みを提案している**ERC7699**」についてまとめてきました！
いかがだったでしょうか？

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

[Twitter @cardene777](https://twitter.com/cardene777)

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

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
