はじめに
「ブロックチェーン技術を学びたい」と思いながらも、なかなか行動に移せずにいました。
ローカル環境で Tapyrus ブロックチェーンを触ってみることで、「ブロックチェーンとは何か?」を体験しながら学んでいきます。
これまでの記事では、Tapyrus ブロックチェーンのローカル環境構築から、RPC を使った基本操作、そしてブロック生成までを試してきました。
本記事では、既に得たコインベース報酬(UTXO)を使って、別アドレスに TPC を送金することに挑戦します。
これまでの記事
Tapyrus とは
Tapyrus は Bitcoin をベースにしたオープンソースのブロックチェーンプラットフォームで、日本の企業 Chaintope によって開発されています。企業や自治体での実用を想定した設計が特徴です。
手元の環境
今回の動作確認は以下のローカル環境で行いました:
- OS:LMDE 6(Linux Mint Debian Edition)
- Docker:28.2.2
- Docker Compose:v2.36.2
- シェル:bash
- その他:インターネット接続(イメージ取得のため)
※ Docker 環境があれば、macOS や WSL2 でも同様に動くはずです。
手順概要
今回の流れは以下の通りです:
- コインベース報酬を mature(使用可能状態)にする
- 送金先アドレスを生成
-
sendtoaddress
コマンドで送金 - 確認用ブロックを生成し、送金トランザクションをブロックに取り込む
- UTXO 状態の変化を確認
ノードとクライアント起動
以下は、以前の記事 で作成した Docker Compose 環境がすでに存在している前提です。
docker compose up -d tapyrusd
docker compose run --rm tapyrus-client
これで Tapyrus ノードがバックグラウンドで起動し、Ruby クライアント(IRB セッション)が開始されます。
コインベース報酬の成熟(ブロック生成)
Tapyrus の dev モード(dev=1
)では、コインベース報酬は 1 ブロックの確認 で spendable
(使用可能)になります。
まずは 1 ブロックを生成して報酬を使用可能にしましょう。
報酬受取用アドレスの生成とアグリゲート秘密鍵のインポート
まず、コインベース報酬の送り先となるアドレスを新規に生成します。
addr_mine = $client.getnewaddress
次に、Tapyrus dev モードで必要となる アグリゲート秘密鍵 をインポートします。
agg_wif = "cUJN5RVzYWFoeY8rUztd47jzXCu1p57Ay8V7pqCzsBD3PEXN7Dd4"
$client.importprivkey(agg_wif, "dev_aggkey", false)
- 第1引数:インポートする秘密鍵(WIF 形式)
- 第2引数:任意のラベル名(ここでは
"dev_aggkey"
) - 第3引数:
false
を指定するとウォレットの再スキャンをスキップ(処理が高速化されます)
ブロックを生成
これで準備が整ったので、ブロックを生成します。
$client.generatetoaddress(1, addr_mine, agg_wif)
- 第1引数:生成するブロック数(今回は 1)
- 第2引数:コインベース報酬の送り先アドレス
- 第3引数:署名に使用するアグリゲート秘密鍵
これで、50 TPC のコインベース報酬が 1 確認済み で使える状態(confirmations=1, spendable=true
)になります。
UTXO 状態の確認
以下のコマンドで、現在の UTXO 一覧を確認してみます。
$client.listunspent.find { |u| u["confirmations"] == 1 }
{"txid" => "09e260b9d1e5afe0a252e7cf1205c4c13a6278357d656f8258aeeda8d27b4a52",
"vout" => 0,
"address" => "mqopVBKSY2TkXoJx3kdPbHPrChsXDvCCUB",
"token" => "TPC",
"amount" => "50.0",
"label" => "",
"scriptPubKey" => "76a91470e1cfad1d447569775a700dcd14acf6e69bbd0e88ac",
"confirmations" => 1,
"spendable" => true,
"solvable" => true,
"safe" => true}
送金先アドレスの生成
次に、送金先となる新しいアドレスをウォレット内で作成します。
addr_recv = $client.getnewaddress
このコマンドにより、ウォレットが新しい受取用アドレスを 1 つ生成し、文字列として返してくれます。
今回はこのアドレス(addr_recv
)を送金先とし、先ほど生成したコインベース UTXO をここに送ります。
dev モードでは、すべてのアドレスはローカルウォレット内で管理されています。外部ノードや外部ウォレットが存在しないため、「同一ノード内での自己送金」という形になります。
UTXO を使って送金
準備が整ったので、実際に UTXO を使って 10 TPC を先ほど生成したアドレスに送金してみます。
Tapyrus Core では、ウォレット内の未使用 UTXO から送金額と手数料分を消費して、新しいトランザクションが作成されます。
以下のコマンドで、指定したアドレスに 10 TPC を送ります。
txid = $client.sendtoaddress(addr_recv, 10.0)
- 第1引数:送信先アドレス
- 第2引数:送金額(単位は TPC)
戻り値は、作成されたトランザクションの txid(トランザクション ID) です。
手数料について
特に設定しない場合、dev モードでは非常に小さい固定手数料(例:0.000045 TPC) が自動で適用されます。
ウォレット残高が少ない場合でも、通常はこのままで問題なく送金が完了します。
トランザクション情報を確認
送金直後のトランザクション状態は、以下のように確認できます。
info = $client.gettransaction(txid)
{"confirmations" => 0,
"trusted" => true,
"txid" => "e9da62fb5200ace43f9ae1f70da082120b6c8d28dc7128f6ae660e56431dd8d4",
"walletconflicts" => [],
"time" => 1751372216,
"timereceived" => 1751372216,
"bip125-replaceable" => "no",
"details" =>
[{"address" => "mrgEDw3W3Ht75yF3Gdmp3G3CtxrSXs6Ukn",
"category" => "send",
"token" => "TPC",
"amount" => "-10.0",
"label" => "",
"vout" => 0,
"fee" => "-4.5e-05",
"abandoned" => false},
{"address" => "mrgEDw3W3Ht75yF3Gdmp3G3CtxrSXs6Ukn",
"category" => "receive",
"token" => "TPC",
"amount" => "10.0",
"label" => "",
"vout" => 0}],
"hex" =>
"0100000001524a7bd2a8edae58826f657d3578623ac1c40512cfe752a2e0afe5d1b960e209000000006a473044022013e7bba19a3242c2ef2e213af417de62299e40c748f075d11b028b6396566a820220068a56995ee8a2574db80ce5c5c7d03c57b86c2990a4e489b8b9b82873bc2c99012103b0499b7a7bb348b227cd5b10eea7b5174884c6cea27ab99847b92ed3af32362efeffffff0200ca9a3b000000001976a9147a6a6c91069323f3f32e2fb6763dd47d995b24f388ac6c166bee000000001976a914dc6eda65c811bb975bde6462f8b63270a8c8694888ac00000000"}
-
confirmations
: 現在の確認数(送金直後は0
) -
details
: 各アドレスごとの出入金情報 -
hex
: 生のトランザクションデータ(hex)
送金直後は未承認(confirmations = 0
)です。
info["confirmations"]
#=> 0
sent = info["details"].find { |d| d["category"] == "send" }["amount"]
#=> "-10.0"
fee = info["details"].find { |d| d["category"] == "send" }["fee"]
#=> "-4.5e-05"
recv = info["details"].find { |d| d["category"] == "receive" }["amount"]
#=> "10.0"
確認用ブロックを 1 つ生成
先ほど送信したトランザクションは、現在 mempool(未承認トランザクションプール) に入っている状態です。
これをブロックに取り込み、confirmation = 1
の状態にするためには、新たに 1 ブロックを生成する必要があります。
$client.generatetoaddress(1, addr_mine, agg_wif)
このコマンドにより、未承認だったトランザクションが新しいブロックに取り込まれ、正式に承認(confirmation = 1
) されます。
トランザクションの確認数を再チェックしてみます。
$client.gettransaction(txid).fetch("confirmations")
#=> 1
confirmations
が 1 に増えていれば成功です。
これで、送金トランザクションがブロックチェーンに記録されたことが確認できました。
UTXO 状態の変化を確認
最後に、今回の送金によってウォレット内の UTXO がどのように変化したかを確認します。
送金前には「50 TPC のコインベース UTXO」が 1 つだけ存在していましたが、送金後は以下のように変化しているはずです。
- 送金先アドレス(
addr_recv
)宛: 10 TPC の新しい UTXO - お釣りアドレス: 約 39.9999 TPC のお釣りUTXO(ウォレット内で自動生成)
入出力詳細を確認
$client.gettransaction(txid).fetch("details")
[{"address" => "mrgEDw3W3Ht75yF3Gdmp3G3CtxrSXs6Ukn",
"category" => "send",
"token" => "TPC",
"amount" => "-10.0",
"label" => "",
"vout" => 0,
"fee" => "-4.5e-05",
"abandoned" => false},
{"address" => "mrgEDw3W3Ht75yF3Gdmp3G3CtxrSXs6Ukn",
"category" => "receive",
"token" => "TPC",
"amount" => "10.0",
"label" => "",
"vout" => 0}]
全体の UTXO 一覧を確認
まずは、現在ウォレット内に存在する全 UTXO を確認します。
$client.listunspent
[{"txid" => "fca123725a5cb57d11d415a9b9095b91ad3ac5687a4a0e3fb1a4ea660133220c",
"vout" => 0,
"address" => "mncZjvY8bw7GT9VVDs6KC1jomezPV2q9xC",
"token" => "TPC",
"amount" => "10.0",
"label" => "",
"scriptPubKey" => "76a9144dd88c95444cef495d832b6a38075081dc33912388ac",
"confirmations" => 1,
"spendable" => true,
"solvable" => true,
"safe" => true},
{"txid" => "fca123725a5cb57d11d415a9b9095b91ad3ac5687a4a0e3fb1a4ea660133220c",
"vout" => 1,
"address" => "n3JX9XtYiWGQFHTvVCcpAMWu6fMypU575R",
"token" => "TPC",
"amount" => "39.999955",
"scriptPubKey" => "76a914eef8c8bc1bdd90c64af786d5028adc44a9c530b988ac",
"confirmations" => 1,
"spendable" => true,
"solvable" => true,
"safe" => true},
{"txid" => "c61da3212313bb612deb8ff7f365cac8823cad1b9eb5825c30fba2acd394b126",
"vout" => 0,
"address" => "mvASke4rE4zrfne3NGE8LoUFSCrpv58UJD",
"token" => "TPC",
"amount" => "50.000045",
"label" => "",
"scriptPubKey" => "76a914a0a8e84c693edfce761d2d8722ee344392a5306f88ac",
"confirmations" => 1,
"spendable" => true,
"solvable" => true,
"safe" => true}]
この中で、以下が確認できるはずです:
- 送金先アドレス(addr_recv)宛の 10 TPC の新規 UTXO
- ウォレット内で自動生成された change アドレス宛の 約 39.9999 TPC のお釣り UTXO
- 未使用の他の UTXO(もしウォレットに他の残高があれば)
今回は同一ウォレット内で「自己送金」しているため、
送金先アドレスもお釣りアドレスも、どちらも自分のウォレットの UTXO として一覧に出てきます。
送金先アドレス側の UTXO
次に、送金先アドレス(addr_recv
)で新たに受け取った 10 TPC の UTXO を確認します。
$client.listunspent.select { |u| u["address"] == addr_recv }
[{"txid" => "e9da62fb5200ace43f9ae1f70da082120b6c8d28dc7128f6ae660e56431dd8d4",
"vout" => 0,
"address" => "mrgEDw3W3Ht75yF3Gdmp3G3CtxrSXs6Ukn",
"token" => "TPC",
"amount" => "10.0",
"label" => "",
"scriptPubKey" => "76a9147a6a6c91069323f3f32e2fb6763dd47d995b24f388ac",
"confirmations" => 1,
"spendable" => true,
"solvable" => true,
"safe" => true}]
10 TPC の UTXO が新たに生成されているはずです。
トランザクションの構造確認
もしトランザクションの中身(入出力詳細)を確認したい場合は、以下のように生トランザクションをデコードできます。
raw = $client.getrawtransaction(txid)
$client.decoderawtransaction(raw)
{"txid" => "e9da62fb5200ace43f9ae1f70da082120b6c8d28dc7128f6ae660e56431dd8d4",
"hash" => "948aec037bb3036b090a02e0601dbf140051328f1f3aefd4e98a5d483b3f924f",
"features" => 1,
"size" => 225,
"locktime" => 0,
"vin" =>
[{"txid" => "09e260b9d1e5afe0a252e7cf1205c4c13a6278357d656f8258aeeda8d27b4a52",
"vout" => 0,
"scriptSig" =>
{"asm" =>
"3044022013e7bba19a3242c2ef2e213af417de62299e40c748f075d11b028b6396566a820220068a56995ee8a2574db80ce5c5c7d03c57b86c2990a4e489b8b9b82873bc2c99[ALL] 03b0499b7a7bb348b227cd5b10eea7b5174884c6cea27ab99847b92ed3af32362e",
"hex" =>
"473044022013e7bba19a3242c2ef2e213af417de62299e40c748f075d11b028b6396566a820220068a56995ee8a2574db80ce5c5c7d03c57b86c2990a4e489b8b9b82873bc2c99012103b0499b7a7bb348b227cd5b10eea7b5174884c6cea27ab99847b92ed3af32362e"},
"sequence" => 4294967294}],
"vout" =>
[{"token" => "TPC",
"value" => "10.0",
"n" => 0,
"scriptPubKey" =>
{"asm" =>
"OP_DUP OP_HASH160 7a6a6c91069323f3f32e2fb6763dd47d995b24f3 OP_EQUALVERIFY OP_CHECKSIG",
"hex" => "76a9147a6a6c91069323f3f32e2fb6763dd47d995b24f388ac",
"reqSigs" => 1,
"type" => "pubkeyhash",
"addresses" => ["mrgEDw3W3Ht75yF3Gdmp3G3CtxrSXs6Ukn"]}},
{"token" => "TPC",
"value" => "39.999955",
"n" => 1,
"scriptPubKey" =>
{"asm" =>
"OP_DUP OP_HASH160 dc6eda65c811bb975bde6462f8b63270a8c86948 OP_EQUALVERIFY OP_CHECKSIG",
"hex" => "76a914dc6eda65c811bb975bde6462f8b63270a8c8694888ac",
"reqSigs" => 1,
"type" => "pubkeyhash",
"addresses" => ["n1cVkTzb7JB35RDufBLsX6mo5FAc9WDSeS"]}}]}
おわりに
本記事では、Tapyrus dev モード環境で得たコインベース報酬から、別アドレスへの送金と UTXO 状態の変化を体験しました。
資料
- Tapyrus ブロックチェーンに挑戦 – 環境構築(dev モード)
- Tapyrus ブロックチェーンに挑戦: RPC を叩いてみる
- Tapyrus Core GitHub
- Tapyrus Docker イメージ
- Tapyrus 開発者向けドキュメント(Docker)
- Tapyrus 開発者向けドキュメント(Getting Started)
- Tapyrus Ruby gem (tapyrusrb)
- Chaintope 公式サイト
- https://qiita.com/John110/items/0ac6deb458af976b0cf8
- https://qiita.com/mnishiguchi/items/2cd72b485ddf2e178f3f