こんにちは、ブロックチェーンチームの id:charines です。 今回は ERC-721 コントラクト(NFT コントラクト)にメタトランザクションを導入した開発事例について紹介します。 主にブロックチェーンに関する開発者の方を対象とした内容になります。
メタトランザクションの導入理由
メタトランザクションとは、トランザクションの実行に必要なガス代を実行者ではない第三者が払うシステムです。 これによりトランザクション実行者は ETH を保持する必要がなくなり、 DApps を利用する障壁の 1 つを取り除くことができます。
弊チームでメタトランザクションを導入したい具体的な目的は主に 2 つです。
1. マーケットプレイスのユーザが NFT を出品しやすい
弊チームが提供していた NFT マーケットプレイス「ユニマ」では、ユーザが NFT を出品する機能は設けていませんでしたが、新機能として所有する NFT の二次販売を可能にする予定でした。
ブロックチェーンに詳しくないユーザにも使いやすいというのがユニマのコンセプトの 1 つでもあるため、出品のハードルを下げるためにガス代の肩代わりは必須でした。
2. NFT クリエイターがコントラクトを管理しやすい
マーケットプレイスのユーザと同様に、NFT を作成・販売するクリエイターも必ずしもブロックチェーンに詳しいわけではありません。
ERC-721 コントラクトには mint や pause 、メタデータ URI の変更などコントラクト管理のための関数を実装していますが、それらの管理をクリエイターがガス代なしで行えるようにすることも、販売のハードルを下げるために重要でした。
実装方針
メタトランザクションの実装方針として大きく 2 つの方針を検討しました。
1 つ目は機能ごとにメタトランザクション用の関数を実装することです。
例えば ERC-2612 は FT である ERC-20 において approve
関数のメタトランザクションを可能にする permit
関数を定義しています。
approve
は自身の持つ FT を移動させる許可を与える関数ですから、 transferFrom
など特定のアカウントの FT を移動させる関数を組み合わせることで transfer
のメタトランザクションと同等のことを可能にします。
ERC-721 においても approve
が存在するので同じ仕組みの実装が可能です。
しかしこの方法では、複数の機能についてメタトランザクションを可能にしようとすると、それぞれに対応する関数の実装が必要になります。 そこで 2 つ目の方針として、 ERC-2771 に基づいたメタトランザクションの仕組みの実装を検討しました。
ERC-2771 はフォワーダーと呼ばれる信頼できるコントラクトが署名を検証し、受信者(今回は ERC-721 コントラクト)はフォワーダーからのメタトランザクションを受け入れるというプロトコルです。 この方法では機能ごとに新しい関数を実装する必要がない上、Gas Station Network (GSN) で既に広く利用されている実績もあったため、今回はこの方法を採用することにしました。
実装
フォワーダー
フォワーダーは前述の通り、署名を検証し受信者にリクエストを転送するコントラクトですが、この部分には独自実装が必要ないため新規実装は行わずに既存のフォワーダーを利用する方針としました。
具体的には GSN で Ethereum と Polygon チェーン上に展開されているコントラクトを使用します。これらの実装は Etherscan で見られるため、信頼に足る実装であることも確認できます。
ERC-721
GSN のフォワーダーを利用するのに合わせて、こちらも GSN の実装を利用します。 BaseRelayRecipient
でメタトランザクションに必要な _msgData()
と _msgSender()
が実装されているのでこれを継承します。
import "@opengsn/contracts/src/BaseRelayRecipient.sol";
また弊チームで開発しているコントラクトは元々 OpenZeppelin をベースにしています。
こちらにも _msgData()
と _msgSender()
を含む Context
の継承が含まれており多重継承となるため、override を明記する必要があります。
function _msgData() internal view override(ContextUpgradeable, BaseRelayRecipient) returns (bytes calldata ret) { return BaseRelayRecipient._msgData(); } function _msgSender() internal view override(ContextUpgradeable, BaseRelayRecipient) returns (address ret) { return BaseRelayRecipient._msgSender(); }
最後に、メタトランザクションを行うには信頼できるフォワーダーを事前に知っておく必要があるため、コンストラクタで _setTrustedForwarder()
を呼び出すようにすれば実装は完了です。
まとめ
ERC-721 コントラクトにメタトランザクションを導入する上での技術選定や実装の流れについて紹介しました。 特に
- ERC-2771 によって任意の関数をメタトランザクションとして実行可能にした
- GSN の実装や展開済みのコントラクトを利用することで工数を抑えて開発できた
の 2 つが今回のポイントです。
弊社は 4 月 1 日を以ってブロックチェーン事業を撤退することとなりましたが、この記事やこれまでの発信が、今後ブロックチェーン技術を活用する開発者の方々の助けになれば幸いです。