int artの「the metro」で用いられているscripty.solの概要とその使い方、そしてp5.jsを使ったNFTの開発方法について解説

こんにちは、フルオンチェーンNFTクリエイターのnawooです。

nawoo

今回は、フルオンチェーンNFTを作成するときに役立つライブラリ「scripty.sol」について解説します。

前回の記事で、the metroというインタラクティブなフルオンチェーンNFTコレクションについて解説しましたが、そこで用いられているgenerativeアート作成のためのオンチェーンツールセットが、今回ご紹介する「scripty.sol」です。

scripty.solというコントラクトは、自分で作ったJavaScriptコードや、「Three.js」「p5.js」といったJavaScriptを、自由に組み合わせることができます。そして、それを使ってオンチェーンでHTMLを作成することができるのです。

scripty.solはフルオンチェーンNFTの幅を広げることができると同時に、そのようなNFTの構築を簡単にしてくれるため、web3開発者はフルオンチェーンNFTに関心があるという方は、知っておいて損はないと思います。

ということで今回は、フルオンチェーンNFTを作成するときに役立つライブラリ「scripty.sol」について、概要やその使い方などを中心に解説します。

でははじめに、この記事の構成について説明します。

STEP
scripty.solとは

まずは、scripty.solの基本的な概要について、簡潔に解説します。

STEP
scripty.solの使い方

続いて、どのような流れでscripty.solを使用すれば良いのかについて、ステップバイステップで解説します。

STEP
p5.jsを使ったNFTを作ってみよう

最後に、実際にscripty.solを使いながらp5.jsを使ったフルオンチェーンNFTを作成しつつ、全体的な流れや完成物などについて概観します。

本記事が、scripty.solの概要や使い方、p5.jsを使ったフルオンチェーンNFTの構築方法などについて理解したいと思われている方にとって、少しでもお役に立てれば幸いです。

※本記事は一般的な情報提供を目的としたものであり、法的または投資上のアドバイスとして解釈されることを意図したものではなく、また解釈されるべきではありません。ゆえに、特定のFT/NFTの購入を推奨するものではございませんので、あくまで勉強の一環としてご活用ください。

イーサリアムnaviの活動をサポートしたい方は、「定期購読プラン」をご利用ください。

目次

scripty.solとは

出典:docs.int.art/open-source-projects/scripty.sol

scripty.solは、「Three.js」「p5.js」などのJavaScriptや、自作のJavaScriptコードを自由に組み合わせて、オンチェーンでHTMLを作成することができるコントラクトです。

Three.jsやp5.jsを使ったフルオンチェーンNFTを作成するためには、多くのJavaScriptを組み合わせてHTMLを作成する必要があります。

  • Base64を扱うためのJavaScriptライブラリ
  • gzipを展開するためのJavaScriptライブラリ
  • gzip圧縮されたThree.jsやp5.js本体
  • tokenIDを定義するJavaScriptコード
  • メインのJavaScriptコード

これらのJavaScriptをHTMLにまとめて、Base64エンコードまたはURLエンコードしてからdataURLに変換し、メタデータのanimation_urlに指定する必要があります。

ところが、scripty.solを使うことで、こういった一連の作業がとても簡単になります。

nawoo

HTMLに含めたいJavaScriptをrequestsとして指定するだけで、簡単にdataURL化したHTMLを作成できるのです。

scripty.solの作者は、0xdude氏とxtremetom氏です。

先日、xtremetom氏がローンチしたCryptoCoasterというNFTコレクションや、前回の記事で取り上げたint art(0xdude氏)のフルオンチェーン×インタラクティブNFT「the metro」でも、scripty.solが使用されています。

scripty.solでは、様々なタイプのHTMLを作成することができます。

nawoo

次章では、p5.jsを使ったフルオンチェーンNFTを作成する方法を中心に解説していきます。

scripty.solの使い方

まずは、公式サイトでもp5.jsを使ったシンプルなNFTの例がありますので、概観していきます。

p5.jsを使ったexampleには、以下の2種類があります。今回は、後者②のEthFS_P5_URLSafe.solを使います。

  1. Base64エンコードを使ったEthFS_P5.sol
  2. URLエンコードを使ったEthFS_P5_URLSafe.sol
nawoo

では最初に、tokenURIを確認してみます。

メタデータはURLエンコードされているのでデコードしたところ、以下のようになっていました。

