[ERC4393] NFTにERC20形式の少額の報酬を送る仕組みを理解しよう！

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、**ERC721**や**ERC1155**のNFTに**ERC20**形式のチップ（少額の報酬）を送る仕組みを提案している**ERC4393**についてまとめていきます！

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

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

他にも様々なEIPについてまとめています。

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

# 概要

**ERC4393**は、**ERC721**（NFT）および**ERC1155**（マルチトークン）に対してチップ（少額の報酬）を送るコントラクトインターフェースを定義しています。
チップは、**ERC20**トークンとしてNFTやマルチトークンの保有者（コントローラー）が引き出すことができます。

ここでいう「チップ」とは、主に少額のマイクロペイメントを指し、特定のNFTやマルチトークンに対して送信される報酬のことを意味します。
また、「保有者（controller）」という表現は、デジタル資産に限らずサービスなどの非デジタル資産を表現するNFTのケースにも対応できるように、より一般的な用語として用いられています。

# 動機

**ERC4393**の主な目的は、NFTやマルチトークンに対して安価にチップを送れる仕組みを提供することです。
そのために、ガスコストを最適化したチップ用のトークンコントラクトを定義し、`tipBatch`という関数を使って一括でチップを送る機能を備えています。

さらに、このインターフェースは極力シンプルに設計されており、多様なユースケースに容易に組み込めるようになっています。
例えば、以下のような用途が想定されています。

- ゲーム内アイテムや仮想商品への支払い
- 投稿、音楽、動画へのチップ
- 寄付やクラウドファンディング
- 著作権収益の分配
- クリックごとの報酬広告
- サービス利用へのインセンティブ付与
- ポイントカードやクーポンのような報酬システム

これら全てのユースケースにおいて、ブロックチェーンの「セキュリティ」「即時性」「透明性」といった特徴が活かされます。

# 仕様

**ERC4393**では、**ITipToken**インターフェースの実装により、NFT（**ERC721**）およびマルチトークン（**ERC1155**）へのチップ（少額報酬）を送信するための汎用的な方法を定義しています。
インターフェースは、可能な限り多くのユースケースに適用できるように最小限の実装にとどめています。

コントラクトが**ITipToken**を実装する時には、以下の条件を満たす必要があります。

- インターフェース内で定義された全ての関数を実装する。
- 全てのイベントを正しく発行する。
- `supportsInterface`（**ERC165**）関数を実装し、`0xE47A7022`が引数に渡された場合に `true` を返す。

なお、コード中の`nft`という語は、**ERC1155**における代替可能トークンを含む場合があります。

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

https://qiita.com/cardene/items/541184977a5cba51b759

## インターフェース

