♨️

ルーラNFTの中身を探ってみた

2022/09/04に公開

はじめに

ルーラNFT が 2022 年 6 月から有馬温泉・飯坂温泉でテスト販売を開始してから約 2 ヶ月が経ちました。 PolygonScan によると[1]、この記事を書いている時点で既に 531 のルーラ NFT が 303 人のユーザーに流通しているそうです。
特に 2022 年 7 月の大雨で被災した宮城県松島町の支援のために販売された NFT はルーラ NFT の中で最も多く発行されており、全体の約 3 分の 1 を占めているという点は観光特化型の「ローカル NFT」を掲げるルーラ NFT らしい出来事であるように思います。

https://prtimes.jp/main/html/rd/p/000000012.000093692.html

一方で、このルーラ NFT については(現状ではオタク層向けのコンテンツとして売り出されている割には)技術的な観点から語られることが少なく[2]、その詳細は謎に包まれていました。

そこで、筆者の自宅(福島市)から近い飯坂温泉のほりえや旅館でテスト販売されているルーラ NFT を実際に購入して、どのような NFT が発行されるのかを調査してみました。

なお、この記事では検証に使用したサービスやソースコードについても掲載しているので、読者の中で既にルーラ NFT を購入している人は同様の手順を真似することができます。もし購入していないとしても、Web 上に公開されている情報を取得しているだけなので自分で所有していない NFT に対しても自由に試すことができます。

ルーラ NFT を購入してみた

ルーラ NFT を購入するためには 「ルーラコイン」アプリ を使う必要があります。このアプリは、前払式支払手段の「ルーラコイン」のチャージ・決済や、「ルーラ NFT」の購入・閲覧に使用することができるアプリです。このアプリは PWA なので、スマホの Web ブラウザで開いてそのまま使用するか、アプリとしてインストールして使用することが可能です。
また、ルーラ NFT の購入に使用できる決済手段はルーラコインのみとなっているため、あらかじめ購入に必要な金額をチャージしておきましょう。

ルーラ NFT の特徴として、オンラインでどこからでも購入できる訳ではなく、ルーラ NFT を販売している店舗を実際に訪れなければ購入できないという制約が設けられています。これはルーラコインアプリ上でスマートフォンの位置情報を取得することで実現されており、店舗に設置されている購入用の QR コードをユーザーがアプリで読み取り、なおかつ端末の位置情報がチェックされた上でようやく購入できる仕組みとなっています。

今回購入したのは、飯坂温泉のほりえや旅館で販売されている「ほりえや旅館 扇風機真尋」(R) のルーラ NFT で価格は ¥1,000/点 (税込) です。
購入したルーラ NFT は「ルーラコイン」アプリ上で表示することができます。何やら「ルーラNFT 所有証明書」という表示もありますね。

https://twitter.com/yufushiro/status/1543162105171324928

アプリに表示されている情報は次のようになっていました。

  • 所有者名: ゆふしろ
  • 発行日: 2022/07/02
  • カード名: ほりえや旅館 扇風機真尋
  • シリアルNo.: #8
  • 発行者: ルーラ
  • トークンID: 400000008
  • コントラクトアドレス: 0xa632450B73E007448428F38770BaE7BD0cb7cF01

予備知識もなくこの情報を見せられても何がなんだかという感じだと思います。ここから PolygonScan を使用して、ここに表示されているものと同じトークンが実際にパブリックチェーン上に存在するかどうかを探していきます。

購入したルーラ NFT を PolygonScan で探す

ルーラコインのプレスリリースによると、ルーラコインおよびルーラ NFT は Polygon(MATIC) ネットワークを採用していると記載されています。

■ルーラが採用するブロックチェーン技術
ルーラNFT及び、ルーラコインは、トランザクションの管理にブロックチェーン技術を利用しており、各NFTサービスで利用実績の多い「Polygon(MATIC)」ネットワークを採用しています。[4]

そこで、Polygon の Blockchain Explorer である PolygonScan を使用して、先ほど購入したルーラ NFT を探してみようと思います。

ルーラコインアプリに表示されていた「コントラクトアドレス」の 0xa632450B73E007448428F38770BaE7BD0cb7cF01 で検索すると以下のページにたどり着きます。

https://polygonscan.com/address/0xa632450b73e007448428f38770bae7bd0cb7cf01

