ここでは、node.js を用いた Bitcoin の送金処理を解説する。
● 環境設定(environment)
使用するライブラリを定義
require('dotenv').config(); const BN = require('bignumber.js'); const bitcore = require('bitcore-lib'); const explorers = require('bitcore-explorers'); const insight = new explorers.Insight(); const Client = require('bitcoin-core'); const WAValidator = require('wallet-address-validator'); const _ = require('lodash'); const client = new Client({ host: process.env.BITCOIN_NODE_HOST, network: process.env.BITCOIN_NODE_NETWORK, username: process.env.BITCOIN_NODE_USER, password: process.env.BITCOIN_NODE_PASS, port: process.env.BITCOIN_NODE_PORT, }); var Bitcoin = function() { };
※bitcore-explorers(Insight)
ブロックチェーンの状態を問い合わせるために、異なるWeb APIへのHTTPリクエストを実装するBitcore用の実行ファイル。
アドレスのUTXO(Unspent Transaction Output:未使用トランザクションアウトプット)を取得およびBitcoinネットワークにトランザクションをブロードキャストするためのインタフェース。
● 残高取得(getBalance)
承認前トランザクションの送受信量(受信:プラス、送信:マイナス)との合計を算出
Bitcoin.prototype.getBalance = function(address) { return new Promise((resolve, reject) => { insight.address(address, function (err, addrinfo) { const finalBalance = addrinfo.balance + addrinfo.unconfirmedBalance; const balance = addrinfo.balance > finalBalance ? finalBalance : addrinfo.balance; if (err) { reject(err.message); } resolve({ address: address, balance: new BN(balance).dividedBy(1e8).toNumber(), finalBalance: new BN(finalBalance).dividedBy(1e8).toNumber(), }); }); }); };
● トランザクション取得(getTransaction)
insight を用いてトランザクション情報を取得
Bitcoin.prototype.getTransaction = function(transactionId) { return new Promise((resolve, reject) => { insight.getTransaction(transactionId, (err, tx) => { if (err) { reject(err.message); return; } let from = []; for (let i = 0; i < tx.vin.length; i++) { if (!(tx.vin[i].addr)) continue; from.push(tx.vin[i].addr); } let to = []; for (let i = 0; i < tx.vout.length; i++) { if (!(tx.vout[i].scriptPubKey.addresses && tx.vout[i].value)) continue; to.push({ address: _.head(tx.vout[i].scriptPubKey.addresses), amount: tx.vout[i].value }); } var arr = { tx_hash: tx.txid, from: from, to: to, fee: tx.fees, confirmation: tx.confirmations, executed_at: tx.time }; resolve(arr); }); }); };
● 送金処理(send)
署名を行い送金処理を実行
Bitcoin.prototype.send = function(signature) { return new Promise((resolve, reject) => { client.sendRawTransaction(signature).then(tx_hash => { resolve(tx_hash); }).catch(err => { reject(err.message); }); }); };
● 送金パラメーター作成(send_param)
入力データから送金に必要な情報を取得し送金パラメータを作成
Bitcoin.prototype.send_param = function(fromaddress, private_key, toaddress1, amount1, toaddress2, amount2, fee) { return new Promise(async (resolve, reject) => { let preparedTransaction; const amountNum1 = Math.floor(new BN(amount1.toString()).multipliedBy(1e8).toNumber()); const amountNum2 = Math.floor(new BN(amount2.toString()).multipliedBy(1e8).toNumber()); const feeNum = Math.floor(new BN(fee.toString()).multipliedBy(1e8).toNumber()); try { const transactionBuilder = function(transaction) { return transaction .to(toaddress1, amountNum1) .to(toaddress2, amountNum2) .change(fromaddress) .sign(private_key); }; preparedTransaction = await this.prepareTransaction(fromaddress, transactionBuilder); } catch (err) { reject(err); return; } try { const serializedTransaction = preparedTransaction .fee(feeNum) .sign(private_key) .serialize(); client.sendRawTransaction(serializedTransaction).then(txId => { resolve({ transactionId: txId, fee: fee }); }).catch(err => { reject(err); }); } catch (err) { reject(err); } }); };
アドレス、数量を並べて書く(toaddress1, amount1, toaddress2, amount2)ことで、一つのトランザクションで複数のアドレスに送金することが可能となる。
● 未使用トランザクション取得(prepareTransaction)
insight を用いて UTXO を取得
Bitcoin.prototype.prepareTransaction = function(fromaddress, builder) { return new Promise((resolve, reject) => { insight.getUnspentUtxos(fromaddress, function (err, utxos) { if (err) reject(err.message); const transaction = new bitcore.Transaction(); resolve(builder(transaction.from(utxos))); }); }); };
● 手数料取得(getFee)
手数料を取得し結果を env ファイルに書き出し
Bitcoin.prototype.getFee = function() { return process.env.BITCOIN_FEE; };
● アドレス検証(isAddress)
アドレスの長さが正しいかを検証
Bitcoin.prototype.isAddress = function(address) { if (!(WAValidator.validate(address, 'BTC'))) return false; return true; };
● プライベートキー検証(isPrivateKey)
プライベートキーの長さが正しいかを検証
Bitcoin.prototype.isPrivateKey = function(rawPrivateKey, expectedAddress = null) { const privateKey = new bitcore.PrivateKey(rawPrivateKey, 'mainnet'); if (expectedAddress) { const address = privateKey.toAddress(); if (!(expectedAddress === address.toString())) { return false; } } return true; };
● アドレス生成(getAddress)
bitcore を用いたアドレス生成
Bitcoin.prototype.getAddress = function(rawPrivateKey) { const privateKey = new bitcore.PrivateKey(rawPrivateKey, 'mainnet'); return privateKey.toAddress().toString(); }; module.exports = Bitcoin;
● 入力データ
実際に送金を送るための情報。ここに記載した内容をもとに送金元、送金先を決定。
const BTC = require('./BTC'); const btc = new BTC(); const fromaddress = "14myP**********"; const private_key = "133fe**********"; const toaddress1 = "1A5jR**********"; const amount1 = 100; const toaddress2 = "13GQG**********"; const amount2 = 200; const fee = 0.01; btc.send_param(fromaddress, private_key, toaddress1, amount1, toaddress2, amount2, fee).then(tx_hash => { console.log({'tx_hash': tx_hash}); }).catch(err => { console.log("error"); });