```js:ITipToken.sol
interface ITipToken {
    /**
        @dev This emits when the tip token implementation approves the address
        of an NFT for tipping.
        The holders of the 'nft' are approved to receive rewards.
        When an NFT Transfer event emits, this also indicates that the approved
        addresses for that NFT (if any) is reset to none.
        Note: the ERC-165 identifier for this interface is 0x985A3267.
    */
    event ApprovalForNFT(
        address[] holders,
        address indexed nft,
        uint256 indexed id,
        bool approved
    );

    /**
        @dev This emits when a user has deposited an ERC-20 compatible token to
        the tip token's contract address or to an external address.
        This also indicates that the deposit has been exchanged for an
        amount of tip tokens
    */
    event Deposit(
        address indexed user,
        address indexed rewardToken,
        uint256 amount,
        uint256 tipTokenAmount
    );

    /**
        @dev This emits when a holder withdraws an amount of ERC-20 compatible
        reward. This reward comes from the tip token's contract address or from
        an external address, depending on the tip token implementation
    */
    event WithdrawReward(
        address indexed holder,
        address indexed rewardToken,
        uint256 amount
    );

    /**
        @dev This emits when the tip token constructor or initialize method is
        executed.
        Importantly the ERC-20 compatible token 'rewardToken_' to use as reward
        to NFT holders is set at this time and remains the same throughout the
        lifetime of the tip token contract.
        The 'rewardToken_' and 'tipToken_' MAY be the same.
    */
    event InitializeTipToken(
        address indexed tipToken_,
        address indexed rewardToken_,
        address owner_
    );

    /**
        @dev This emits every time a user tips an NFT holder.
        Also includes the reward token address and the reward token amount that
        will be held pending until the holder withdraws the reward tokens.
    */
    event Tip(
        address indexed user,
        address[] holder,
        address indexed nft,
        uint256 id,
        uint256 amount,
        address rewardToken,
        uint256[] rewardTokenAmount
    );

    /**
        @notice Enable or disable approval for tipping for a single NFT held
        by a holder or a multi token shared by holders
        @dev MUST revert if calling nft's supportsInterface does not return
        true for either IERC721 or IERC1155.
        MUST revert if any of the 'holders' is the zero address.
        MUST revert if 'nft' has not approved the tip token contract address as operator.
        MUST emit the 'ApprovalForNFT' event to reflect approval or not approval.
        @param holders The holders of the NFT (NFT controllers)
        @param nft The NFT contract address
        @param id The NFT token id
        @param approved True if the 'holder' is approved, false to revoke approval
    */
    function setApprovalForNFT(
        address[] calldata holders,
        address nft,
        uint256 id,
        bool approved
    ) external;

    /**
        @notice Checks if 'holder' and 'nft' with token 'id' have been approved
        by setApprovalForNFT
        @dev This does not check that the holder of the NFT has changed. That is
        left to the implementer to detect events for change of ownership and to
        take appropriate action
        @param holder The holder of the NFT (NFT controller)
        @param nft The NFT contract address
        @param id The NFT token id
        @return True if 'holder' and 'nft' with token 'id' have previously been
        approved by the tip token contract
    */
    function isApprovalForNFT(
        address holder,
        address nft,
        uint256 id
    ) external returns (bool);

    /**
        @notice Sends tip from msg.sender to holder of a single NFT or
        to shared holders of a multi token
        @dev If 'nft' has not been approved for tipping, MUST revert
        MUST revert if 'nft' is zero address.
        MUST burn the tip 'amount' to the 'holder' and send the reward to
        an account pending for the holder(s).
        If 'nft' is a multi token that has multiple holders then each holder
        MUST receive tip amount in proportion of their balance of multi tokens
        MUST emit the 'Tip' event to reflect the amounts that msg.sender tipped
        to holder(s) of 'nft'.
        @param nft The NFT contract address
        @param id The NFT token id
        @param amount Amount of tip tokens to send to the holder of the NFT
    */
    function tip(
        address nft,
        uint256 id,
        uint256 amount
    ) external;

    /**
        @notice Sends a batch of tips to holders of 'nfts' for gas efficiency
        @dev If NFT has not been approved for tipping, revert
        MUST revert if the input arguments lengths are not all the same
        MUST revert if any of the user addresses are zero
        MUST revert the whole batch if there are any errors
        MUST emit the 'Tip' events so that the state of the amounts sent to
        each holder and for which nft and from whom, can be reconstructed.
        @param users User accounts to tip from
        @param nfts The NFT contract addresses whose holders to tip to
        @param ids The NFT token ids that uniquely identifies the 'nfts'
        @param amounts Amount of tip tokens to send to the holders of the NFTs
    */
    function tipBatch(
        address[] calldata users,
        address[] calldata nfts,
        uint256[] calldata ids,
        uint256[] calldata amounts
    ) external;

    /**
        @notice Deposit an ERC-20 compatible token in exchange for tip tokens
        @dev The price of tip tokens can be different for each deposit as
        the amount of reward token sent ultimately is a ratio of the
        amount of tip tokens to tip over the user's tip tokens balance available
        multiplied by the user's deposit balance.
        The deposited tokens can be held in the tip tokens contract account or
        in an external escrow. This will depend on the tip token implementation.
        Each tip token contract MUST handle only one type of ERC-20 compatible
        reward for deposits.
        This token address SHOULD be passed in to the tip token constructor or
        initialize method. SHOULD revert if ERC-20 reward for deposits is
        zero address.
        MUST emit the 'Deposit' event that shows the user, deposited token details
        and amount of tip tokens minted in exchange
        @param user The user account
        @param amount Amount of ERC-20 token to deposit in exchange for tip tokens.
        This deposit is to be used later as the reward token
    */
    function deposit(address user, uint256 amount) external payable;

    /**
        @notice An NFT holder can withdraw their tips as an ERC-20 compatible
        reward at a time of their choosing
        @dev MUST revert if not enough balance pending available to withdraw.
        MUST send 'amount' to msg.sender account (the holder)
        MUST reduce the balance of reward tokens pending by the 'amount' withdrawn.
        MUST emit the 'WithdrawReward' event to show the holder who withdrew, the reward
        token address and 'amount'
        @param amount Amount of ERC-20 token to withdraw as a reward
    */
    function withdrawReward(uint256 amount) external payable;

    /**
        @notice MUST have identical behaviour to ERC-20 balanceOf and is the amount
        of tip tokens held by 'user'
        @param user The user account
        @return The balance of tip tokens held by user
    */
    function balanceOf(address user) external view returns (uint256);

    /**
        @notice The balance of deposit available to become rewards when
        user sends the tips
        @param user The user account
        @return The remaining balance of the ERC-20 compatible token deposited
    */
    function balanceDepositOf(address user) external view returns (uint256);

    /**
        @notice The amount of reward token owed to 'holder'
        @dev The pending tokens can come from the tip token contract account
        or from an external escrow, depending on tip token implementation
        @param holder The holder of NFT(s) (NFT controller)
        @return The amount of reward tokens owed to the holder from tipping
    */
    function rewardPendingOf(address holder) external view returns (uint256);
}
```

