【スマートコントラクト】独自トークンでNFTガチャ回せるようにしてみた

ガチャを回すとNFTがランダムで当たるようなスマートコントラクトを書いたのでメモがてらQiitaにも書き殴ろうと思います。

## 概要

今回は独自コインであるERC20トークンを使うと1回ガチャができるようにします。

ガチャで当たるトークンはERC1155のトークンで4種類にします。
使うチェーンはETHのテストネット、Rinkebyです。fakeのETHを[faucet](https://rinkebyfaucet.com/) からもらってきてくださいね。

今回は確率は均一にします。
「トークンのレアリティを設計して、レアなトークンは確率を下げる」という実装も応用してやろうと思えばできますね。

そういえば [前書いた記事](https://qiita.com/lowzzy/items/a9343d7aea38c54c826e) でERC20トークンの実装やrinkeby、faucetについては書いているので今回はスキップします。同じ内容ですのでそちらを参照してください。

## コード

### ERC1155トークン & ガチャ


```

// SPDX-License-Identifier: MIT LICENSE

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Strings.sol";


pragma solidity ^0.8.0;

contract Emaki is ERC1155, Ownable {
    string baseMetadataURIPrefix;
    string baseMetadataURISuffix;
    using Strings for uint256;

    uint256 randNonce = 0;

    struct TokenInfo {
        IERC20 paytoken;
        uint256 costvalue;
    }
    uint256 degit_18 = 1000000000000000000
    address LWZaddress = 0x...的なデプロイしたERC20トークンのアドレスを入れる;
    IERC20 public LWZ = IERC20(LWZAddress);

    TokenInfo public AllowedCrypto = {
        LWZ,10*degit_18
};

    uint256[] public tokens = [
        0, 1, 2, 3
    ];

    string public name;
    string public symbol;


    constructor() ERC1155("") {

        name = "LowzzyGacha";
        symbol = "LGacha";
    }


    function uri(uint256 _id) public view override returns (string memory) {
        return string(abi.encodePacked(
            baseMetadataURIPrefix,
            Strings.toString(_id),
            baseMetadataURISuffix
        ));
    }
    
    # ガチャを回すときはこの関数を呼び出します。
    # _amount : 何回ガチャを回すか
    # requireは例外処理の記述で、rails でいうところのraiseです。
    function gacha(uint256 _amount) public payable {
        TokenInfo storage tokens = AllowedCrypto;
        IERC20 paytoken;
        paytoken = tokens.paytoken;
        uint256 cost;
        cost = tokens.costvalue * _amount;
        uint256 allowCost = paytoken.allowance(msg.sender,address(this));
        require(allowCost >= cost * _amount, "Not enough balance to complete transaction");
        paytoken.transferFrom(msg.sender, address(this), cost);

        uint256 num;
        uint256 id;
        for (uint256 i = 1; i <= _amount; i++) {
            randomNum = getRandomNum(msg.sender);
            uint256 len = tokens.length;
            uint256 id = randomNum % len;
            uint256 mintAmount = 1;
            _mint(msg.sender, id, mintAmount, "");
        }
    }

　　　# 今回の実装のキモである擬似乱数生成部分。
     # ブロックチェーンはその性質上乱数は予測しやすいため、
     # 十分に注意して生成する必要があります。
　　　# keccak 256を用いてtransactionやblockの情報から擬似乱数を生成しています。
     # ガチャが同時に複数回回された時、_to、block.timestampは同じ数値が入ってしまうため
     # randNonceを用いて同じ数値が入らないようにしています。
    function getRandomNum(address _to) public returns (uint256){
        randNonce++;

        uint256 randomNum = uint256(
            keccak256(
                abi.encode(
                    _to,
                    block.timestamp,
                    randNonce
                )
            )
        );
        return randomNum;
    }

     # ガチャをする際に支払われたトークンはスマートコントラクトのアドレスに
　　　# 貯まっていきます。
　　　# この関数はそのコントラクトに貯まったERC20トークンを引き出す関数です。
    function withdraw() public payable onlyOwner() {
        TokenInfo storage tokens = AllowedCrypto;
        IERC20 paytoken;
        paytoken = tokens.paytoken;
        paytoken.transfer(msg.sender, paytoken.balanceOf(address(this)));
    }
}


```

## デプロイ

まず初めにERC20トークンをデプロイします。

前回と同じようにremixを使います。

デプロイしたら、ERC20トークンのコントラクトアドレスを

    address LWZaddress = 0x...的なデプロイしたERC20トークンのアドレスを入れる;

に入れてみてください。

入れたらガチャスマートコントラクトをデプロイしてください。

ガチャトークンをデプロイしたら、erc20トークンであるLWZをガチャスマートコントラクトにapproveします。

その後、gacha(1)とでもすれば1回ガチャが回せますね。

ちょいと眠いので一旦おわり。最後の方が雑になってしまったので今度加筆修正をしますね！



それでは！