特定のERC20トークンの保有者のみ閲覧可能のサイトを構築する(モバイル版metamask対応もさせる)

#初めに
vue.jsを用いて特定のERC20トークンの有無で表示を切り替えるwebサイトを構築します。
簡便な作りのため厳密な認証を行っているわけではないので注意してください。
metamaskがインストールされていることを前提としています。

完成品のgithub:
https://github.com/ramo798/erc20_authentication_vue_app

デプロイしたもの:
https://erc20-authentication-vue-app.web.app/
モバイル用のディープリンク:
https://metamask.app.link/dapp/erc20-authentication-vue-app.web.app/

#流れ
以下の順番で行っていきます。

1. metamaskとの接続を行いウォレットのアドレスの取得
2. アドレスを元にトークンの所有量を調べる
3. vue.jsで構築する

##1. metamaskとの接続を行いウォレットのアドレスの取得
```javascript:
async function getAccount() {
  const accounts = await window.ethereum.request({
    method: "eth_requestAccounts",
  });
  return accounts[0];
}

if (window.ethereum) {
  getAccount().then((res) => {
    console.log(res);
  });
} else {
  window.addEventListener("ethereum#initialized", this.handleEthereum, {
    once: true,
  });
  setTimeout(this.handleEthereum, 3000);
}
```

metamaskがインストールされているとEthereum Provider APIが使用可能なので`window.ethereum`にてmetamask有無を調べている。
インストールされてる場合に、`getAccount()`が実行され、ブラウザとmetamaskアカウントの接続を行い、ウォレットのアドレスを取得する。
`window.ethereum.request`については[ドキュメント](https://docs.metamask.io/guide/getting-started.html#connecting-to-metamask "ドキュメント")を参照してください。

##2. アドレスを元にトークンの所有量を調べる
ウォレットのアドレスを取得できたのでトークンの保有量を調べていきます。
ERC20には`balanceOf`という関数が用意されており、それを利用して調べていきます。
最終的に以下の`getTokenBalance()`はトークンの保有数を整数で返します。

```javascript:
function getTokenBalance(address) {
  let minABI = [
    {
      constant: true,
      inputs: [{ name: "_owner", type: "address" }],
      name: "balanceOf",
      outputs: [{ name: "balance", type: "uint256" }],
      type: "function",
    },
    {
      constant: true,
      inputs: [],
      name: "decimals",
      outputs: [{ name: "", type: "uint8" }],
      type: "function",
    },
  ];

  const web3 = new Web3();
  web3.setProvider(
    new web3.providers.HttpProvider(
      "https://ropsten.infura.io/v3/674fa8842b484b24987e1267a15f06cc"
    )
  );

  const WALLET_ADDRESS = address;
  const CONTRACT_ADDRESS = "0xc2fdf34114fccB32dF11f0852F61b9165Ec872fF";

  const contract = new web3.eth.Contract(minABI, CONTRACT_ADDRESS);

  var balance_result = contract.methods
    .balanceOf(WALLET_ADDRESS)
    .call()
    .then((res) => {
      var balance = contract.methods
        .decimals()
        .call()
        .then((decimal) => {
          return res / 10 ** decimal;
        });
      return balance;
    })
    .then((balance) => {
      return balance;
    });
  return balance_result;
}
```
スマートコントラクトの関数を呼び出すにはweb3というライブラリが必要なので適宜インストールし、インポートします。
web3からスマートコントラクトの関数を呼び出すには、スマートコントラクトがどのような関数を持っているかを記述されたABIというものが必要なので宣言します。ここでは、`minABI`として宣言し、今回使う関数である`balanceOf` `decimals`のみ記述しておきます。
`balanceOf`はトークンの所有量を返す関数で、`decimals`はトークンが使用する小数点以下の桁数を返す関数です。

スマートコントラクトの関数を実行する場合は下記のように実行します。

```javascript
const contract = new web3.eth.Contract(ABI, CONTRACT_ADDRESS);
contract.methods.関数名().call()
```
##3. vue.jsで構築する
以上を元にvueにて、トークンを1以上を持ってる場合に"hello,world"を表示するようにします。

```html:HTML
  <div id="app">
    <div>
      <img alt="Vue logo" src="./assets/logo.png" />
      <p>{{ walletAddress }}</p>
      <p>所持数は {{ howmany }} TCKMです。</p>
    </div>
    <div v-if="load">
      <div v-if="howmany > 0">
        <p>HELLO,WORLD!</p>
      </div>
      <div v-else>
        <p>You dont have token.</p>
      </div>
    </div>
  </div>
```
```javascript:script
import Web3 from "web3";

export default {
  name: "App",
  components: {},
  data: () => ({
    walletAddress: null,
    howmany: null,
    load: false,
    sec_load: false,
  }),
  mounted() {
    if (window.ethereum) {
      this.getAccount().then((res) => {
        this.walletAddress = res;
        this.getTokenBalance(res)
          .then((res) => {
            this.howmany = res;
          })
          .finally(() => {
            this.load = true;
          });
      });
    } else {
      window.addEventListener("ethereum#initialized", this.handleEthereum, {
        once: true,
      });
      setTimeout(this.handleEthereum, 3000);
    }
  },
  methods: {
    handleEthereum: () => {
      const { ethereum } = window;
      if (ethereum && ethereum.isMetaMask) {
        // console.log("Ethereum successfully detected!");
        console.log(ethereum.selectedAddress);
        const myaddress = ethereum.selectedAddress;
        return myaddress;
      } else {
        console.log("Please install MetaMask!");
      }
    },
    getAccount: async function() {
      const accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      // console.log(accounts);
      return accounts[0];
    },
    getTokenBalance: (address) => {
      const web3 = new Web3();
      web3.setProvider(
        new web3.providers.HttpProvider(
          "https://ropsten.infura.io/v3/674fa8842b484b24987e1267a15f06cc"
        )
      );
      const WALLET_ADDRESS = address;
      const CONTRACT_ADDRESS = "0xc2fdf34114fccB32dF11f0852F61b9165Ec872fF";
      let minABI = [
        {
          constant: true,
          inputs: [{ name: "_owner", type: "address" }],
          name: "balanceOf",
          outputs: [{ name: "balance", type: "uint256" }],
          type: "function",
        },
        {
          constant: true,
          inputs: [],
          name: "decimals",
          outputs: [{ name: "", type: "uint8" }],
          type: "function",
        },
      ];
      const contract = new web3.eth.Contract(minABI, CONTRACT_ADDRESS);

      var balance_result = contract.methods
        .balanceOf(WALLET_ADDRESS)
        .call()
        .then((res) => {
          var balance = contract.methods
            .decimals()
            .call()
            .then((decimal) => {
              return res / 10 ** decimal;
            });
          return balance;
        })
        .then((balance) => {
          return balance;
        });
      return balance_result;
    },
  },
};
```
#表示結果
トークンを所持してる場合(PC)
![スクリーンショット 2020-12-13 023051.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285408/11480ebf-480f-6b71-ff21-46e8e7937a94.png)
トークンを所持してる場合(iPhone)
![S__36061186.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285408/0f5d74a4-f7e4-5d00-020b-4b5a0d77b202.jpeg)
トークンを所持していない場合(PC)
![スクリーンショット 2020-12-13 023018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285408/c9bdaca0-f68e-2d06-4e8d-0463cb083ecc.png)