{
  "name":"p5.js Example - GZIP - Base64 - URL Safe", 
  "description":"Assembles GZIP compressed base64 encoded p5.js that's stored in ethfs's FileStore...",
  "animation_url":"data:text/html,%3Cbody%20style%3D%27margin%3A0%3B%27%3E%3Cscript%20src%3..."
}

また、animation_urlはURLエンコードされているのでこちらもデコードしてみると、<body>タグの中に4つの<script>が含まれていることがわかります。

<body style="margin: 0">
  <script src="data:text/javascript;base64,bGV0IF9zYj17fTtfc2IuZXZlbnRzPVtd..."></script>
  <script type="text/javascript+gzip" src="data:text/javascript;base64,H4sIAAAAAAAAE8S923bbWLY..."></script>
  <script src="data:text/javascript;base64,InVzZSBzdHJpY3QiOygoKT0..."></script>
  <script src="data:text/javascript;base64,ZnVuY3Rpb24gc2V0dXAoKSB7CglsZXQgZCA9IDc..."></script>
</body>
nawoo

このメタデータをどのように作成しているのか、コントラクトのtokenURI関数を確認してみましょう。

tokenURI関数では、以下3つの処理が行われています。

  1. requestsを作成
  2. bufferSizeを取得
  3. animation_url用のdataURLとメタデータを作成

1. requestsの作成

まず、HTMLに含めるJavaScriptをrequestsとして順番に指定しています。

function tokenURI(uint256) public view virtual override returns (string memory) {
  WrappedScriptRequest[] memory requests = new WrappedScriptRequest[](4);

  requests[0].name = "scriptyBase";
  requests[0].wrapType = 0; // エンコードや圧縮されていないJavaScript (raw)
  requests[0].contractAddress = scriptyStorageAddress; // ScriptyStorageから読み込み

  requests[1].name = "p5-v1.5.0.min.js.gz";
  requests[1].wrapType = 2; // gzip圧縮されたJavaScript
  requests[1].contractAddress = ethfsFileStorageAddress; // EthFSから読み込み

  requests[2].name = "gunzipScripts-0.0.1.js";
  requests[2].wrapType = 1; // Base64エンコードされたJavaScript
  requests[2].contractAddress = ethfsFileStorageAddress; // EthFSから読み込み

  requests[3].name = "pointsAndLines";
  requests[3].wrapType = 0; // エンコードや圧縮されていないJavaScript (raw)
  requests[3].contractAddress = scriptyStorageAddress; // ScriptyStorageから読み込み

  // ...略
}

requestsでは、JavaScriptの読み込み先とタイプを指定します。読み込み先は、主に以下の3パターンあります。

  1. scriptys.sol用のストレージであるScriptyStorageコントラクトから読み込む
  2. EthFSのFileStorageコントラクトから読み込む
  3. コントラクト内でコードを指定する

1と2では、contractAddressで読み込み先のコントラクトを指定し、nameでファイル名を指定します。

3は、コントラクト内でJavaScriptを動的に作成する場合に使います。

nawoo

scriptContentにコードを指定しますが、これに関しては後ほど具体例を紹介します。

次に、JavaScriptのタイプをwrapTypeで指定します。これにより、生成される<script>タグの種類が決まります。

  • 0: エンコードや圧縮されていないJavaScript (raw) → Base64エンコードしてからdataURL化してsrcに指定
  • 1: Base64エンコードされたJavaScript → そのままdataURL化してsrcに指定
  • 2: gzip圧縮されたJavaScript → type="text/javascript+gzip"を追加
  • 3: PNG圧縮されたJavaScript → type="text/javascript+png"を追加
  • 4: カスタムタイプ

つまり、上のコードではrequestsに対して、以下4つのJavaScriptを読み込むように指定されていることがわかります。

  1. ScriptyStorageから、無圧縮の"scriptyBase"を読み込む
  2. EthFSから、gzip圧縮された"p5-v1.5.0.min.js.gz"を読み込む
  3. EthFSから、Base64エンコードされた"gunzipScripts-0.0.1.js"を読み込む
  4. ScriptyStorageから、無圧縮の"pointsAndLines"を読み込む
補足: 各JavaScriptの説明

"scriptyBase"は、他のスクリプトからイベントの追加やcanvasの生成を行うための_sbオブジェクトを定義しています。(しかし、他のスクリプトから使われていないので、無くても良さそうです。)
"p5-v1.5.0.min.js.gz"は、EthFSにアップロードされているp5.jsの本体です。gzip圧縮されています。
"gunzipScripts-0.0.1.js"は、gzip展開するためのJavaScriptです。
"pointsAndLines"は、このNFT用のp5.jsのスケッチファイルです。setup関数やdraw関数を含みます。

