🎨

テイラーワークス社内実験「Play! Web3」について#2:〜NFT編〜

2023/07/21に公開

こんにちは。テイラーワークスの澤田です。
プロダクトチームでWeb3エンジニアをやっています。

Web3を中心に、気になるテーマについてブログにまとめていけたらと思います。

前回は、テイラーワークスが行った「Play! Web3」という Web3 体験まつり(社内実験)の概要と、発行した Tailor Works Token について 説明しました。

今回は NFT について、紹介したいと思います。

※ブログ内で引用しているコードは、社内で実験したものを紹介しています。安全性や脆弱性の検査は行っていません。参考にされる場合は、ご自身の責任の元、ご対応ください

連載一覧

その前にNFTとは・・・?

デジタルアセット(アート作品、ゲームアイテムやキャラクター、コレクターアイテムなどなど)の所有情報を管理し、売買などによって、所有者が変わる仕組みを持っているものが、NFT(Non-Fungible Token)です。

トークンの次はNFT

Web3を体験するのですから、NFTもやってみたいですよね?

これも楽しく簡単に体験できるように、NFTの発行(Mint・ミントといいます)を行えるようにウェブサイトを実装しました。

画像ファイルはNFT作成時に AWS(Amazon Web Service)S3 へアップロードし、Mint後は CloudFront を経由して画像を表示させています。

NFTをMintするウェブページ

画像のトリム処理には Vue Cropper を使用しています。

https://github.com/Agontuk/vue-cropperjs

画像のアップロードやトリム処理をウェブに実装したこともあって、簡単に NFT を発行できるようになりました。

MintされたNFT

Tailor Works Token のバージョンアップ

実は、NFTを実装する際に Tailor Works Token をバージョンアップしています。

最初に実装した Tailor Works Token には販売時にトークンを自動的に送金する機能がなかったため、そのあたりの機能追加を行っています。(前回のブログの 支払い機能をもたせました で記載した内容です)

バージョンアップでトークンのスマートコントラクトが新しくなる際に、下記のような形で新しいトークンの初回残高を決定するようにしてみました。

NFT購入の送金機能を持たせるため、TWトークンをバージョンアップしました。
そのまま残高を引き継ぐのではなく、このような仕様になっています。

1)初期プレゼントを 1,000TW (増額)・・・ベーシックインカム要素として
2)現状のTWトークンの最終保有額
3)今のトークンでの送金回数×累計送金額・・・貢献に対する報酬として

上記1)〜3)の合計し、その額の 50%〜300% を次の初期保有額にする(少数値切り捨て・親ガチャ要素)

乱数はスマートコントラクトにより生成。(0〜250の範囲で取得し、その後スプレッドシート内で50〜300となるように調整)

NFTに独自に実装した機能

基本は ERC721 の使用に基づいていますが、以下のような機能をスマートコントラクトに追加しています。

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

NFT に Tailor Works Token で価値をつけました

あくまでも実験用の実装ですが、次のような機能も盛り込んでいます。

  • 作品の価値を管理できるように実装し、 Mint 時に Tailor Works Token で NFT の価値を設定できるようにしました(内部で持つ値段)
  • 作品が販売状態であるかの状態管理と販売価格を設定する機能を実装し、スマートコントラクト上で NFT を購入できるようにしました
  • 作品の販売価格が、Mint 時の価値の10倍以上であった場合、販売利益の一部を作者に分配する機能を実装しました

出品処理

    function exhibit(uint256 tokenId, uint256 price) external onlyTwMember whenNotPaused returns (bool success) {
        _requireMinted(tokenId);

        require(ownerOf(tokenId) == msg.sender, 'Not a holder');
        require(workList[tokenId].isOnSale == false, 'Cannot be changed during the sale');

        // require(LOWEST_PRICE <= price, 'Price is too low');
        require(workList[tokenId].currentValue <= price, 'Price is too low');

        workList[tokenId].isOnSale = true;
        workList[tokenId].price = price;

        emit Exhibited(msg.sender, tokenId, price);

        return true;
    }

