Ethereum上で自分オリジナルのトークンを作ろう!!
2021-12-05
azblob://2022/11/11/eyecatch/2021-12-05-create-ft-000-1.jpg

この記事は FIXER Advent Calendar 2021(https://adventar.org/calendars/6788) 5日目の記事です。

前回は、A.Sさんの「Power AppsでJSONを解析するときに気を付けること」でした。データのエクスポートやインポートでJSONを使うことは多いのでとてもためになる記事でした!
最近、ブロックチェーンについて触れていましたので、今回はEthereumネットワーク上でERC-20トークンを作成する方法についてご紹介しようと思います。Ethereumの公式サイトには開発者のために様々なチュートリアルが用意されているのですが、ERC-20トークンの作成方法のチュートリアルがドキュメントではなく、まさかの海外の方の解説動画でした。なので、私と同じようにこれから勉強し始める方に少しでも力になれれば幸いです。なお、私自身、ブロックチェーンやトークンについて勉強し始めて2週間ほどだということを念頭に置いて読んでいただけると幸いです。

「Ethereumネットワーク上でトークンを作成する」についてもう少し詳しい手順に言い換えますと、
EthereumのRopstenテストネットワーク上にERC-20のトークンについて実装したスマートコントラクトをデプロイして、スマートコントラクトにトークンの作成をしてもらうということです。

用語やツールについて簡単に説明

用語

  • スマートコントラクト(以下コントラクト)
    • Ethereumネットワーク上であらかじめ定義された処理を自動的に行うプログラム・アプリケーションのこと
  • Ropstenテストネットワーク
    • 開発者用のEthereumテストネットワーク

ツール類

今回使用するのは、MetaMask, Solidity, Hardhat, Alchemyを使います。これらについて簡単に説明しますと、

  • MetaMask
    • 仮想通貨ウォレット(Ethereumアカウントを作成するため・トークンの受け取りを確認するために使用)
  • Solidity
    • コントラクトを記述するためのプログラミング言語
  • Hardhat
    • Ethereumのコントラクトを開発する際に必要なデプロイやテストなどが用意されている開発環境
  • Alchemy
    • ブロックチェーンアプリ開発者向けプラットフォーム(デプロイ後のコントラクト)

下準備

Alchemyの無料アカウントの作成とRopstenテストネットワークへの接続

最初に、Alchemyの無料アカウントを作成します。(ここから行えます)無料アカウントの作成後、上にある「Apps」から「Create App」を選択してください。

選択後、下の画像のように入力してください。「NETWORK」項目は、Ropstenを選択してください!「NAME」部分に関しては自由変更しても大丈夫です。入力が完了後、「Create app」をクリックしてください。

Ethereumアカウントの作成

次に、Ethereumアカウントを作成するために、MetaMaskを使います。Chromeの拡張機能やスマホのアプリも用意されています。ここではChromeの拡張機能を使用しています。ダウンロードはこちらからできます。ダウンロード後、左上のアイコンから「設定」をクリックしてください。設定画面の詳細にある「Show test networks」項目をONに変更後、左上にあるネットワークの選択プルダウンから「Ropstenテストネットワーク」を選択してください。

選択後、アカウントのトップページにあるアカウント名部分をクリックして、Ethereumアドレスをコピーしてください。

テスト用ETHの取得

ブロックチェーン上でコントラクトをデプロイ・動作させるためにテスト用のETHを取得する必要があります。こちらのサイトからETHを取得することができます。サイト内のテキストボックスに先ほどコピーしたEthereumアカウントのアドレスをペーストし、「Send me test Ether」をクリックするとETHを取得できます。取得したかどうかの確認は、MetaMaskのトップページでできます。

Hardhatプロジェクトの作成

任意のディレクトリから、以下のコマンドを実行してください。

mkdir my-ft
cd my-ft

作成したディレクトリに移動後、以下のコマンドでプロジェクトの初期化処理を行います。いくつか質問されますが、すべてデフォルトで大丈夫です。

npm init

次に、npm inistallでHardHatをインストールします。

npm install --save-dev hardhat

インストール後、以下のコマンドでHardHatプロジェクトを作成します。コマンド実行後、選択項目がありますが、Create an empty hardhat.config.jsを選択してください。

npx hardhat

888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888
👷 Welcome to Hardhat v2.0.11 👷‍
? What do you want to do? …
Create a sample project
❯ Create an empty hardhat.config.js
Quit

最後に、コントラクトのコードとスクリプトそれぞれを置くディレクトリと、パッケージのインストールを行います。

mkdir contracts
mkdir scripts
npm install @openzeppelin/contracts
npm install dotenv
npm install --save-dev @nomiclabs/hardhat-ethers 'ethers@^5.0.0'

先ほどインストールしたパッケージや依存関係をプロジェクトで認識するためにHardhatの設定ファイルhardhat.config.jsを下記のようにしてください。環境変数に関しては後ほど記述しますので今は気にしないでください。

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
require('dotenv').config();
require('@nomiclabs/hardhat-ethers');
const { API_URL, PRIVATE_KEY } = process.env;
module.exports = {
  solidity: "0.8.0",
  defaultNetwork: "ropsten",
   networks: {
      hardhat: {},
      ropsten: {
         url: API_URL,
         accounts: [`0x${PRIVATE_KEY}`]
      }
   },
};

Solidityコードの記述

contractsディレクトリに移動後、MyFT.solを作成してください。MyFT.solをエディタで開いて下記のコードを貼ってください。

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MTT is ERC20, Ownable {
    uint256 private _totalSupply;

    // 発行するトークンの名前・シンボルを設定
    constructor() public ERC20("Maki Tai Token", "MTT") {}

    // コントラクトの所有者しか実行できないトークンを発行する関数
    function mintMTT(address recipient, uint256 initialSupply)
    public onlyOwner
    {
        _totalSupply = initialSupply;

        _mint(recipient, _totalSupply);
    }
}

@openzeppelin/contracts/token/ERC20/ERC20.sol は、OpenZeppelinライブラリのERC-20を実装したコントラクトです。これをインポートすることで簡単にERC-20のコントラクトを実装することができます。
@openzeppelin/contracts/access/Ownable.solは、Ethereumネットワークにデプロイ後、コントラクトに対するアクセスを制御するのに用いられます。今回の場合、mintMTT関数にonlyOwnerを付けているので、この関数を使用できるのはコントラクトの所有者のみに制限しています。

9行目では、ERC-20のトークンの名前とシンボルを設定します。ビットコインの場合、名前がBitcoinでシンボルがBTC、イサーリアムの場合、名前がEthereumでシンボルがETHということです。そのため、ここは好きな名前・シンボルにしてください。

constructor() public ERC20("Manki Taishi Token", "MTT") {}

11行目からのmintMTT関数では、指定したアカウント(recipient)に9行目で設定したトークンを指定した数(initialSupply)だけ生成・送金の処理を行っています。

function mintMTT(address recipient, uint256 initialSupply)
public onlyOwner
{
    _totalSupply = initialSupply;

    _mint(recipient, _totalSupply);
}

環境変数の設定

プロジェクトのルートディレクトリに.envファイルを生成し下記の変数を記述してください。
API_URLには、Ethereumのノードに接続するために必要なAlchemyのAPIKeyを、PRIVATE_KEYは、Ethereumアカウントの秘密鍵を記述します。PUBLIC_KEYは、Ethereumアカウントのアドレスを記述します。

API_URL="https://eth-ropsten.alchemyapi.io/v2/your-api-key"
PRIVATE_KEY="your-metamask-private-key"
PUBLIC_KEY="your-public-account-address"

AlchemyのAPIKeyは、Alchemyで作成したアプリの詳細ページから取得できます。また、Ethereumアカウントの秘密鍵は、MetaMaskのアカウントページ内の左上にある縦3点リーダーをクリックして、「アカウントの詳細」をクリックすると「秘密鍵のエクスポート」から取得できます。Ethereumアカウントのアドレスは、下準備で行った「 MetaMaskを用いてEthereumアカウントの作成 」で取得方法を説明しましたので、そちらを参考にしてください。

何も問題がなければ下記のコンパイルコマンドがうまく実行されると思います。(Warningが出るかと思いますが今回は大丈夫ですのでお気になさらず)

npx hardhat compile

スクリプトの作成

デプロイ用スクリプトの作成

scripts/ディレクトリにdeploy.jsを作成し、下記のコードを貼ってください。

async function main() {
    const MTT = await ethers.getContractFactory("MTT")
  
    // コントラクトのデプロイを行う
    const myMTT = await MTT.deploy()
    console.log("Contract deployed to address:", myMTT.address)
  }
  
  main()
    .then(() => process.exit(0))
    .catch((error) => {
      console.error(error)
      process.exit(1)
    })
  

トークン発行用スクリプトの作成

Ethereumブロックチェーン上のデータを扱う・コントラクトとのやり取りを簡単に行うことができるライブラリAlchemy Web3をインストールします。プロジェクトのルートディレクトリで行ってください。

npm install @alch/alchemy-web3

インストール後、scripts/ディレクトリにmint.jsを作成し、下記のコードを貼ってください。

require("dotenv").config()
const API_URL = process.env.API_URL
const PUBLIC_KEY = process.env.PUBLIC_KEY
const PRIVATE_KEY = process.env.PRIVATE_KEY
const INITIALSUPPLY = process.env.INITIALSUPPLY

const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(API_URL)

// コントラクトと対話するためのインターフェースのコントラクトABI
const contract = require("../artifacts/contracts/MyFT.sol/MTT.json")

// デプロイしたコントラクトのアドレスとコントラクトABIを使用してインスタンスを生成
const contractAddress = process.env.CONTRACT_ADDRESS;
const mttContract = new web3.eth.Contract(contract.abi, contractAddress)

async function mintMTT() {
    const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest");  // 自分のアカウントの最新のナンスの取得

    // トランザクションの作成
    const tx = {
        'from': PUBLIC_KEY,
        'to': contractAddress,
        'nonce': nonce,
        'gas': 500000,  // コントラクトを動かすために支払うETHの料金
        'data': mttContract.methods.mintMTT(PUBLIC_KEY, INITIALSUPPLY).encodeABI() // このトランザクションで実行したい計算・処理
    }

    // トランザクションを送信するために秘密鍵で署名
    const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
    signPromise
    .then((signedTx) => {
      web3.eth.sendSignedTransaction(
        signedTx.rawTransaction,
        function (err, hash) {
          if (!err) {
            console.log(
              "The hash of your transaction is: ",
              hash,
              "\nCheck Alchemy's Mempool to view the status of your transaction!"
            )
          } else {
            console.log(
              "Something went wrong when submitting your transaction:",
              err
            )
          }
        }
      )
    })
    .catch((err) => {
      console.log(" Promise failed:", err)
    })
}

mintMTT()require("dotenv").config()
const API_URL = process.env.API_URL
const PUBLIC_KEY = process.env.PUBLIC_KEY
const PRIVATE_KEY = process.env.PRIVATE_KEY
const INITIALSUPPLY = process.env.INITIALSUPPLY

 
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(API_URL)

// コントラクトと対話するためのインターフェースのコントラクトABI
const contract = require("../artifacts/contracts/MTT.sol/MTT.json")

// デプロイしたコントラクトのアドレスとコントラクトABIを使用してインスタンスを生成
const contractAddress = process.env.CONTRACT_ADDRESS;
const mttContract = new web3.eth.Contract(contract.abi, contractAddress)


async function mintMTT() {
    const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest");  // 自分のアカウントの最新のナンスの取得

    // トランザクションの作成
    const tx = {
        'from': PUBLIC_KEY,
        'to': contractAddress,
        'nonce': nonce,
        'gas': 500000,  // コントラクトを動かすために支払うETHの料金
        'data': mttContract.methods.mintMTT(PUBLIC_KEY, INITIALSUPPLY).encodeABI() // このトランザクションで実行したい計算・処理
    }

    // トランザクションを送信するために秘密鍵で署名
    const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
    signPromise
    .then((signedTx) => {
      web3.eth.sendSignedTransaction(
        signedTx.rawTransaction,
        function (err, hash) {
          if (!err) {
            console.log(
              "The hash of your transaction is: ",
              hash,
              "\nCheck Alchemy's Mempool to view the status of your transaction!"
            )
          } else {
            console.log(
              "Something went wrong when submitting your transaction:",
              err
            )
          }
        }
      )
    })
    .catch((err) => {
      console.log(" Promise failed:", err)
    })
}

mintMTT()

デプロイ

ついに作成したコントラクトをデプロイします!プロジェクトのルートディレクトリに戻り、下記のコマンドを実行してください。

npx hardhat run scripts/deploy.js --network ropsten

デプロイが正常に行われた場合、コンソールに下記のような出力があると思います。

Contract deployed to address: 0x81c587EB0fE773404c42c1d2666b5f557C470eED

この出力されたアドレスをコピーし、環境変数ファイル.envにCONTRACT_ADDRESSで追加してください。また、トークンの発行数を指定するINITIALSUPPLYも追加してください。最終的に下記のようになるはずです。

API_URL="https://eth-ropsten.alchemyapi.io/v2/your-api-key"
PRIVATE_KEY="your-metamask-private-key"
PUBLIC_KEY="your-public-account-address"
CONTRACT_ADDRESS="deploy-smart-contract-address"
INITIALSUPPLY=100000000000000000

Etherescanで検索すると、コントラクトが正常にデプロイされたことが確認できると思います。検索後、下のスクショのような画面が出ていれば成功です!!

トークンの発行

デプロイも成功したので、トークンを発行してみましょう!下記のコマンドを実行してみてください。

node scripts/mint.js

スクリプトが正常に実行された場合、下記の出力がされると思います。

The hash of your transaction is:  0xaf0035fb427a8f692027f6b4a8418413ea8e2120ffba848e01cecdb8242b1ea0
Check Alchemy's Mempool to view the status of your transaction!

出力されたトランザクションのアドレスをコピーして、Etherscanで検索すると、下のスクショのような画面でStatusがSuccessになっていれば成功です!!

最後に、MetaMask上で自身のEthereumアカウントにトークンが発行されたのか確認しましょう!MetaMaskのアカウントページのやや下のところにImport tokensがあるのでクリックしてください。カスタムトークンの追加画面に移ったら、「トークンコントラクトアドレス」にデプロイしたコントラクトのアドレスを入力してください。(環境変数のPUBLIC_KEYのことです)入力後、追加ボタンを押すと恐らく下のスクショのように発行したトークンが表示されるようになります。

おめでとう!ついに自分だけのトークンを作ることができた!!

最後に

お疲れさまでした!恐らく色々と分かりづらい点や気になる点があると思いますが、ここまで読んでくださりありがとうございました。また、ブロックチェーン・トークンについて説明できていない部分が多々ありますので、もし興味・気になる点がありましたら下記のリンクが参考になれば幸いです。また新たな知識・技術をインプットしましたら記事にしたいと思いますのでよろしくお願いします!!

参考文献