バックエンドからのモナカードの送付
Monacard(モナカード)1 Advent Calendar 2023 14日目です。
https://adventar.org/calendars/8922
モナカードとは
Monapartyアセット(トークン)にイラストや画像を紐付ける、いわゆるアート系(N)FTです。
Monapartyは、モナコインブロックチェーン上で、アセット発行、送付等をできるようにする仕組みで、Bitcoin ↔ Counterparty, Monacoin ↔ Monaparty のような関係です。
法律
金融庁の「事務ガイドライン(第三分冊:金融会社関係)」の一部改正(案)【ドキュメントA】、および、左記に対するパブリックコメントに対する金融庁の考え方【ドキュメントB】によると、以下のようなものは暗号資産に該当しない可能性が高いと考えられます。
【A】Ⅰ-1-1①(注)ロ.
当該財産的価値の価格や数量、技術的特性・仕様等を 総合考慮し、不特定の者に対して物品等の代価の弁済に 使用し得る要素が限定的であること。例えば、以下のいずれかの性質を有すること
・ 最小取引単位当たりの価格が通常の決済手段として 用いるものとしては高額であること
・ 発行数量を最小取引単位で除した数量(分割可能性を踏まえた発行数量)が限定的であること
【B】20
一般的に発行数量を最小取引単位で除した数 量(分割可能性を踏まえた発行数量)が少ないほ ど通常の決済手段として用いられる蓋然性が小 さいと考えられ、例えば 100 万個以下である場合 には、「限定的」といえると考えられます。
「事務ガイドライン(第三分冊:金融会社関係)」の一部改正(案)の公表に対するパブリックコメントの結果等について
https://www.fsa.go.jp/news/r4/sonota/20230324-2/20230324-2.html
【Aの新旧対照表】
https://www.fsa.go.jp/news/r4/sonota/20221216-2/01.pdf
【B】
https://www.fsa.go.jp/news/r4/sonota/20230324-2/1.pdf
というわけで、発行枚数が100万個以下であれば、暗号資産ではない、すなわち、人様のモナカードを預かっても大丈夫そうです。
ちなみに、
・ 最小取引単位当たりの価格が通常の決済手段として 用いるものとしては高額であること
に対する金融庁の考え方は、
【B】16
一般的に最小取引単位当たりの価格が高額で あるほど通常の決済手段として用いられる蓋然 性が小さいと考えられ、例えば 1000 円以上のも のについては「最小取引単位当たりの価格が通常 の決済手段として用いるものとしては高額」なも のであると考えられます。
なので、低価格で取引されているモナカードの場合、この条件は使えなさそうです。
動機
人様のモナカードを預かれるとなると、「モナパちゃん」(サービス停止中)や「もなこっと」(https://monacotto.monatoka.com/)のようなサービスを提供できるということになります。
そうすると、人様から預かったモナカードを、人様に送り返す(あるいは購入した別の人様にお送りする)という作業が必要になりますね。そうでないと、預かりっぱなしになってしまいます。
流れ
0. 準備
1.トランザクション作成
2.署名
3.ブロードキャスト
有志の方々が建ててくださっているモナコイン・Monaparty関連サーバー
Blockbook
counterparty-server(counterblock)
■ドキュメント
BlockbookAPI
https://github.com/trezor/blockbook/blob/master/docs/api.md
Counterparty公式
counterparty-server API
https://github.com/CounterpartyXCP/Documentation/blob/master/Developers/API.md
counterblock API
https://github.com/CounterpartyXCP/Documentation/blob/master/Developers/counterblock_API.md
※ 人様が運営するノードです。大きな負荷をかけることは避け、お行儀よく使わせていただきましょう。
準備
node.js でやります。
import bitcoin from 'bitcoinjs-lib';
import coininfo from 'coininfo';
bitcoinjs-libは署名に使います。
Monapartyサーバが作る未署名トランザクションがPSBTのフォーマットでないので、version6のPSBTではなく、version5のTransactionBuilderを使います。
npm install bitcoinjs-lib@5
bitcoinjs-lib
https://github.com/bitcoinjs/bitcoinjs-lib
bitcoinjs-libはその名の通り、ビットコインためのライブラリなので、モナコインで使う場合は、モナコインのネットワーク情報が必要です。そのために、coininfoを使います。
coininfo
https://github.com/cryptocoinjs/coininfo
トランザクション作成
Counterpartyサーバ群は、counterparty-serverとcounterblockで構成されています。APIも、counterparty-serverに投げるものとcounterblockに投げるものがありますが、counterblockがcounterparty-serverのプロキシを担ってくれています。よって、counterparty-serverに投げたいリクエストは、counterblockに対して「これをcounterparty-serverに投げてね」と言えば大丈夫です。
それをふまえて、
const requestToMonapartyBody = JSON.stringify({
"jsonrpc": "2.0",
"id": 0,
"method": "proxy_to_counterpartyd",
"params": {
"method": "create_send",
"params": {
"source": source,
"destination": destinations.length >= 2 ? destinations : destinations[0],
"asset": assets.length >= 2 ? assets : assets[0],
"quantity": quantitys.length >= 2 ? quantitys : quantitys[0],
}
}
});
をcounterblockに対してリクエストします。
1つ目の"method"である"proxy_to_counterpartyd"、これが「これをcounterparty-serverに投げてね」ですね。
2つ目の"method"である"create_send"は、「モナパアセットを送信する未署名トランザクションを作ってください」というものです。
"source": 送信元のアドレスです。
"destination": 送信先のアドレスなのですが、単数のときと複数のときで型を変えなければいけない!!!からこんなことをしています。
"asset": 送信対象のアセットです。
"quantity": 送信する数量です。
送信先が複数、あるいは、送信対象アセットが複数種の場合は、配列で指定しますが、"destination", "asset", "quantity"の同一のインデックスのものが組み合わされます。
署名
const networkMona = coininfo('MONA').toBitcoinJS();
const keyPair = bitcoin.ECPair.fromWIF(wif, networkMona);
const tx = bitcoin.Transaction.fromHex(txHex);
const txb = bitcoin.TransactionBuilder.fromTransaction(tx, networkMona);
for (let i = 0; i < tx.ins.length; i++) {
txb.sign(i, keyPair);
}
const signedTxHex = txb.build().toHex();
wifは、送信元の秘密鍵のWIFです。
txHexは、さっきcounterblockにリクエストして返ってきた未署名トランザクションです。
未署名トランザクションをTransactionBuilderオブジェクトにして、各インプットを秘密鍵で署名しています。
最後に、署名済みトランザクションをHEXで出力しています。
ブロードキャスト
自前でモナコインサーバを建てている場合
const requestToMonacoindUrl = `http://localhost:8332/wallet/${wallet}`;
const requestToMonacoindMethod = 'POST';
const requestToMonacoindHeaders = {
'Content-Type': 'text/plain',
};
const requestToMonacoindBody = JSON.stringify({
jsonrpc: "1.0",
id: 0,
method: "sendrawtransaction",
params: [
signedTxHex,
],
});
const authorization = Buffer.from(rpcUser + ':' + rpcPassword, 'utf-8').toString('base64');
requestToMonacoind.headers.Authorization = 'Basic ' + authorization;
これを投げてください。成功すれば、TXIDが返ってきます。
walletはウォレット名です。(マルチウォレットの前提。)
ホスト、ポート番号は人によって違うかもしれません。
人様のサーバを利用させていただく場合
Blockbook
GET /api/v2/sendtx/<hex tx data>
POST /api/v2/sendtx/ (hex tx data in request body)
だそうです。
counterblock
broadcast_tx(signed_tx_hex)
というmethodがあるようです。
Blockbook、counterblockのいずれも、私は使ったことがないので、ドキュメントを参照ください。
おわりに
バックエンドでモナカードが扱えると、だいぶサービスの幅が広がりますよね。
楽しいモナカードサービス、作ってみてください!