GetBlock で Bitcoin のエンドポイントを作ってみる
はじめに
このスクラップでは GetBlock で Bitcoin テストネットのエンドポイントを作ってみる
サインインしてみた
エンドポイントを作ってみた
Protocol = Bitcoin, Network = Testnet を選んで Get ボタンを押す
Bitcoin Testnet の JSON-RPC URL が MyEndpoints のセクションに表示される
URL の例は https://btc.getblock.io/00000000-0000-0000-0000-000000000000/testnet/
Docker から bitcoin-cli を使う
mkdir getblocks
cd getblocks
touch Dockerfile
FROM lncm/bitcoind:v22.0
USER root
RUN apk add --update bash
ENTRYPOINT bash
$ docker build -t bitcoind .
[+] Building 4.2s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 121B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/lncm/bitcoind:v22.0 0.0s
=> CACHED [1/2] FROM docker.io/lncm/bitcoind:v22.0 0.0s
=> [2/2] RUN apk add --update bash 4.1s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => writing image sha256:26b5ac1e384b6af3e6b702654bea9d1010ea06a778b2e 0.0s
=> => naming to docker.io/library/bitcoind 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
$ docker run -it bitcoind
bash-5.0# bitcoin-cli --version
Bitcoin Core RPC client version v22.0.0
bitcoin.conf のデフォルトパス
下記によると /root/.bitcoin/bitcoin.conf
のようだ
bitcoin-cli では IP アドレスしか設定できない?
となると下記のように HTTP リクエストを送ることになる
curl \
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getrpcinfo", "params": []}' \
-H 'content-type: text/plain;' \
https://btc.getblock.io/00000000-0000-0000-0000-000000000000/testnet/
{
"result": {
"active_commands": [{ "method": "getrpcinfo", "duration": 24 }],
"logpath": "/home/bitcoin/.bitcoin/testnet3/debug.log"
},
"error": null,
"id": "curltest"
}
すいません、Docker とかの手順は不要でした
とはいえ bitcoin-cli を使えないのは不便だな
リクエストは1日10万回まで送信できるらしい
Each day we send you 100K requests for free! Unused requests from the free package can’t be transferred to the next day.
太っ腹
createwallet とかはできない
共有ノードだから当たり前か
touch create-wallet.json
{
"jsonrpc": "1.0",
"id": "curltest",
"method": "createwallet",
"params": ["testwallet"]
}
curl -v --data-binary @create-wallet.json \
-H 'content-type: text/plain;' \
https://btc.getblock.io/00000000-0000-0000-0000-000000000000/testnet/
* Trying 65.108.42.230:443...
* Connected to btc.getblock.io (65.108.42.230) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: CN=*.getblock.io
* start date: Nov 7 09:00:26 2022 GMT
* expire date: Feb 5 09:00:25 2023 GMT
* subjectAltName: host "btc.getblock.io" matched cert's "*.getblock.io"
* issuer: C=US; O=Let's Encrypt; CN=R3
* SSL certificate verify ok.
> POST /7f321c06-a74f-4b80-92c3-cb90de0c5d10/testnet/ HTTP/1.1
> Host: btc.getblock.io
> User-Agent: curl/7.79.1
> Accept: */*
> content-type: text/plain;
> Content-Length: 99
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< date: Tue, 03 Jan 2023 08:28:58 GMT
< content-length: 0
< content-type: text/html; charset=ISO-8859-1
< x-envoy-upstream-service-time: 249
< x-cluster: Shared nodes
< server: envoy
<
* Connection #0 to host btc.getblock.io left intact
鍵を作ったりするのには結局 Docker が必要になるのか...
Docker でもテストネットの鍵を作るには regtest モード以外で bitcoind を起動する必要がある
これでは何のために GetBlocks を使うのかがわからなくなる
なんとなくだけど GetBlocks の JSON-RPC エンドポイントに RawTransaction を送っても大丈夫そうな気がする
RawTransaction を作るには下記のようなライブラリが役に立ちそう
テストネット用アドレスの作成
npm init -y
npm init --save-dev @types/bitcore-lib bitcore-lib ts-node
touch address.ts
import { Address, Networks, PrivateKey } from "bitcore-lib";
const privateKey = new PrivateKey();
const publicKey = privateKey.toPublicKey();
const address = new Address(publicKey, Networks.testnet);
console.log(JSON.stringify({
privateKey: privateKey.toString(),
address: address.toString(),
}, null, 2))
npx ts-node address.ts > address.json
{
"privateKey": "0000000000000000000000000000000000000000000000000000000000000000",
"address": "mofDKpPWP8vG2ZaLQeSKxF9bZ2aQjkDeqk"
}
Bitcoin アドレスにも色々あるらしい
m / n で始まるのは Testnet pubkey hash
Bitcoin Faucet
この Faucet が 0.01 BTC くらいくれた、太っ腹
初めは下記を使おうとしたけどアドレスが tb1q から始まる必要があるらしくダメだった
トランザクションは BlockCypher から確認できる
GetBlock ドキュメントに実行可能な JSON-RPC 一覧があった
sendrawtransaction は呼び出せるようだ、良かった
今更だけど
BlockCypher の API の方が使い勝手良さそう
GetBlock の方が bitcoind ベースなのでスタンダードといえばスタンダードかも知れないけど
今更だけど2
bitcore-lib よりも bitcoinjs-lib の方が良いかも知れない
理由は BlockCypher のサンプルでも使われているので
でも bitcore-lib の方が使い方がシンプルなので悩ましい所
ある程度しくみを理解できてから bitcoinjs-lib を使う方が良いかも知れない
目標
せっかくなので OP_RETURN する所までやってみたい
bitcore-lib でトランザクションを作成してようとしたけど
Examples の通りやろうとすると TypeScript のエラーが出る
JavaScript なら動くかも知れないけどせっかくであれば TypeScript でやりたい
一旦ストップして bitcoinjs-lib の方を使ってみようと思う
bitcoinjs-lib アドレス作るのからして難しい
mkdir hello-bitcoinjs
cd hello-bitcoinjs
npm init -y
npm install --save-dev bitcoinjs-lib ecpair tiny-secp256k1 ts-node
touch address.ts
import { ECPairFactory, networks } from "ecpair";
import * as bitcoin from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
const ECPair = ECPairFactory(ecc);
const keyPair = ECPair.makeRandom({
network: networks.testnet,
});
const { address: p2pkh } = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
network: networks.testnet,
});
const { address: p2sh } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2ms({
m: 1,
pubkeys: [keyPair.publicKey],
network: networks.testnet,
}),
});
const wif = keyPair.toWIF();
const { address: p2wpkh } = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
network: networks.testnet,
});
console.log(JSON.stringify({ p2pkh, p2sh, wif, p2wpkh }, null, 2));
npx ts-node address.ts
{
"p2pkh": "mh64DSu26HBVXtbgeDgLmBe6Q4Fiaw5sWp",
"p2sh": "2N5nhhE6W5Xq9AWRPezpLbmBUiqTmxUBBPw",
"wif": "cP6vmWkxz4WAZYQSFRDhkK1ozEKR1jjyHN1LxxakVv7SrgnCuh54",
"p2wpkh": "tb1qzyavka0atu2u3924rz4vp77urlasv74yhpkkts"
}
でも色々な形式のアドレスを作れるのは嬉しい
Bitcoin の P2PKH とかって何?
最後のリンクがわかりやすい
bitcoinjs-lib でトランザクションを作る
it can create a 1-to-1 Transaction
のテストコードが何やっているかよくわからない
スタックオーバーフローに関連する質問があってよかった
PSBT: Partially Signed Bitcoin Transactions(部分的署名ビットコイン取引)はトランザクションのフォーマットみたいなものなのかな
Bitcoin testnet3 の テスト用 BTC を下記の Faucet から貰う、ありがとう
Segwit: Segregated Witness(隔離された証人)もトランザクションの一形式のようだ、PSBT との違いは何だろう
Bitbank の記事が勉強になる
トランザクションの ID や内容、生データについては下記から調べられる
blockstream.info の API については下記がドキュメント
自分のアドレス tb1qj3tmla4tmjtvwu0c5adl6vt6m68v3rw246hg0u を使って調べてみた
[
{
"txid": "cd94b51ca31e6c743dae0d1c0d562848e04ef1e7d7b96622fa5327e066a41043",
"vout": 0,
"status": {
"confirmed": true,
"block_height": 2414788,
"block_hash": "000000000000002be58f00eb479097af19ee998064aa2d870a131fe962407b01",
"block_time": 1672961447
},
"value": 1750922
}
]
{
"txid": "cd94b51ca31e6c743dae0d1c0d562848e04ef1e7d7b96622fa5327e066a41043",
"version": 2,
"locktime": 2414707,
"vin": [
{
"txid": "279d533a6004b283c1437ecf39c9870bc8ebb14b901b850d10e45b41e84934de",
"vout": 1,
"prevout": {
"scriptpubkey": "0014dc97571f85e558d3e79b7fa8452ce1379bbbe7f4",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 dc97571f85e558d3e79b7fa8452ce1379bbbe7f4",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qmjt4w8u9u4vd8eum075y2t8px7dmhel5fm3cx9",
"value": 869411879
},
"scriptsig": "",
"scriptsig_asm": "",
"witness": [
"304402203ec88e56641439dd63b2e79cbecfc8b4ecb4a09b28ab757043ff59ebb39608080220484b28fa88caf0b7144a76bb70a2e9f895389fcee04d26b87ffd2ad2de73c60601",
"025edf5111c728ceb5c1439adf8fc702c14e2c4b5e17594d686a593a02462f44ba"
],
"is_coinbase": false,
"sequence": 4294967294
}
],
"vout": [
{
"scriptpubkey": "00149457bff6abdc96c771f8a75bfd317ade8ec88dca",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 9457bff6abdc96c771f8a75bfd317ade8ec88dca",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qj3tmla4tmjtvwu0c5adl6vt6m68v3rw246hg0u",
"value": 1750922
},
{
"scriptpubkey": "0014336ae5fbce04dca6856d61913ee54363786b9590",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 336ae5fbce04dca6856d61913ee54363786b9590",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "tb1qxd4wt77wqnw2dptdvxgnae2rvduxh9vsnkqx2r",
"value": 867646534
}
],
"size": 222,
"weight": 561,
"fee": 14423,
"status": {
"confirmed": true,
"block_height": 2414788,
"block_hash": "000000000000002be58f00eb479097af19ee998064aa2d870a131fe962407b01",
"block_time": 1672961447
}
}
02000000000101de3449e8415be4100d851b904bb1ebc80b87c939cf7e43c183b204603a539d270100000000feffffff028ab71a00000000001600149457bff6abdc96c771f8a75bfd317ade8ec88dca463cb73300000000160014336ae5fbce04dca6856d61913ee54363786b95900247304402203ec88e56641439dd63b2e79cbecfc8b4ecb4a09b28ab757043ff59ebb39608080220484b28fa88caf0b7144a76bb70a2e9f895389fcee04d26b87ffd2ad2de73c6060121025edf5111c728ceb5c1439adf8fc702c14e2c4b5e17594d686a593a02462f44ba73d82400
確かに両方に ScriptPubKey の 00149457bff6abdc96c771f8a75bfd317ade8ec88dca
が含まれていることがわかる
トランザクションが segwit であるかどうかはどうやって調べれば良いんだろう
Blockstream でトランザクションを確認すると segwit であることはとりあえずわかる
何でも知っている Stack Overflow は偉大
1つ以上のトランザクション入力が witness を含むか、生トランザクションの 5 バイト目が 00
だったら segwit のようだ
A transaction is a segwit tx if at least one of the inputs contain a witness. Or if you are inspecting the raw tx then you check the 5th byte (the input count) and if it is 0x00 then it is a segwit tx.
Mastering Bitcoin を GitHub で読める
無料で読めるのは嬉しい、著者様に感謝
トランザクションが作れない
touch transaction.ts
import { Psbt } from "bitcoinjs-lib";
import { ECPairFactory, networks } from "ecpair";
import { readFileSync } from "fs";
import * as ecc from "tiny-secp256k1";
const ECPair = ECPairFactory(ecc);
const { wif, p2wpkh } = JSON.parse(readFileSync("address.json", "utf8"));
const keyPair = ECPair.fromWIF(wif, networks.testnet);
const psbt = new Psbt();
psbt.addInput({
hash: "cd94b51ca31e6c743dae0d1c0d562848e04ef1e7d7b96622fa5327e066a41043",
index: 0,
witnessUtxo: {
script: Buffer.from("00149457bff6abdc96c771f8a75bfd317ade8ec88dca", "hex"),
value: 175_0922,
},
});
psbt.addOutput({
address: p2wpkh,
value: 175_0000,
});
psbt.signInput(0, keyPair);
const validator = (
pubkey: Buffer,
msghash: Buffer,
signature: Buffer
): boolean =>
ECPair.fromPublicKey(pubkey, { network: networks.testnet }).verify(
msghash,
signature
);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeAllInputs();
console.log(psbt.extractTransaction().toHex());
npx ts-node transaction.ts
/Users/susukida/workspace/js/hello-bitcoinjs/node_modules/bitcoinjs-lib/src/address.js:137
throw new Error(address + ' has an invalid prefix');
^
Error: tb1qj3tmla4tmjtvwu0c5adl6vt6m68v3rw246hg0u has an invalid prefix
at toOutputScript (/Users/susukida/workspace/js/hello-bitcoinjs/node_modules/bitcoinjs-lib/src/address.js:137:15)
at Psbt.addOutput (/Users/susukida/workspace/js/hello-bitcoinjs/node_modules/bitcoinjs-lib/src/psbt.js:240:51)
at Object.<anonymous> (/Users/susukida/workspace/js/hello-bitcoinjs/transaction.ts:20:6)
at Module._compile (node:internal/modules/cjs/loader:1126:14)
at Module.m._compile (/Users/susukida/workspace/js/hello-bitcoinjs/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1180:10)
at Object.require.extensions.<computed> [as .ts] (/Users/susukida/workspace/js/hello-bitcoinjs/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:1004:32)
at Function.Module._load (node:internal/modules/cjs/loader:839:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
やっとトランザクションらしきものを作れた
ポイントは下記の通り
-
payments.p2wpkh()
を使うこと - アドレスではなく pubkey を指定すること
-
psbt.addOutput()
を呼び出すときにaddress
ではなくscript
フィールドを使うこと
import { payments, Psbt } from "bitcoinjs-lib";
import { ECPairFactory, networks } from "ecpair";
import { readFileSync } from "fs";
import * as ecc from "tiny-secp256k1";
function main() {
const ECPair = ECPairFactory(ecc);
const address = JSON.parse(readFileSync("address.json", "utf8"));
const keyPair = ECPair.fromWIF(address.wif, networks.testnet);
const psbt = new Psbt();
psbt.addInput({
hash: "cd94b51ca31e6c743dae0d1c0d562848e04ef1e7d7b96622fa5327e066a41043",
index: 0,
witnessUtxo: {
script: Buffer.from(
"00149457bff6abdc96c771f8a75bfd317ade8ec88dca",
"hex"
),
value: 175_0922,
},
});
const payment = payments.p2wpkh({
pubkey: keyPair.publicKey,
network: networks.testnet,
});
psbt.addOutput({
script: payment.pubkey!,
value: 175_0000,
});
psbt.signInput(0, keyPair);
const validator = (
pubkey: Buffer,
msghash: Buffer,
signature: Buffer
): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeAllInputs();
console.log(psbt.extractTransaction().toHex());
}
main();
実行結果
020000000001014310a466e02753fa2266b9d7e7f14ee04828560d1c0dae3d746c1ea31cb594cd0000000000ffffffff01f0b31a00000000002102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa02483045022100bd0a86def594e8131b0588f0607dd05bfbd4da02fcd6942fe540b84cafe092aa022072862fce9271e1e3792c027eda81e136691f1cb5e0eec98a2dd2f5668c108c89012102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa00000000
【追記】上記のトランザクションを送信した結果、送金先が Unknown になって時空の間に消えていったのでご注意ください
curl する時に面倒なので
API キーをエクスポートしておく
apiKey=00000000-0000-0000-0000-000000000000
使うときは下記のような感じ
echo https://btc.getblock.io/${apiKey}/testnet/
生トランザクションを投げてみた
curl \
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "sendrawtransaction", "params": ["020000000001014310a466e02753fa2266b9d7e7f14ee04828560d1c0dae3d746c1ea31cb594cd0000000000ffffffff01f0b31a00000000002102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa02483045022100bd0a86def594e8131b0588f0607dd05bfbd4da02fcd6942fe540b84cafe092aa022072862fce9271e1e3792c027eda81e136691f1cb5e0eec98a2dd2f5668c108c89012102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa00000000"]}' \
-H 'content-type: application/json;' \
https://btc.getblock.io/${apiKey}/testnet/
{
"result": "859faeb8471af436db112d007a5f627f57c9e6ed6f151111846ca6a555c46c84",
"error": null,
"id": "curltest"
}
なんか一瞬にしてテスト用のビットコインを失ってしまった気がするぞ笑
何度もすいません
有効な Script Pubkey
00149457bff6abdc96c771f8a75bfd317ade8ec88dca
0014336ae5fbce04dca6856d61913ee54363786b9590
どうやら 0014
から始まっている様子
そもそも Script Pubkey って何?
Script Pubkey は送金するための条件と考えて良さそう
OP_DUP OP_HASH160 14836dbe7f38c5ac3d49e8d790af808a4ee9edcf OP_EQUALVERIFY OP_CHECKSIG
上記の先頭に1つ以上の何らかの命令を追加して実行結果が有効であれば送金が行われる
何が正しい命令かどうかは秘密鍵を持っている人だけが生成することができる
Script Pubkey の中に公開鍵が含まれている
Script Pubkey らしいものがあった
const payment = payments.p2wpkh({
pubkey: keyPair.publicKey,
network: networks.testnet,
});
console.log(payment.output);
<Buffer 00 14 94 57 bf f6 ab dc 96 c7 71 f8 a7 5b fd 31 7a de 8e c8 8d ca>
これって witnessUtxo
に指定しているものと同じだ
また失敗する前に
トランザクションを2分割したいのだがどうすれば良いかわからない
coinbin を使おうと思ったけどテストネットは対応していないみたい
coinbin 普通に testnet 対応していた
ごめん嘘ついた
やっぱりできない
coinbin では testnet では署名等ができないから使えないに近い状態だ
仕方ない勢いでやってみよう
import { payments, Psbt } from "bitcoinjs-lib";
import { ECPairFactory, networks } from "ecpair";
import { readFileSync } from "fs";
import * as ecc from "tiny-secp256k1";
function main() {
const ECPair = ECPairFactory(ecc);
const address = JSON.parse(readFileSync("address.json", "utf8"));
const keyPair = ECPair.fromWIF(address.wif, networks.testnet);
const psbt = new Psbt();
const tx = "634e8307a6df2b8ebfacd5d845d26a55c15f07b637ad403d35515a99b1f25727";
const index = 1;
const satoshi = 197_2910
const fee = 200
psbt.addInput({
hash: tx,
index: index,
witnessUtxo: {
script: Buffer.from(
"00149457bff6abdc96c771f8a75bfd317ade8ec88dca",
"hex"
),
value: satoshi,
},
});
const payment = payments.p2wpkh({
pubkey: keyPair.publicKey,
network: networks.testnet,
});
psbt.addOutput({
script: payment.output!,
value: satoshi - fee,
});
psbt.signInput(0, keyPair);
const validator = (
pubkey: Buffer,
msghash: Buffer,
signature: Buffer
): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeAllInputs();
console.log(psbt.extractTransaction().toHex());
}
main();
npx ts-node transaction.ts > transaction.txt
020000000001012757f2b1995a51353d40ad37b6075fc1556ad245d8d5acbf8e2bdfa607834e630100000000ffffffff01e6191e00000000001600149457bff6abdc96c771f8a75bfd317ade8ec88dca02483045022100902aee8b50d9b9b820dfa6b6ae7562235f71bf37b316b6ade83d7dcd07d80f7002205688ed84ede141e334d6adcb1235ac0b3bb124cfdebb8c59bc5f7518904e8d34012102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa00000000
curl \
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "sendrawtransaction", "params": ["020000000001012757f2b1995a51353d40ad37b6075fc1556ad245d8d5acbf8e2bdfa607834e630100000000ffffffff01e6191e00000000001600149457bff6abdc96c771f8a75bfd317ade8ec88dca02483045022100902aee8b50d9b9b820dfa6b6ae7562235f71bf37b316b6ade83d7dcd07d80f7002205688ed84ede141e334d6adcb1235ac0b3bb124cfdebb8c59bc5f7518904e8d34012102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa00000000"]}' \
-H 'content-type: application/json;' \
https://btc.getblock.io/${apiKey}/testnet/
{"result":"c6096734d8a854a79235ece9fe567deba370419b57318f50fce6f493416ca560","error":null,"id":"curltest"}
どうやらできているっぽいぞ!
間違えてもいいように UTXO を2つ作っておく
psbt.addOutput({
script: payment.output!,
value: satoshi - fee - 1_0000,
});
psbt.addOutput({
script: payment.output!,
value: 1_0000,
});
もしかしてこれで OP_RETURN もできる?
import { payments, Psbt } from "bitcoinjs-lib";
import { ECPairFactory, networks } from "ecpair";
import { readFileSync } from "fs";
import * as ecc from "tiny-secp256k1";
function main() {
const ECPair = ECPairFactory(ecc);
const address = JSON.parse(readFileSync("address.json", "utf8"));
const keyPair = ECPair.fromWIF(address.wif, networks.testnet);
const psbt = new Psbt();
const tx = "9e2b73905beeac7b8416e152a13632c2196d06797e32327bc328d6305a64a817";
const index = 1;
const satoshi = 1_0000;
const fee = 250;
psbt.addInput({
hash: tx,
index: index,
witnessUtxo: {
script: Buffer.from(
"00149457bff6abdc96c771f8a75bfd317ade8ec88dca",
"hex"
),
value: satoshi,
},
});
const payment = payments.p2wpkh({
pubkey: keyPair.publicKey,
network: networks.testnet,
});
psbt.addOutput({
script: payment.output!,
value: satoshi - fee,
});
psbt.addOutput({
script: payments.embed({
data: [Buffer.from('https://zenn.dev/tatsuyasusukida/scraps/1943afd6008301', 'utf8')]
}).output!,
value: 0,
});
psbt.signInput(0, keyPair);
const validator = (
pubkey: Buffer,
msghash: Buffer,
signature: Buffer
): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeAllInputs();
console.log(psbt.extractTransaction().toHex());
}
main();
npx ts-node transaction.ts
0200000000010117a8645a30d628c37b32327e79066d19c23236a152e116847bacee5b90732b9e0100000000ffffffff0216260000000000001600149457bff6abdc96c771f8a75bfd317ade8ec88dca0000000000000000386a3668747470733a2f2f7a656e6e2e6465762f74617473757961737573756b6964612f7363726170732f313934336166643630303833303102483045022100c392f0fb0cf15f471ffef3522ae5e159a31faf9e8e893ac5732e22ee2a2b1a1c022060247d8270f2e3a2d4f5fa767bf175d1f4f6ff2de267244f8d5bd68e1c5bcdcd012102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa00000000
curl \
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "sendrawtransaction", "params": ["0200000000010117a8645a30d628c37b32327e79066d19c23236a152e116847bacee5b90732b9e0100000000ffffffff0216260000000000001600149457bff6abdc96c771f8a75bfd317ade8ec88dca0000000000000000386a3668747470733a2f2f7a656e6e2e6465762f74617473757961737573756b6964612f7363726170732f313934336166643630303833303102483045022100c392f0fb0cf15f471ffef3522ae5e159a31faf9e8e893ac5732e22ee2a2b1a1c022060247d8270f2e3a2d4f5fa767bf175d1f4f6ff2de267244f8d5bd68e1c5bcdcd012102cacb5776c24ee5d6070c2363d32a0737ebac0720ce5fc201fdd15e1db44c5baa00000000"]}' \
-H 'content-type: application/json;' \
https://btc.getblock.io/${apiKey}/testnet/
{"result":"77e5e4505b1847368cb96b361b334704551c2e905751af94753ef0ce6f1b37bb","error":null,"id":"curltest"}
人生初めての OP_RETURN ができた!テストネットだけど感動!
おわりに
目標は達成したので一旦クローズ
GetBlock は Bitcoin 標準の RPC を手軽に使えるようにしてくれるのでおすすめです
残った Bitcoin は返却する
mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB
返却するためのコード
import { payments, Psbt } from "bitcoinjs-lib";
import { ECPairFactory, networks } from "ecpair";
import { readFileSync } from "fs";
import * as ecc from "tiny-secp256k1";
function main() {
const ECPair = ECPairFactory(ecc);
const address = JSON.parse(readFileSync("address.json", "utf8"));
const keyPair = ECPair.fromWIF(address.wif, networks.testnet);
const psbt = new Psbt();
const tx = "9e2b73905beeac7b8416e152a13632c2196d06797e32327bc328d6305a64a817";
const index = 0;
const satoshi = 0.0196246e8;
const fee = 120;
psbt.addInput({
hash: tx,
index: index,
witnessUtxo: {
script: Buffer.from(
"00149457bff6abdc96c771f8a75bfd317ade8ec88dca",
"hex"
),
value: satoshi,
},
});
const payment = payments.p2pkh({
address: 'mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB',
network: networks.testnet,
});
psbt.addOutput({
script: payment.output!,
value: satoshi - fee,
});
psbt.signInput(0, keyPair);
const validator = (
pubkey: Buffer,
msghash: Buffer,
signature: Buffer
): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeAllInputs();
console.log(psbt.extractTransaction().toHex());
}
main();