2. bufferSizeの取得

続いて、bufferSizeを求めます。 bufferSizeは、作成されるHTMLのバイトサイズです。

scripty.solでは、最初にバッファサイズを計算して、その分をメモリ上に確保してからHTMLを作成しています。

これにより、ガス節約しながら大きなサイズのHTMLを作成できるようになっています。

EthFS_P5_URLSafe.solでは、bufferSizeをローカルPCで事前に計算しておいて、コンストラクタの引数として渡すようになっています。

nawoo

ローカルPCでbufferSizeを計算する方法は、こちらのデプロイスクリプトが参考になります。

// コンストラクタ
constructor(
    address _ethfsFileStorageAddress,
    address _scriptyStorageAddress,
    address _scriptyBuilderAddress,
    uint256 _bufferSize // ← コンストラクタの引数でbufferSizeを指定している
) ERC721("example", "EXP") {
    ethfsFileStorageAddress = _ethfsFileStorageAddress;
    scriptyStorageAddress = _scriptyStorageAddress;
    scriptyBuilderAddress = _scriptyBuilderAddress;
    bufferSize = _bufferSize;
    mint();
}

もちろん、ローカルPCではなく、コントラクト内でbufferSizeを計算することもできます。

その場合、ScriptyBuilderコントラクトのgetBufferSizeForURLSafeHTMLWrapped関数を使い、requestsを引数に渡すだけです。

// bufferSizeの計算
uint256 bufferSize = scriptyBuilder.getBufferSizeForURLSafeHTMLWrapped(requests);

今回は、URLエンコードされたHTMLを生成するので、URLエンコードされた状態のHTMLのサイズを求めます。

Base64エンコードの場合は、getBufferSizeForHTMLWrapped関数を使います。

3. animation_url用のdataURLとメタデータを作成

ここで、いよいよHTMLの作成です。

ScriptyBuilderコントラクトのgetHTMLWrappedURLSafe関数にrequestsbufferSizeを引数に渡すと、 HTMLをdataURL形式で返します。dataURLのデータ部は2回URLエンコードされています。

URLエンコードを2回行う理由について知りたいという方は、以下の記事をご参照ください。

