エクスプローラのAPIを使ってアドレスの特定日時の残高を取得する(Bitcoin編)

# はじめに

最近、ブロックチェーンエクスプローラのAPIを触る機会があったので、[アドベントカレンダー](https://qiita.com/advent-calendar/2021/blockchain)もあることだし、知見を書いてみることにしました。

# Bitcoin Blockchain ExplorerのAPI

APIはたくさんあります。

Bitcoin系に対応したもの、Bitcoinだけに対応したもの、Ethereumに対応したものなど様々です。

今回はBitcoinアドレスの特定日時の残高を取得してみたいと思います。

https://blockstream.info/

blockstream.infoのAPIを使ってみましょう。

↓API仕様はこちらです。無料で使えます。

https://github.com/Blockstream/esplora/blob/master/API.md

↓なお、blockchain.comのAPIは無料で使いやすいのですが、すぐにrate limitがかかりました(苦笑)

https://www.blockchain.com/api

Limitsを増やすためにAPI Keyの申請をしましたが未だにメールなし(苦笑)

↓BlockCypherのAPIも使いやすいものの、無料だとlimitでしばらく使えなくなります。。

https://www.blockcypher.com/dev/bitcoin/?shell#introduction

# blockstream.infoのAPI

秒間、分間いくつみたいなlimitはあっても、他のAPIのように大量に使ったら何日経っても使えないということはないです。

他のAPIであるアドレスの残高機能はないものの、アドレスに紐づくTXを取得していくことによって、アドレス残高を取得できます。

```getBalance.js
import fetch from 'node-fetch';

const bitcoin_address = process.argv[2];

import fs from 'fs';

var balance = 0;

var termend_block = 702951;

var url2 = "";
var response2 = "";
var json2 = "";

async function getTxMovement(bitcoin_address, balance)  {
  try {
    var url = `https://blockstream.info/api/address/${bitcoin_address}/txs`;
    var response = await fetch(url);
    var json = await response.json();

    for(var i=0; i<json.length; i++){
      if (json[i].status.confirmed == true) {
        if (json[i].status.block_height <= termend_block) {
          for (var j=0; j<json[i].vin.length; j++){
            if (!json[i].vin[j].is_coinbase){
              if (json[i].vin[j].prevout.scriptpubkey_address == bitcoin_address) {
                balance = parseInt(balance) - parseInt(json[i].vin[j].prevout.value);
              }
            }
          }
          for (var k=0; k<json[i].vout.length; k++){
            if (json[i].vout[k].scriptpubkey_address == bitcoin_address) {
              balance = parseInt(balance) + (parseInt(json[i].vout[k].value))
            }
          }
        }
      }
    }
    if (json.length == 25) {
      last_seen_txid = json[24].txid;
      do {
        var {balance, last_seen_txid} = await getMoreMovement(bitcoin_address, balance, last_seen_txid);
      } while (last_seen_txid)
    }
		return balance;
	} catch (error) {
		console.log(error);
	}
}

async function getMoreMovement(bitcoin_address, balance, last_seen_txid) {
  try {
    url2 = `https://blockstream.info/api/address/${bitcoin_address}/txs/chain/${last_seen_txid}`;
    response2 = await fetch(url2);
    json2 = await response2.json();
    for(var i=0; i<json2.length; i++){
      if (json2[i].status.confirmed == true) {
        if (json2[i].status.block_height <= termend_block) {
          for (var j=0; j<json2[i].vin.length; j++){
            if (json2[i].vin[j].prevout.scriptpubkey_address == bitcoin_address) {
              balance = parseInt(balance) - parseInt(json2[i].vin[j].prevout.value);
            }
          }
          for (var k=0; k<json2[i].vout.length; k++){
            if (json2[i].vout[k].scriptpubkey_address == bitcoin_address) {
              balance = parseInt(balance) + (parseInt(json2[i].vout[k].value))
            }
          }
        }
      }
    }
    if (json2.length == 25) {
      last_seen_txid = json2[24].txid;
    } else {
      last_seen_txid = null;
    }
    return {balance, last_seen_txid};
  } catch (error) {
		console.log(error);
	}
};