このコントラクトアドレスはざっくり言うとルーラ NFT の発行主体を指すアドレスで、このアドレスだけ知っていても誰のどの NFT を指しているのかは分かりません。個別の NFT を特定するためには、コントラクトアドレスに加えて「トークン ID」の情報も必要です。

先ほどのコントラクトアドレスに加えてトークン ID の 400000008 で検索すると、以下のように購入したルーラ NFT と同じトークンが表示されます。

https://polygonscan.com/token/0xa632450b73e007448428f38770bae7bd0cb7cf01?a=400000008

これでルーラ NFT がパブリックチェーン上に存在するらしいことが確認できました。めでたしめでたし。 …としたい所ですが、果たしてこのトークンは本当に温泉むすめのイラストに紐付いている NFT なのでしょうか? 実際のところ PolygonScan のページを探しても NFT が指し示すイラストに関する情報は何も書かれていません。

NFT の標準規格: ERC-721

ルーラ NFT に紐付いたイラストを探す前に、ルーラ NFT を含む一般的な NFT で使われている ERC-721 という規格について説明します。ここから若干難しい内容になるので次の節まで読み飛ばしても構いません。

ERC-721 の規格の中には ERC721 interface というインタフェースが定義されています。
ルーラ NFT をアプリで表示した時に「コントラクトアドレス」という項目がありましたが、コントラクトアドレスが指す先には EVM バイトコードで書かれた実行可能なプログラムが存在しています。このプログラム内の関数を呼び出すためには関数名や引数の型を事前に知っている必要があり[5]、そのためのインタフェースを定義したものの一つが ERC721 interface です。
実際の ERC721 interface の定義を見てみましょう。

