Edited at

Bitcoin JSON-RPCを叩く

JavaScriptで bitcoin/bitcoin (Bitcoin core)の API を叩こうとしたとき、検索すると、いくつかの定番っぽい npm がヒットしますが、どれも古すぎてメンテナンスもされていません。

現在の Bitcoin core のバージョンは0.17ですが、0.16以前とは API 仕様に違いがあります。ウォレット関連で、いくつものAPIが deprecated になって、新しい API も新設されています。

というか、Bitcoin core の API を JSON-RPC で叩くのはアホみたいに簡単なのでわざわざライブラリ、それも古代に書かれたものを使う必要性はありません。


Bitcoin core JSON-RPC

bitcoindのrpc用のポートにHTTP POSTを投げるだけです。

筆者の好みでrequest-promiseで書くと以下のようなコードになります。

const dispatch = async (host, rpcport, user, pass, method, ...params) => {

const { result, error } = JSON.parse(
await rp(`http://${host}:${rpcport}`, {
method: 'POST',
body: JSON.stringify({ method, params }),
auth: { user, pass },
}).catch(e => {
if (e.statusCode) {
return JSON.stringify({ error: JSON.parse(e.error).error })
} else {
return JSON.stringify({ error: e.error })
}
})
)
}

たとえば const { result } = await dispatch('localhost', 18332, 'u', 'p', 'help')みたいに叩くだけです。いとも簡単ですね。


接続情報を毎回返さなくてもいいようにする

毎回、hostだのを書くのは大変なのでクライアントというものを作ってみます。

export const createClient = ({ host, rpcport, user, pass }) => {

return async (method, ...params) => {
...
}
}

const client = createClient(.....)
client('help')

これだと const client = createClient({host: 'localhost', ...}) でクライアントを作成し、クライアントの引数にメソッドとパラメータを渡すだけです。


メソッドを引数ではなくてメソッドとして叩けるようにする

既存のライブラリだとクライアントのメソッドとして、client.help() とか client.getBlockchainInfo() のようなたたき方ができるので、それを実現します。

export interface Client {

[method: string]: (...args) => Promise<any>
}

まずは、Client型の定義をします。もっともこれはTypeScriptの話なので、JavaScriptだけで生きていく決意をしている方には不要です。

export const createClient = ({ host, rpcport, user, pass }) => {

const client: Client = new Proxy({}, {
get: (target: any, method: string) => {
if (method === 'then' || method === 'catch') {
return target[method] // 実質 undefined
}
method = method.toLowerCase()
return async (...params) => {
// 省略
}
}
})
return client
}

ECMAScriptで定義されているProxyを使っています。Proxyオブジェクトは、メンバーへのアクセスを乗っとることができます。getハンドラを書くと、メンバー変数の読み出し、メソッドの呼び出しを制御ます。

あと、既存のライブラリは、キャメルケースの区切りがいまいちよくわからない感じがあってクソダルいのと、もともとBitcoin coreのJSON-RPCのmethod名は、全部小文字と決まっているので、.toLowerCase()をしています。

ちなみにthen catch を特別扱いしているのは、特定のケースでなぜかクライアント自体のthenが叩かれることがあるっぽいからです。

return new Promise(resolve => {

...
resolve(client)
})

みたいなコードを書いた時に、なるようです。おそらく、resolveする時だかthenを実際に呼び出すときだかに、resolveする変数がthenableかどうかチェックしているためでしょう。きっと。

既存のライブラリのように変換テーブルを持っておくのは手ですが、それだとAPIが追加されたときにいちいちテーブルに追加する必要があります。


まとめ


  • Bitcoin coreのJSON-RPCを叩くのにライブラリはいらん


    • むしろ古すぎて弊害が大きい

    • どうせ簡単



  • 関数を返す関数で関数型プログラミング気分


    • Proxy 便利



最後に筆者が実際に使っているTypeScriptのコードは、gist に貼り付けてあります。