さらに、animation_urlにHTMLのdataURLを指定して、メタデータ全体を作成します。 メタデータ内の{"name":〜などの文字列はURLエンコードされています。

最後に、メタデータをdataURL化して戻り値にします。

function tokenURI(uint256) public view virtual override returns (string memory) {
  // ...略 (requestsの作成とBufferSizeの計算)

  // HTMLを作成 (dataURL)
  bytes memory dataURL = scriptyBuilder.getHTMLWrappedURLSafe(requests, bufferSize);

  // メタデータを作成してdataURL化
  return string(abi.encodePacked(
    "data:application/json,",
    // {"name":"p5.js Example...", "description":"...","animation_url":"
    "%7B%22name%22%3A%22p5.js%20Example...%22%2C%22animation_url%22%3A%22",
    dataURL,
    // "}
    "%22%7D"));
  }

tokenURI関数に関しては、これだけです。

ROSESのtokenURI関数と比べると、かなり簡単ですね。

Base64エンコードの場合

メタデータをURLエンコードではなくBase64エンコードする場合は、 requestsの作成は同じですが、BufferSizeの計算とHTMLの作成に別の関数を使います。

  • BufferSizeの計算 :
    • getBufferSizeForURLSafeHTMLWrapped → getBufferSizeForHTMLWrapped関数に変更
  • HTMLの作成 :
    • getHTMLWrappedURLSafe → getEncodedHTMLWrapped関数に変更

メタデータはURLエンコードせずに作成しておいて、最後にBase64でエンコードします。

nawoo

Base64エンコードの方がtokenURI関数のガス使用量は多くなるので、out of gasエラーになりやすいです。

function tokenURI(uint256) public view virtual override returns (string memory) {
  // ...略 (requestsの作成)

  // BufferSizeを計算
  uint256 bufferSize = scriptyBuilder.getBufferSizeForHTMLWrapped(requests);

  // HTMLを作成(Base64エンコードタイプのdataURL化)
  bytes memory base64EncodedHTMLDataURI = scriptyBuilder.getEncodedHTMLWrapped(requests, bufferSize);

  // メタデータを作成
  bytes memory metadata = abi.encodePacked(
      '{"name":"...","description":"...","animation_url":"',
      base64EncodedHTMLDataURI,
      '"}'
  );
  // メタデータをBase64エンコードしてdataURL化
  return string.concat("data:application/json;base64,", Base64.encode(metadata));
}

コントラクト内でJavaScriptを動的に作成する場合

JavaScriptコードを動的に作成したい場合は、どうすればいいでしょうか。

例えばジェネラティブNFTで、tokenIdごとに内容を変化させたい場合は、次のようなコードを追加して、JavaScript側にtokenIdの値を渡す必要があります。

<script>
const tokenId=XXX;
</script>

このコードは、tokenIdごとにtokenId=1tokenId=2tokenId=3…と変化するので、EthFSなどから読み込むわけにはいかず、コントラクト内で動的に作成する必要があります。

こういった場合は、requestsの作成時に、scriptContentを指定します。

requests[2].wrapType = 0;
requests[2].scriptContent = abi.encodePacked("const tokenId=", tokenId.toString(), ";");

このようにすれば、tokenIdが1の場合、

<script src="data:text/javascript;base64,Y29uc3QgdG9rZW5JZD0xOw=="></script>

というJavaScriptコードがHTMLに追加されます。 これは、以下のコードをdataURL化したものです。

<script>
const tokenId=1;
</script>

const tokenId=1;をBase64エンコードして、dataURL化して、<script>タグのsrc属性に設定して、HTMLに追加する、という作業をscripty.solが自動的に行なってくれるのです。すごいですよね。

以上で、p5.jsのスケッチでtokenIdというグローバル変数を使えるようになります。

ScriptyStorageへのアップロード

EthFS_P5_URLSafe.solでは、以下4つのJavaScriptを読み込んでいました。

  1. “scriptyBase”
  2. “p5-v1.5.0.min.js.gz”
  3. “gunzipScripts-0.0.1.js”
  4. “pointsAndLines”

1,2,3は、すでにScriptyStorageやEthFSにアップロードされている共有のファイルですが、4の”pointsAndLines”はこのNFT専用のファイルなので、事前にアップロードしておく必要があります。

参考 : pointsAndLines.js → p5.jsのスケッチファイルです

ScriptyStorageには、スクリプトでアップロードします。

  1. ScriptyStorageコントラクトのcreateScript関数でファイルを作成
  2. 最大24,575バイトになるようにファイルを分割 (SSTORE2を使うため)
  3. 分割した各チャンクをaddChunkToScript関数でアップロード

参考 : EthFS_P5_URLSafeのデプロイスクリプトのstoreScript関数

nawoo

以下の記事で紹介したように、EthFSを使えば公式サイトからアップロードできるので、個人的にはEthFSを使う方がおすすめです。 

ただし、EthFSの公式サイトからアップロードした場合はBase64エンコードされるため、requestsの作成の際にwrapType=1を指定することを忘れないでください。


最後に次章では、筆者が実際にscripty.solを使いながらp5.jsを使ったフルオンチェーンNFTを作成してみましたので、ステップバイステップで解説したものを「定期購読プラン」登録者向けにまとめています。ご興味あればご覧ください。

p5.jsを使ったNFTを作ってみよう


この続き: 2,730文字 / 画像11枚

この続きは、 定期購読プランメンバー専用です。
Already a member? ここでログイン

まとめ

今回は、フルオンチェーンNFTを作成するときに役立つライブラリ「scripty.sol」について、概要やその使い方などを中心に解説しました。

本記事が、scripty.solの概要や使い方、p5.jsを使ったフルオンチェーンNFTの構築方法などについて理解したいと思われている方にとって、少しでもお役に立ったのであれば幸いです。

また励みになりますので、参考になったという方はぜひTwitterでのシェア・コメントなどしていただけると嬉しいです。

scripty.solはさまざまなパターンのHTMLを作成することができますが、 今回はp5.jsを使ったフルオンチェーンNFTという目的に絞って使い方を説明しました。

nawoo

Three.jsを使う場合にも同じ方法で作成できると思います。 ぜひscripty.solを使って、すごいフルオンチェーンNFTを作ってみてください。

なお、本記事のコントラクトやスクリプトはこちらのGitHubで公開していますので、ぜひ参考にしてください。

イーサリアムnaviを運営するSTILL合同会社では、web3/crypto関連のリサーチ代行、アドバイザー業務、その他(ご依頼・ご提案・ご相談など)に関するお問い合わせを受け付けております。

まずはお気軽に、こちらからご連絡ください。

みんなにも読んでほしいですか?
  • URLをコピーしました!
目次