### イベント

#### `ApprovalForNFT`

```js
event ApprovalForNFT(
    address[] holders,
    address indexed nft,
    uint256 indexed id,
    bool approved
);
```

NFTまたはマルチトークンに対してチップ受け取りの承認が行われた時に発行されるイベント。
このイベントは、NFTコントラクトと特定のトークンIDに対して、指定された保有者（コントローラー）がチップを受け取る権利を持つことが承認または取り消された時に発行されます。
NFTの保有者が変わると、承認状態はリセットされます。

**パラメータ**  
- `holders`  
    - チップの受け取りを承認されたNFTの保有者アドレス配列。  
- `nft`  
    - 対象の**ERC721**または**ERC1155**トークンのコントラクトアドレス。  
- `id`  
    - 対象のトークンID。  
- `approved`  
    - 承認されたかどうかの真偽値（`true`で承認、`false`で取り消し）。  

#### `Deposit`

```js
event Deposit(
    address indexed user,
    address indexed rewardToken,
    uint256 amount,
    uint256 tipTokenAmount
);
```

ユーザーが報酬トークンを預け入れ、チップトークンと交換した時に発行されるイベント。
このイベントは、ユーザーが**ERC20**互換トークンをコントラクトにデポジットした時に発行され、対応する量のチップトークンが発行されたことを示します。

**パラメータ**  
- `user`  
    - デポジットしたユーザーのアドレス。  
- `rewardToken`  
    - 預け入れに使用された**ERC20**互換トークンのアドレス。  
- `amount`  
    - 預け入れた**ERC20**トークンの量。  
- `tipTokenAmount`  
    - 発行されたチップトークンの量。  

#### `WithdrawReward`

```js
event WithdrawReward(
    address indexed holder,
    address indexed rewardToken,
    uint256 amount
);
```

NFT保有者が報酬トークンを引き出した時に発行されるイベント。
NFTまたはマルチトークン保有者が、自分に保留されている報酬（**ERC20**トークン）を引き出したことを通知します。