getTxMovement(bitcoin_address, balance).then(balance => {
  console.log(bitcoin_address + " " + balance)
  fs.appendFile('btc_balance.tsv', `${bitcoin_address}\t${balance}\n`, (err) => {
    if (err) throw err;
  });
});
```

```
```

node-fetchは簡単に言えば、curlをnodeで扱うためのライブラリです。

requireで読み込むことができません。importで読み込んでください。

importで読み込むとpackage.jsonに以下の記述が必要です。

```package.json
"type": "module"
```

```
var termend_block = 702951;
```
今回、日本時間(JST)で2021/10/1 00:00の残高を取得してみたいと思います。

2021/10/1 00:00(JST)の直前のブロックが702951です。(blockstream.infoではGMT表示になってます)

https://blockstream.info/block/0000000000000000000b3fe14a0f2b17fae62995505478997d5f0d4343d14f18

最新の残高を出したい場合は、ここの処理をカットするか、9999999など馬鹿でかい値にしてあげます。

https://github.com/Blockstream/esplora/blob/master/API.md#get-addressaddresstxs

GET /address/:address/txs
を用いたgetTxMovementはアドレスに紐づくTXを取得して、そのアドレスがInputがある場合は、使用済み(Spent)なので残高からマイナス、
Outputにある場合は、残高にプラスします（その後に使われた場合、後のTXでマイナスすることになります)

なお、APIによっては最新のTXから時系列では降順に出すものが多いですが、
blockstream.infoでは、最古のものから時系列に昇順です。

```
if (!json[i].vin[j].is_coinbase)
```

これは、Inputがcoinbaseでないという条件です。稀ですが、coinbaseTxでマイニング報酬を得ているアドレスもあり、
その場合、Inputの形式が通常とは異なるので考慮する必要あります。

blockstream.infoで、アドレスに紐づくTXは1回に最大25しか取得できないので、最後のTXを用いて、
GET /address/:address/txs/chain[/:last_seen_txid]によって、続きの26個目以降のTXを取得できます。

https://github.com/Blockstream/esplora/blob/master/API.md#get-addressaddresstxschainlast_seen_txid

getMoreMovement内で使用しています。
ループで回せば、アドレスに紐づくTXがいくつであろうと、全TXを取得できます。

中身はgetTxMovementと被りますが、リファクタリングする時間がなかったので、ご容赦ください。

```
const bitcoin_address = process.argv[2];
```

とありますように、対象のアドレスはコンソールから読み込みます。

下記のようなファイルを用意してあげます。特定日時残高を取得したいアドレスを改行して1行1アドレスで記載します。

```btc_address.tsv
bc1qmexh7kfrgpejv68v4dym8mr32ullvpn4kw8te3zxlrqjrksz0jks9z60pf
18HLB1NZ1YrWXmT2ChqoTKGzEs3HwpMa89
16FCLH1Lqr22NGRZ36D3nPFRAPstHShCXa
38k4uvZcL5v72RGk3GGHvseSxY6Y6eStDJ
```

APIを連打すると、rate limitsで詰まります。
jsはsleepがなく、実装しようにも非同期処理が面倒なので、ここはshellに任せます。

```balance.sh
#!/bin/bash
while read line
do
  node getBalance.js $line
  sleep 3
done < ./btc_address.tsv
```

実行すると、tsvに書き出されます。

```btc_balance.tsv
bc1qmexh7kfrgpejv68v4dym8mr32ullvpn4kw8te3zxlrqjrksz0jks9z60pf	0
18HLB1NZ1YrWXmT2ChqoTKGzEs3HwpMa89	1955710
16FCLH1Lqr22NGRZ36D3nPFRAPstHShCXa	0
38k4uvZcL5v72RGk3GGHvseSxY6Y6eStDJ	2990281691
```

CSVではなく、TSVにしたのはエクセル等にすぐ貼れるためです。

エクスプローラで色々調べたいけど、手動ではとても無理な場合は、エクスプローラのAPIが自前のフルノードがなくても手軽で便利です。

フルノードにはない欲しい機能が結構あったりしますし。

エクスプローラのAPIを活用してみてください。