Yenom Yuiki さんのBITBOX解説チュートリアル

Yenom YuikiさんによるBITBOX解説チュートリアル


Yenomの開発者、Yuikiさんのビットコインキャッシュアプリの作り方チュートリアル。

今回はビットコインキャッシュアプリを簡単に作れるライブラリ、BITBOXを使って、ビットコイントランザクションを検知するアプリのチュートリアルを書いて頂きました。
Yenomさんは「Bitcoin」をテーマにしたエンジニア向けのハッカソン「Tokyo Bitcoin Hackathon」を開催していて、Bitcoinを勉強したい、プロダクトを作ってみたいという方へ向けた素晴らしい取り組みになっています。第2回目の開催は9/22(土) – 9/23(日)に行われました。結果発表はこちらです。


@yuikijp

概要

bitcoin.com が開発しているBITBOXというBCHを用いたアプリの開発の支援をするライブラリを用いて、ユーザが入力したアドレス宛のトランザクションを検知するというReactアプリを作成してみましょう。

目的

  • ブロックチェーン上の情報を用いてアプリを作れるようになる
  • BITBOXの使い方を知る

要件

  • ユーザは任意のBCHのメインネットアドレスをフォームから登録できる
  • BITBOXを用いてソケット接続し、最新のトランザクションを受け取る
  • 受け取ったトランザクションが登録したアドレス宛のものであれば表示する

注意

Reactの開発経験が無い場合、このチュートリアルを開始する前に、公式のReactチュートリアルを修了させておくことを推奨します。


セットアップ


プロジェクトの作成

create-react-app を用いてReactプロジェクトを作成してください。

$ npx create-react-app bitbox-watch-js

$ cd bitbox-watch-js

BITBOXのインストール

yarn を用いてbitbox-cli をインストールします。

$ yarn add bitbox-cli

いざ開発

全体のソースコードは以下にて閲覧することができます。

App.js

セットアップ

まず、BITBOXとソケット通信のためのオブジェクトを作成します。

import * as BITBOXCli from 'bitbox-cli/lib/bitbox-cli'

const BITBOX = new BITBOXCli.default()
const socket = new BITBOX.Socket()

constructor

App#constructor ではstateの初期値を設定します。 stateには登録済みアドレスのリスト、アドレス登録フォームの入力値、登録済みアドレス宛のトランザクションのリストの3つがあります。

constructor(props) {
  super(props)

  this.state = {
    addrs: [],
    input: '',
    txs: []
  }
}

render

主にアドレス登録フォームや登録済みアドレス、登録済みアドレス宛のトランザクションを表示します。

render() {
  return (
    <div>
      <h1>BCH Transaction Watcher Powered by BITBOX</h1>
      <h2>Watching Addresses</h2>
      <form onSubmit={this.handleSubmit}>
        <input
          type='text'
          value={this.state.input}
          onChange={this.handleChangeText}
          placeholder='Watch address [Enter]'
        />
      </form>
      <ul>
        {this.state.addrs.map((addr, idx) =>
          <li key={idx}><p>{addr}</p></li>
        )}
      </ul>
      <h2>Watching transactions</h2>
      <ul>
        {this.state.txs.map((tx, idx) => 
          <li key={idx}><Transaction id={tx.format.txid} /></li>
        )}
      </ul>
    </div>
  )
}

handleChangeText

フォームの値をstateで管理するようにしています。

handleChangeText = (event) => 
  this.setState({
    input: event.currentTarget.value
})

handleSubmit

フォームに入力された値がBCHのメインネットのアドレスの形式であるかをBITBOX.Address.isMainnetAddressを用いてチェックしています。 問題がなければ、stateにそのアドレスを追加します。

handleSubmit = (event) => {
  event.preventDefault()

  const input = this.state.input

  try {
    // BCHのメインネットのアドレスであるか
    if (!BITBOX.Address.isMainnetAddress(input)) {
        return
    }
  } catch (e) {
    return
  }

  // 既に登録されていないか
  if (this.state.addrs.indexOf(input) >= 0) {
    return
  }

  this.setState({
    addrs: this.state.addrs.concat(input),
    input: ''
  })
}

componentDidMount

componentDidMountのタイミングで、BCHのAPIサーバとソケット通信を開始します。 ここでは新規トランザクションを逐次受け取り、App#handleNewTx に渡すようにしています。

componentDidMount() {
  socket.listen('transactions', this.handleNewTx)
}

handleNewTx

ソケット通信で受け取ったトランザクションの各アウトプットのアドレスが、ユーザによって登録されているかを検知し、されていればstateにそのトランザクションを追加します。

handleNewTx = (msg) => {
  const tx = JSON.parse(msg)

  // トランザクションのアウトプットが指すアドレスをリストにする
  const txAddrs = tx.outputs
    .map((output) => output.scriptPubKey.addresses)
    .reduce((acc, x) => acc.concat(x, []))

  // 登録済みアドレスに存在するかチェックする
  const found = this.state.addrs
    .map(addr => BITBOX.Address.toLegacyAddress(addr))
    .filter(addr => txAddrs.indexOf(addr) !== -1).length > 0

  // 存在しなければ何もしない
  if (!found) {
    return
  }

  this.setState({
    txs: this.state.txs.concat(tx)
  })
}

Transaction.js

クリックすると explorer.bitcoin.com にてpropsとして渡されたトランザクションの詳細を確認できるコンポーネントです。

import * as React from 'react'

const Transaction = (props) => (
  <a href={`https://explorer.bitcoin.com/bch/tx/${props.id}`} target="_blank">{props.id}</a>
)

export default Transaction

完成!

以下のコマンドを実行して実際に動かしてみましょう。

$ npm run start

フォームに任意のBCHのメインネットアドレスを入力してEnterを押下し、アドレスを登録します。 その後、そのアドレス宛に送金してみてください。 実際に当該トランザクションが検知されることがわかります。