**パラメータ**  
- `holder`  
    - 報酬を引き出したNFT保有者のアドレス。  
- `rewardToken`  
    - 報酬として支払われる**ERC20**互換トークンのアドレス。  
- `amount`  
    - 引き出された報酬トークンの量。  

#### `InitializeTipToken`

```js
event InitializeTipToken(
    address indexed tipToken_,
    address indexed rewardToken_,
    address owner_
);
```

チップトークンコントラクトの初期化時に発行されるイベント。
コントラクトの初期化時に、チップトークンと報酬トークンのアドレス、およびオーナーが設定されたことを示します。

**パラメータ**  
- `tipToken_`  
    - チップトークンのアドレス。  
- `rewardToken_`  
    - 報酬に使われる**ERC20**互換トークンのアドレス。  
- `owner_`  
    - コントラクトのオーナーのアドレス。  

#### `Tip`

```js
event Tip(
    address indexed user,
    address[] holder,
    address indexed nft,
    uint256 id,
    uint256 amount,
    address rewardToken,
    uint256[] rewardTokenAmount
);
```

ユーザーがNFTまたはマルチトークンの保有者にチップを送信した時に発行されるイベント。
このイベントは、ユーザーがチップトークンを使用してNFT保有者へ報酬を送ったことを記録します。
複数保有者が存在する場合、それぞれに分配される報酬額も記録されます。

**パラメータ**  
- `user`  
    - チップを送ったユーザーのアドレス。  
- `holder`  
    - 報酬を受け取るNFT保有者のアドレス配列。  
- `nft`  
    - 対象のNFTコントラクトアドレス。  
- `id`  
    - トークンID。  
- `amount`  
    - チップトークンの送信量。  
- `rewardToken`  
	- 報酬として使用される**ERC20**互換トークンのアドレス。  
- `rewardTokenAmount`  
	- 各保有者に送信される報酬トークンの量。  

### 関数

### `setApprovalForNFT`

```js
function setApprovalForNFT(
    address[] calldata holders,
    address nft,
    uint256 id,
    bool approved
) external;
```

NFTまたはマルチトークンに対して、指定した保有者がチップを受け取れるように承認または承認解除する関数。
指定したNFT（**ERC721**または**ERC1155**）とそのトークンIDに対して、チップを受け取る対象となる保有者を設定します。
コントラクトがオペレーターとして承認されていない場合や、ゼロアドレスが含まれる場合には処理を拒否します。
設定後は `ApprovalForNFT` イベントが発行されます。

**引数**
- `holders`  
    - NFTやマルチトークンの報酬受取対象として承認する保有者アドレスの配列。  
- `nft`  
    - 対象のNFTまたはマルチトークンのコントラクトアドレス。  
- `id`  
	- 対象トークンのID。  
- `approved`  
	- `true`で承認、`false`で取り消し。  

#### `isApprovalForNFT`

```javascript
function isApprovalForNFT(
    address holder,
    address nft,
    uint256 id
) external returns (bool);
```

指定されたNFTの保有者がチップ受け取りの承認状態か確認する関数。
過去に `setApprovalForNFT` 関数で承認されたかどうかを返します。
ただし、NFTの所有者が既に変更されている可能性には注意が必要です。

**引数**
- `holder`  
	- 確認対象となるNFTの保有者アドレス。  
- `nft`  
	- NFTまたはマルチトークンのコントラクトアドレス。  
- `id`  
	- 対象のトークンID。  

**戻り値**
- `bool`  
	- `true`で承認済み、`false`で未承認。  

#### `tip`

```js
function tip(
    address nft,
    uint256 id,
    uint256 amount
) external;
```

NFTまたはマルチトークンの保有者に対してチップを送る関数。
指定されたトークンに対してチップトークンを送信し、報酬トークンが保有者に割り当てられます。
対象のNFTが承認されていない場合は`revert`します。
トークンが**ERC1155**であり複数保有者がいる場合、それぞれの保有割合に応じて分配されます。