購入処理

    function purchase(uint256 tokenId) external onlyTwMember whenNotPaused returns (bool success) {
        _requireMinted(tokenId);

        address nftHolder = ownerOf(tokenId);
        address purchaser = msg.sender;

        require(workList[tokenId].isOnSale == true, 'Not during sale');
        require(nftHolder != purchaser, 'Holders cannot purchase');
        require(isTwTokenMember(purchaser), 'Must be a member of TW Token');

        uint256 newPrice = workList[tokenId].price;

        // processing start
        workList[tokenId].isOnSale = false;

        // For transactions at more than 10 times the initial price:
        // Profit sharing of 5% of the price to the NFT author
        if (workList[tokenId].initialValue.mul(10) <= workList[tokenId].price) {
            uint256 authorProfit = workList[tokenId].price.mul(5).div(100);
            uint256 soldProfit = workList[tokenId].price.sub(authorProfit);
            address nftAuthor = workList[tokenId].auther;

            bool isSuccessfulToAuther = twToken.pay(purchaser, nftAuthor, authorProfit);
            if (isSuccessfulToAuther == false) {
                _suspendPayment(tokenId);
            }
            bool isPaymentSuccessful = twToken.pay(purchaser, nftHolder, soldProfit);
            if (isPaymentSuccessful == false) {
                _suspendPayment(tokenId);
            }
        } else {
            bool isPaymentSuccessful = twToken.pay(purchaser, nftHolder, newPrice);
            if (isPaymentSuccessful == false) {
                _suspendPayment(tokenId);
            }
        }

        /* ===== The following is executed as payment finalization ===== */

        _transfer(nftHolder, msg.sender, tokenId);

        uint256 purchasedAt = block.timestamp;

        workList[tokenId].currentValue = newPrice;
        workList[tokenId].price = 0;
        workList[tokenId].soldTimes = workList[tokenId].soldTimes.add(1);

        emit Purchased(nftHolder, msg.sender, tokenId, purchasedAt);

        return true;
    }

データの管理について

オンチェーンで管理した内容・項目

  • トークンID(作品No.)
  • 作者のイーサリアムアドレス
  • 現在の保有者のイーサリアムアドレス
  • 販売中であるか否かを示すフラグ
  • 制作費用(製作時に支払ったTWトークンの額)
  • 販売回数
  • 販売中の価格(値段)
  • 作品の現在の価値(販売回数が0なら制作費用、違うなら最後の販売価格)
  • 画像のURI(S3のオブジェクトキー)
    struct Works {
        address auther;
        bool isOnSale;
        uint256 initialValue;
        uint256 currentValue;
        uint256 price;
        uint256 soldTimes;
        uint256 mintedAt;
    }

オフチェーンで管理した内容・項目

  • 画像ファイル(画像は Amazon S3 でファイルを管理し、Amazon CloudFront で配信しました)
  • 作品名
  • 概要

補足・オンチェーン・オフチェーンって?

オンチェーン(On-Chain) は、ブロックチェーン上で行われる処理やデータのことを指します。オンチェーンはトランザクションやデータがブロックチェーンに記録され、検証されます。

一方、 オフチェーン(Off-Chain) は、ブロックチェーン外で行われる処理やデータのことを指します。ブロックチェーン上のトランザクションやデータを直接変更しないため、ブロックチェーンの制約に縛られない設計や実装が可能になります。

今回の実験でやらなかったこと

フルオンチェーンでの NFT 管理の方法もやってみたかったのですが、時間の都合と、画像や文字情報が消せなくなるため行っていません。

おわりに

今回は、テイラーワークスの社内のWeb3体験イベント「Play! Web3」で行った NFT について紹介しました。

次回は他人に売却や譲渡ができない NFT、SoudBound Token (SBT) について書きたいと思います。

テイラーワークスは、エンジニア採用強化中

テイラーワークスは、エンジニア採用強化中です!
▼少しでも興味をお持ちいただけましたら、採用ページもチェックしてみてください
https://tailorworks.co.jp/careers

Tailor Worksテックブログ

Discussion