• Home
  • ビットコインのウォレットをNode.jsで作る

ビットコインのウォレットをNode.jsで作る

ここでは、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");
});

ご相談・お見積もり

03-5207-2689