**引数**
- `nft`  
	- チップの対象となるNFTまたはマルチトークンのコントラクトアドレス。  
- `id`  
	- 対象のトークンID。  
- `amount`  
	- 送信するチップトークンの量。  

#### `tipBatch`

```js
function tipBatch(
    address[] calldata users,
    address[] calldata nfts,
    uint256[] calldata ids,
    uint256[] calldata amounts
) external;
```

複数のユーザー・NFTに対して一括でチップを送る関数。
一度のトランザクションで複数のチップ送信をまとめて処理します。
全ての配列の長さが一致している必要があります。
どれか一つでも無効なデータが含まれていた場合、バッチ全体を`revert`します。
各チップ送信について `Tip` イベントが発行されます。

**引数**
- `users`  
	- チップを送信するユーザーのアドレス配列。  
- `nfts`  
	- チップ送信先のNFTまたはマルチトークンのコントラクトアドレス配列。  
- `ids`  
	- 各NFTまたはマルチトークンのトークンID配列。  
- `amounts`  
	- 各チップの送信量の配列。  

### `deposit`

```js
function deposit(address user, uint256 amount) external payable;
```

ユーザーが報酬トークン（**ERC20**互換）を預け入れ、チップトークンを受け取る関数。
ユーザーはこの関数を通じて、報酬として使用されるトークンを預け入れます。
チップトークンは預け入れた量に基づいて発行されます。
デポジットに使用できるトークンの種類はコントラクトの初期化時に指定されており1種類のみを扱います。

**引数**
- `user`  
	- 預け入れを行うユーザーのアドレス。  
- `amount`  
	- 預け入れる報酬トークンの量。  

#### `withdrawReward`

```js
function withdrawReward(uint256 amount) external payable;
```

NFT保有者が報酬トークンを引き出す関数。
ユーザーが保留中の報酬トークンを引き出す時に使用します。
残高が不足している場合は`revert`します。
引き出し後は、該当分の保留報酬を減算します。

**引数**
- `amount`  
	- 引き出す報酬トークンの量。  

#### `balanceOf`

```js
function balanceOf(address user) external view returns (uint256);
```

指定したユーザーのチップトークン残高を返す関数。
**ERC20**の `balanceOf` と同様に動作します。
ユーザーが保有しているチップトークンの量を取得できます。

**引数**
- `user`  
	- 残高を取得するユーザーのアドレス。  

**戻り値**
- `uint256`  
	- ユーザーのチップトークン残高。  

#### `balanceDepositOf`

```js
function balanceDepositOf(address user) external view returns (uint256);
```

ユーザーがデポジットした報酬トークンの残高を取得する関数。
報酬として割り当てるために残っている、ユーザーの**ERC20**トークンの預け入れ残高を返します。

**引数**
- `user`  
	- 対象のユーザーアドレス。  

**戻り値**
- `uint256`  
	- 残っている報酬用デポジット残高。  

#### `rewardPendingOf`

```js
function rewardPendingOf(address holder) external view returns (uint256);
```

NFT保有者に対して保留中の報酬トークン量を取得する関数。
NFTやマルチトークンを保有するアカウントに対して、まだ引き出されていない報酬トークンの総量を返します。

**引数**
- `holder`  
	- NFTの保有者アドレス。  

**戻り値**
- `uint256`  
	- 保留中の報酬トークンの量。  

## チップと報酬の流れ

ユーザーは、対応する**ERC20**トークンをデポジット（預け入れ）し、代わりに「**チップトークン**」を受け取ります。
このチップトークンを、特定のNFTやマルチトークンに送信することで、最終的にそのNFTの保有者が**ERC20**の報酬を引き出せるようになります（コントラクト内に保持されたデポジットから支払われます）。

## チップトークンの`transfer`と価値計算

チップトークンと**ERC20**のデポジットは交換可能です。
実装者は、チップトークンのレート（価格）を自由に決定できます。
例えば、地域ごとに固定のレートを設定することで、富裕層と貧困層の間で評価のバイアスを防ぐことが可能です。