interface ERC721 /* is ERC165 */ {
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
    function balanceOf(address _owner) external view returns (uint256);
    function ownerOf(uint256 _tokenId) external view returns (address);
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function approve(address _approved, uint256 _tokenId) external payable;
    function setApprovalForAll(address _operator, bool _approved) external;
    function getApproved(uint256 _tokenId) external view returns (address);
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

interface ERC721Metadata /* is ERC721 */ {
    function name() external view returns (string _name);
    function symbol() external view returns (string _symbol);
    function tokenURI(uint256 _tokenId) external view returns (string);
}

ここには多くの event や function が定義されていますが、NFT の情報を取得するために必要なのはこの中のごく一部のみです。

ERC721 interface にはトークンが NFT として機能するために必要な関数が定義されており、例えば safeTransferFrom 関数はルーラ NFT を購入した際のトランザクションにも使用されています。

ルーラNFT購入時のトランザクションをPolygonScanで閲覧した画像。Input Data として「Function: safeTransferFrom(address from, address to, uint256 tokenId)」が表示されており、ERC721 interface で定義されたものと同名の関数が使われていることが分かる。

では、ルーラ NFT に紐付いたイラストの情報を取得するにはどれを使えばよいかというと、ERC721Metadata interface にある tokenURI 関数で取得することができます[6]

それでは次に、この tokenURI 関数を使用して実際に NFT に紐付いているメタデータを取得していきましょう。

tokenURI 関数を呼び出してみる

ルーラ NFT に紐付いたメタデータは tokenURI 関数で取得できることが分かったので、サクッと呼び出して確かめてみます。

interact.mjs
import { ethers } from "ethers";

const CONTRACT_ADDRESS = "0xa632450B73E007448428F38770BaE7BD0cb7cF01";
const TOKEN_ID = "400000008";

const contractAbi = [
  // ERC721Metadata interface
  "function tokenURI(uint256 _tokenId) external view returns (string)",
];

const provider = new ethers.JsonRpcProvider("https://polygon-rpc.com");
const contract = new ethers.Contract(CONTRACT_ADDRESS, contractAbi, provider);

const uri = await contract.tokenURI(TOKEN_ID);
console.log(`tokenURI: ${uri}`);
$ node interact.mjs
tokenURI: https://nft.rural.ne.jp/nft/metadata/400000008.json

何やら JSON を指す URI が返ってきました。この中に NFT のメタデータが含まれているらしいので取得してみましょう。

400000008.json
{
  "name": "ほりえや旅館 扇風機真尋",
  "description": "扇風機で涼む三頭身の飯坂真尋",
  "image": "https://nft.rural.ne.jp/nft/content/4/00002-00002-R.jpg",
  "external_url": "https://nft.rural.ne.jp",
  "attributes": [
    {
      "trait_type": "PublishDate",
      "value": "2022/07/02"
    },
    {
      "trait_type": "Publisher",
      "value": "ルーラ"
    },
    {
      "trait_type": "CardID",
      "value": "00002-00002-R"
    },
    {
      "trait_type": "CardName",
      "value": "ほりえや旅館 扇風機真尋"
    },
    {
      "trait_type": "CharacterName",
      "value": "飯坂真尋"
    },
    {
      "trait_type": "Rarity",
      "value": "R"
    },
    {
      "trait_type": "VoiceActorName",
      "value": "吉岡茉祐"
    },
    {
      "trait_type": "AreaName",
      "value": "飯坂温泉"
    },
    {
      "trait_type": "IntellectualProperty",
      "value": "温泉むすめ"
    },
    {
      "trait_type": "CopyrightHolder",
      "value": "Enbound, Inc."
    },
    {
      "trait_type": "Creator",
      "value": "有坂あこ"
    },
    {
      "trait_type": "SerialNumber",
      "value": 8
    }
  ]
}

ようやくそれっぽい内容の文字列が出てきましたね。あとはもう JSON のプロパティ名を見れば大体何が書いてあるのか察しが付くと思いますが、一応規格上はどうなっているのか確認しておきましょう。 EIP-721 によると、tokenURI 関数が返す値は JSON ファイルを指し示す URI であり、その JSON は以下の JSON Schema に適合することとなっています。

{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}

ここで定義されているプロパティは name, description, image の 3 つだけなので、ルーラ NFT のメタデータに含まれていた external_urlattributes は EIP-721 の規格では定義されていないプロパティということになります。おそらく EIP-721 以外に何らかの標準化された拡張があるのかもしれませんが、今回はそこまでは見付けられませんでした。

メタデータに含まれる画像 URI を確認してみる

最後に image プロパティに含まれている画像 URI を確認してみましょう。規格通りなら image/* の画像が得られるはずです。

https://nft.rural.ne.jp/nft/content/4/00002-00002-R.jpg

Web ブラウザのアドレスバーに URI をコピペして開いてみると画像が…、あれ、出てこないですね。
curl で確認してみると 403 エラーが返っており、リンク切れっぽい雰囲気です[7]。まあそういう事もあるよね。

……

以上です!!!

$ curl -I https://nft.rural.ne.jp/nft/content/4/00002-00002-R.jpg
HTTP/2 403
content-type: text/html
content-length: 841
date: Sat, 03 Sep 2022 17:04:34 GMT
last-modified: Thu, 25 Aug 2022 11:27:52 GMT
etag: "269d4392ac52b55d22084b57fe8a1d4f"
x-amz-version-id: cDLOks4DqCXSVedErRON_QrgkNwMjhvh
accept-ranges: bytes
server: AmazonS3
x-cache: Error from cloudfront
via: 1.1 ece495703bac6f634e6e16b4037affae.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C4
x-amz-cf-id: Uo7KMrhOwrC82g8GGYr27qxy2TgoqOSWT4omCmyMyAP_Yd5DGlz1zw==
cache-control: no-cache
脚注
  1. https://polygonscan.com/token/0xa632450b73e007448428f38770bae7bd0cb7cf01 ↩︎

  2. Twitterで「"ルーラNFT" Polygon」を検索してもルーラコイン公式アカウントと筆者の 2 件のツイートしか出てこないので、この記事で扱う内容には誰も興味がない可能性がある ↩︎

  3. もちろんこれを公開している筆者のルーラコインの購買履歴は誰でも閲覧できる状態にある: https://polygonscan.com/address/0x0ee42053ceeba75fa673fd689c12e5f1ad18793f#tokentxns ↩︎

  4. 観光特化型デジタル通貨「ルーラコイン」を提供する株式会社ルーラは、観光地の魅力を再発見できる日本初のローカライズされたNFTである「ルーラNFT」のテスト販売を開始します!|株式会社ルーラのプレスリリース(アクセス日: 2022-09-03) ↩︎

  5. 正確には、関数名と引数の型を文字列にしてハッシュ化 (Keccak256) した先頭 4 バイトを Method ID として関数の識別に使用している ↩︎

  6. ERC-721 では ERC721Metadata を含む metadata extension の実装は OPTIONAL となっているが、今回調査するルーラ NFT では ERC721Metadata が実装されていたため tokenURI 関数が使用できる前提で進めている ↩︎

  7. ルーラ NFT の名誉のために補足すると、このルーラ NFT を購入した直後(2022年7月2日)に試した際には正常にアクセスできていた ↩︎

Discussion