飽き性の頭の中

0x APIを活用してERC20トークンの残高を取得する方法|Infura、TypeScript、Web3

0x APIを活用してERC20トークンの残高を取得する方法|Infura、TypeScript、Web3

tawachan
tawachan

こんにちは、たわです。0x API を使用したときのメモです。

ERC20 の残高を取得する

Ethereum 上でトークンを発行するときの規格として ERC20 があります。

その規格を踏襲しているトークンであれば同じ API で操作できるので、便利です。

ですが、実際に各トークンを発行しているコントラクトのアドレスが別々なので、それをまず把握して、そのコントラクトに対してリクエストを発行する必要があります…。

これが地味にめんどくさいのですが、0x API を使って少し楽になるような実装をしてみました。

実装の流れ

それでは実際にやってみた実装の流れです。

0x API でトークンの情報を取得

0x は Ethereum 上のトークンの交換をスムーズに行うために開発されているプロトコルです。

この 0x が実際に処理を行うために必要なロジックや便利ツールを用意してくれているのでそれを活用しました。

用意された便利ツールの中で、最近公開された 0x API を使いました。

ブロックチェーンは意識せずに普通に情報が取得できるエンドポイントになっているので詳しくなくても取得できます。

その中に、対応したトークンの情報を返すエンドポイントがあります。

こんな感じでリクエストを飛ばすことができます。

   const queries = {};
const qs = new URLSearchParams(queries);
const url = new URL("https://api.0x.org/swap/v0/tokens" + "?" + qs);

const response = await fetch(url);
const json = await response.json();

すると、レスポンスはこのように返ってきます。

   [
  {
    symbol: "DAI",
    address: "0x6b175474e89094c44da98b954eedeac495271d0f",
    name: "Dai Stablecoin",
    decimals: 18,
  },
  {
    symbol: "REP",
    address: "0x1985365e9f78359a9B6AD760e32412f4a445E862",
    name: "Augur Reputation",
    decimals: 18,
  },
  // ...
];

これでトークンの情報とアドレスがわかります。

これらの情報は変更されることは基本的にないと思うので、その都度 API を飛ばさずローカルに定数として置いてもよいのかもしれません。

トークンごとに残高を取得する

上で取得したトークンのアドレスを使って順番に残高を取得しています。

同じ Ethereum 上の自分のウォレットの残高を見たいだけなのに、トークンの数だけリクエストを飛ばさないといけないのがどうも不便な感じがしますが、現状仕方なさそうです。

コードは全体としてこんな感じになります。元々はクラスにしたりしていたのですが、説明のために 1 つの関数の中に全部盛りにしてしまっています。

   public fetchTokenBalance = async () => {
    const ERC20_ABI = [
      // balanceOf
      {
        constant: true,
        inputs: [{ name: "_owner", type: "address" }],
        name: "balanceOf",
        outputs: [{ name: "balance", type: "uint256" }],
        type: "function"
      }
    ];

    const decodeAmount = (num: BigNumber, decimalPlace: number): BigNumber => {
      return num.div(10 ** decimalPlace);
    };

    const address = process.env.WALLET_ADDRESS;
    const hostEthreumNode = process.env.INFURA_URL;

    const provider = new Web3.providers.HttpProvider(hostEthreumNode!);
    const web3 = new Web3(provider as any);

    const balance = (
      await Promise.all(
        Object.keys(ERC20_TOKENS).map(async k => {
          const t = ERC20_TOKENS[k];
          const contract = new web3.eth.Contract(ERC20_ABI as any, t.address);
          const balance = await contract.methods
            .balanceOf(address)
            .call({ from: address });
          return {
            token: t.symbol,
            balance: balance,
            decimals: t.decimals
          };
        })
      )
    ).reduce<Record<string, number>>((prev, cur) => {
      const amount = decodeAmount(new BigNumber(cur.balance), cur.decimals);
      prev[cur.token] = amount.toNumber();
      return prev;
    }, {});
    console.log({ balance });
  };

順番に説明していきます。

Infura でアカウント作成

本来、Ethereum のノードを自分で用意する必要がありますが、Infura はそれをサービスとして提供しています。

なので、Infura でアカウントを作成し、Infura 経由で Ethereum へアクセスできます。最低限の開発段階であれば無料の範囲内で足りると思います。

自分用のエンドポイントが発行されます。それを環境変数(INFURA_URL)にセットしています。

残高を確認したいウォレットのアドレスを確認

残高を確認したいウォレットのアドレスも環境変数(WALLET_ADDRESS)にセットしましょう。

ABI を用意

ABI はバイナリを呼び出すための型定義ファイルのようなものです。詳細は以下がわかりやすかったです。

今回は ERC20 規格なので、インタフェースも共通です。すべて使ってもいいですが、使用するbalanceOfメソッドの定義のみを上のコード例では入れています。

   // この部分
const ERC20_ABI = [
  // balanceOf
  {
    constant: true,
    inputs: [{ name: "_owner", type: "address" }],
    name: "balanceOf",
    outputs: [{ name: "balance", type: "uint256" }],
    type: "function",
  },
];

各トークンコントラクトアドレスにリクエスト

情報は揃ったので実際に、残高を取得していく処理です。

先ほど取得したトークンの情報はERC20_TOKENSに入れている前提でコードが書かれています。

それを順番に取り出し、並列で API リクエストを投げています。

   const contract = new web3.eth.Contract(ERC20_ABI as any, t.address);
const balance = await contract.methods.balanceOf(address).call({ from: address });

web3 を使えば、こんな感じで残高を取得できます。

残高の桁数を直す

あとは、返ってきた情報を必要な形に加工するのですが、残高の扱いに少しくせがあるので注意です。

たとえば、ZRX を0.3持っている場合、残高の数値は3000000000000000(3 と 0 が 17 個)になっています。

なぜかというと、TOKEN の情報の中のdecimalsに書いてあるように有効桁数が 18 桁で、残高は最小桁数で表現されるという仕様だからのようです。

つまり、0.00000000000000001 が最小単位(= 1)として表現されます。少しややこしいですね…。

なので、decodeAmountに書かれているように、10 の有効桁数乗で割るという処理を入れています。

こんな感じになりました

今回は、reduce関数でトークンのシンボルがキーになるオブジェクトに直したのでこんな感じのデータになりました。

   {
  DAI: 0,
  WETH: 0.037725,
  ZRX: 0,
  LINK: 1.6150693860197611,
  REN: 0,
  ...
}

数としてかなりあるので、必要なトークンだけ取得するように処理を書き直すとよりよいかもしれません。

まとめ

ERC20 のトークンの残高を取得しました。0x API は便利なのですが、ものによっては Ethereum ノードにアクセスしないといけなかったりと少しややこしい感じがしています。

知見もあまりネット上になくかなり探り探りやっているので、また雑に色んなものを公開していきたいと思います。ベターな方法があればぜひ教えていただければ幸いです。

関連記事