チップ送信時には、以下の手順を踏みます。

- ユーザーのチップトークンがBurnされる。
- 同等の**ERC20**価値が、該当するNFTまたはマルチトークンの保有者の「**保留中報酬アカウント**」に割り当てられる。

計算式は以下の通りです。

```
ERC20報酬 = (ユーザーのERC20総残高 × チップ額) ÷ ユーザーのチップトークン総残高
```

つまり、ユーザーに無料でチップを配布した場合、報酬価値は希薄化します。

## ERC20との互換性

- チップトークンが**ERC20**を継承している場合は、ユーザー間で直接送信が可能です。
- 継承していない場合は、`mint`や`burn`に応じて、`Transfer`イベントを適切に発行する必要があります（**ERC20**と同じシグネチャであることが必須）。

## ロイヤリティ（報酬）分配の方法

**ERC1155**では、1つのトークンIDに複数の保有者が存在する可能性があります。
その場合、保有バランスに応じてチップが分配されます。
例えば、バランスが「A：25、B：75」の場合、Aが25%、Bが75%の報酬を得ます。

実装例のマッピング構造。

```js
mapping(address => mapping(uint256 => address[])) private _tokenIdToHolders;
mapping(address => uint256) private _depositBalances;
mapping(address => uint256) private _rewardsPending;
```

これにより、**ERC721**の単一保有者ケースと**ERC1155**の複数保有者ケースの両方に対応できます。

## 注意点

チップトークンコントラクトは1種類の**ERC20**トークンのみをサポートします。
複数の**ERC20**をサポートしたい場合は、トークンごとに別の**ITipToken**コントラクトをデプロイする必要があります。

チップ報酬用の**ERC20**トークンは、ホルダーの引き出し時に利用可能である必要があります。

推奨される方法は、コントラクト内にロックしておくことです。

実際の**ERC20**トークンが手数料などで送信量を減らす可能性があるため、実際の着金額を確認して内部計算と一致させることが推奨されます。

デポジットやチップトークンの払い戻し機能（`refund`）は**ERC4393**では定義されておらず、実装者に任されています。

## ガスコストの最小化

チップをオフチェーンでキャッシュし、`tipBatch`でまとめて送信することで、トランザクションの初期化コストを抑えることができます。
同一ユーザーが同一NFTに複数回チップを送る場合、それらを合算して1回で送信することでさらなる節約が可能です。
また、同一NFTに対して複数のユーザーがチップを送る場合、NFT検証などの処理をグルーピングして最小化できます。

こうした最適化は自由に実装できますが、チップ送信元と送信先、対象のNFT情報が`Tip`および`TipBatch`イベントから復元可能である必要があります。

# 補足

## シンプルなインターフェース設計

`ITipToken` インターフェースは、必要最小限の関数に絞って構成されており、汎用的かつ実用的な設計がなされています。  
これは、NFT保有者へのマイクロペイメントという基本機能を満たしながらも、さまざまな用途に柔軟に適用できるようにするためです。
実装を最低限にすることで、実装者にとっても扱いやすくセキュリティやガスコストの観点からもメリットが大きいです。

## NFTを用いた価値のアンカー化

NFTはアドレスとトークンIDによってブロックチェーン上で一意に識別されるデジタル資産です。  
NFTのハッシュ値がブロックチェーン上に記録されていることにより、改ざんが極めて困難であり、デジタルアートやライセンス、チケット、サービス契約などの真正性証明のアンカーとして活用することが可能になります。

このようにして、NFTは特定のデジタルまたは非デジタル資産と結びついた所有権や使用権を表現でき、その保有者に対して簡単かつ安全にチップ（報酬）を送る手段として機能します。

## 新しいビジネスモデルの創出

例えば音楽業界では、従来CDなどの物理媒体からストリーミングサービスへと移行する過程で、大手企業による中央集権的な収益配分モデルが構築されました。  
こうしたモデルでは、定額制サブスクリプションの料金から音楽制作者への報酬が固定的に決定されます。

`ITipToken` を用いることで、ファンが気に入った楽曲（NFTで表現される）に直接チップを送ることができ、その報酬は制作者に**ERC20**トークンとして直接届くようになります。  
これにより、ファン主導の音楽産業が可能となり、コンテンツ制作者との距離が縮まり新たな収益源の創出が期待されます。

同様の仕組みは他業界にも応用可能で、現在のような収益支配型の構造から支援型・仲介支援型のモデルへと転換を促すことができます。

## 監査可能な履歴の保証

現在、多くのdAppsは、データの管理や検索に従来型のデータベースやブロックチェーンエクスプローラーAPIを利用しています。  
しかし、**ERC4393**ではコントラクトから発行されるイベントログだけで、全てのチップと報酬トークンの状態を完全に再構築可能であることが保証されています。

つまり、外部のデータベースや探索ツールは、イベントログをもとにチップトークンや報酬トークンの送信履歴を分類・検索可能にするだけでよく、これにより透明性と検証性が確保されます。  
これは、オンチェーンイベントのみで正確な台帳を構築できる保証を意味し、より信頼性の高いアプリケーション設計が可能になります。

# 互換性

## ERC20との互換性

チップトークンは、**ERC20**に準拠させることで、通常のトークンのように他のユーザーへ直接送信する機能（`transfer` など）を持たせることが可能です。  
例えば、OpenZeppelinの**ERC20**実装を継承した場合、チップトークンの `balanceOf` 関数は親コントラクトの `balanceOf` をそのまま呼び出すだけで済みます。

このようにすれば、既存のウォレットやインフラとの互換性を保ちつつ、チップトークンとしての機能を実装することができます。

## 未対応の機能

**ERC4393**では、他人のチップを代理で使うためのspender機能（`approve` や `transferFrom`）は導入されていません。
これは、チップという性質上、他人による移動の必要性が低いと想定されているためです。

また、名前ベースでチップトークンを識別するような二次市場での取引などはこの標準の対象外となっており、必要に応じて実装者が独自に対応する前提です。

# セキュリティ

## デポジットの取り扱い

ユーザーが預けた報酬用トークン（**ERC20**）は、チップトークンコントラクト内、または信頼できる外部エスクローにロックされていることが推奨されます。  
これらのトークンはあくまでNFT保有者への報酬支払い専用であり、他の用途に流用されるべきではありません。

ただし、これは強制ではなく、コントラクト実装者の責任に任されています。
そのため、ユーザーが報酬を引き出す時には、確実に引き出し可能な状態にしておくことが義務付けられています。

## チップの送信前に必要な承認

ユーザーがNFTにチップを送信する前に、そのNFTの保有者がチップトークンコントラクトを通じたチップの受け取りを承認している必要があります。  
**ERC4393**では、チップの受け取りによって得られる報酬は、必ずNFT保有者が受け取るべきものと定義されています。

そのため、報酬の流れが明確にコード上で可視化されていることが望ましく、不透明な実装や混乱を招くようなロジックは避けるべきです。  
また、手数料を徴収する場合は事前にユーザーに対して明示されるべきです。

## 悪意ある実装者への対策

悪質な実装者が、チップとして送られた資金を横取りするような仕組みを設けるリスクも考慮されています。  
しかし、チップ送信は頻繁に行われる性質のものであり、不正が行われた場合は比較的早期に検知されやすいという特性があります。  
したがって、ユーザーと監視者がイベントログを確認することで、不正実装の発見・対応が可能になります。

# 引用

Jules Lai (@julesl23), "ERC-4393: Micropayments for NFTs and Multi Tokens [DRAFT]," Ethereum Improvement Proposals, no. 4393, October 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4393.

# 最後に

今回は「**ERC721**や**ERC1155**のNFTに**ERC20**形式のチップ（少額の報酬）を送る仕組みを提案している**ERC4393**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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