Lisk SDK (lisk-client)を使いながらJavaScriptを勉強してみない? - その5 -
おばんです!
モンハンが楽しみな万博おじです🤗
ということで、今回は6回目、内容は「トランザクションの生成と送信」です。
準備
JavaScriptの基本:パスフレーズを生成
文字列操作:パスフレーズからアドレスを取得
非同期処理:APIからアカウント情報を取得
ループ処理:APIからトランザクション情報を取得
トランザクションの生成と送信
トランザクション手数料の取得
はじめに
いよいよLSKを送信します。
今回は内容のほとんどがlisk-clientとLiskサービスAPIの使用方法なので難易度はちょっと高めかもしれませんがのんびりご覧ください😉
前回
今回のお勉強用ソースコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>画面タイトルです</title>
<script src="https://js.lisk.com/lisk-client-5.2.2.min.js" defer></script>
<style>
html { font-size: 10px; }
body { font-size: 1.6rem; }
input[type="text"],
input[type="number"],
input[type="password"],
textarea,
button {
font-size: 1.6rem;
padding: 5px;
}
</style>
</head>
<body>
<div>
<input type="password" id="enter-passphrase" style="width: 750px;" placeholder="パスフレーズを入力してください" oninput="checkPassphrase(this.value)" />
</div>
<div>
<button type="button" id="btn-login" style="width: 150px;" onclick="login()" disabled="true">ログイン</button>
<button type="button" style="width: 150px;" onclick="createAccount()">アカウントを作成</button>
</div>
<div>
<a href="https://testnet-faucet.lisk.com/" target="_blank" rel="noopener noreferrer">テストネット用のLSKを受け取ります</a>
</div>
<div>
<input type="text" id="enter-recipient" style="width: 750px;" placeholder="[必須] 送信先アドレスを入力してください (例:lsk9g3k58b3gzcykjyaob9ekbt3a7b3e586h4gkxj)" disabled="true"/>
</div>
<div>
<input type="number" id="enter-amount" style="width: 750px;" placeholder="[必須] 送信枚数を入力してください (0以上の数値)" disabled="true"/>
</div>
<div>
<input type="text" id="enter-memo" style="width: 750px;" placeholder="[任意] メモを入力してください" disabled="true"/>
</div>
<div>
<button type="button" id="btn-send" style="width: 150px;" onclick="send()" disabled="true">送信</button>
</div>
<hr>
<h4>テスト用パスフレーズ:</h4>
<div>
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
</div>
<hr>
<!-- (1)パスフレーズの表示場所 -->
<h4>パスフレーズ:</h4>
<div id="lisk-passphrase"></div>
<!-- (2)アドレスの表示場所 -->
<h4>アドレス:</h4>
<div id="lisk-address"></div>
<div id="lisk-bufferAddress"></div>
<!-- (3)公開鍵の表示場所 -->
<h4>公開鍵:</h4>
<div id="lisk-publicKey"></div>
<!-- (4)残高の表示場所 -->
<h4>残高:</h4>
<div id="lisk-balance"></div>
<!-- (5)残高の表示場所 -->
<h4>トランザクション(直近10件):</h4>
<div id="lisk-transactions"></div>
<script>
/*
* アカウント情報初期化
*/
function clearAccountInfo() {
document.querySelector("#lisk-address").innerHTML = "";
document.querySelector("#lisk-balance").innerHTML = "";
document.querySelector("#lisk-passphrase").innerHTML = "";
document.querySelector("#lisk-bufferAddress").innerHTML = "";
document.querySelector("#lisk-publicKey").innerHTML = "";
document.querySelector("#lisk-transactions").innerHTML = "";
}
/*
* アカウント作成処理
*/
function createAccount() {
// (0)アカウント情報初期化
clearAccountInfo();
// (1)パスフレーズを生成して画面に表示
const mnemonic = lisk.passphrase.Mnemonic.generateMnemonic();
document.querySelector("#lisk-passphrase").innerHTML = mnemonic;
// (2)アドレスと公開鍵を取得して画面に表示
const addressAndPublicKey = lisk.cryptography.getAddressAndPublicKeyFromPassphrase(mnemonic);
const bufferAddress = addressAndPublicKey.address;
const publicKey = addressAndPublicKey.publicKey;
document.querySelector("#lisk-bufferAddress").innerHTML = `(${bufferAddress.toString("hex")})`;
document.querySelector("#lisk-publicKey").innerHTML = publicKey.toString("hex");
// (3)アドレスを取得して画面に表示
const address = lisk.cryptography.getLisk32AddressFromAddress(bufferAddress);
document.querySelector("#lisk-address").innerHTML = address;
// (4)残高は0LSKとする
document.querySelector("#lisk-balance").innerHTML = "0LSK";
}
/*
* ログイン
*/
async function login() {
// (0)アカウント情報初期化
clearAccountInfo();
// (1)入力されたパスフレーズを取得
const passphrase = document.querySelector("#enter-passphrase").value;
// (2)パスフレーズからlsk始まりのアドレスを取得
const address = lisk.cryptography.getLisk32AddressFromPassphrase(passphrase);
// (3)Lisk Service API の accounts を使用してアカウント情報を取得
const response = await fetch(`https://testnet-service.lisk.com/api/v2/accounts?address=${address}`);
const json = await response.json();
// (4)見つからなかった場合は終了
if (json.error) {
alert("アカウントが見つかりませんでした。");
return;
}
// (5)見つかった場合は画面に表示
const account = json.data[0];
document.querySelector("#lisk-address").innerHTML = account.summary.address;
document.querySelector("#lisk-balance").innerHTML = `${lisk.transactions.convertBeddowsToLSK(account.summary.balance)}LSK`;
document.querySelector("#lisk-passphrase").innerHTML = "ひみつ";
// (6)公開鍵とバッファアドレスはパスフレーズから取得して表示
const addressAndPublicKey = lisk.cryptography.getAddressAndPublicKeyFromPassphrase(passphrase);
document.querySelector("#lisk-bufferAddress").innerHTML = `(${addressAndPublicKey.address.toString("hex")})`;
document.querySelector("#lisk-publicKey").innerHTML = addressAndPublicKey.publicKey.toString("hex");
// (7)トランザクション情報取得して表示
showTransactions(address);
}
/*
* トランザクション情報表示
*/
async function showTransactions(address) {
// (1)指定のアドレスで送信または受信したトランザクション情報を取得
const response = await fetch(`https://testnet-service.lisk.com/api/v2/transactions?address=${address}&offset=0&limit=10`);
const json = await response.json();
// (2)見つからなかった場合は終了
if (json.error || json.data.length === 0) {
return;
}
let html_transactions = "";
for (data of json.data) {
html_transactions += `
<div>ID:${data.id}</div>
<div>タイプ:${data.moduleAssetName}</div>
<div>送信者:${data.sender.address === address? "あなた": data.sender.address}</div>
${data.asset.recipient === undefined? "":
`<div>受信者:${data.asset.recipient.address === address? "あなた": data.asset.recipient.address}</div>`
}
${data.asset.amount === undefined? "":
`<div>${data.sender.address === address? "送信":"受信"}枚数:${lisk.transactions.convertBeddowsToLSK(data.asset.amount)}LSK</div>`
}
<div>手数料:${lisk.transactions.convertBeddowsToLSK(data.fee)}LSK</div>
${data.asset.data === undefined? "":
`<div>データ:${data.asset.data}</div>`
}
<hr>
`;
}
document.querySelector("#lisk-transactions").innerHTML = html_transactions;
}
/*
* パスフレーズチェック
*/
function checkPassphrase(val) {
// (1)現在の入力値をチェック
const ret = lisk.passphrase.Mnemonic.validateMnemonic(val);
// (2)パスフレーズが正しくない場合はログインボタンを入力不可、正しい場合は入力可に変更
document.querySelector("#btn-login").disabled = !ret;
document.querySelector("#btn-send").disabled = !ret;
document.querySelector("#enter-recipient").disabled = !ret;
document.querySelector("#enter-amount").disabled = !ret;
document.querySelector("#enter-memo").disabled = !ret;
}
/*
* 送信処理
*/
async function send() {
// 画面の入力値を取得
const passphrase = document.querySelector("#enter-passphrase").value;
const amount = document.querySelector("#enter-amount").value;
const recipient = document.querySelector("#enter-recipient").value;
const memo = document.querySelector("#enter-memo").value;
// 入力チェック
try {
lisk.cryptography.validateLisk32Address(recipient);
} catch(_err) {
alert("送信先アドレスが不正です。");
return;
}
if (amount.length === 0 || amount < 0) {
alert("送信枚数が不正です。");
return;
}
// アカウント情報取得
const address = lisk.cryptography.getLisk32AddressFromPassphrase(passphrase);
const accountsResponse = await fetch(`https://testnet-service.lisk.com/api/v2/accounts?address=${address}`);
const accounts = await accountsResponse.json();
if (accounts.error) {
alert(`送信に失敗しました。\n${accounts.message}`);
return;
}
const account = accounts.data[0];
// 送信処理用のスキーマ情報を取得
const schemeResponse = await fetch(`https://testnet-service.lisk.com/api/v2/transactions/schemas?moduleAssetId=2:0`);
const schemes = await schemeResponse.json();
const scheme = schemes.data[0].schema;
// Liskネットワーク情報を取得
const networkResponse = await fetch(`https://testnet-service.lisk.com/api/v2/network/status`);
const network = await networkResponse.json();
const networkIdentifier = network.data.networkIdentifier;
// 送信トランザクション設定
const tokenTransferTx = {
moduleID: 2,
assetID: 0,
nonce: BigInt(account.sequence.nonce),
fee: BigInt(lisk.transactions.convertLSKToBeddows("0.1")),
signatures: [],
senderPublicKey: lisk.cryptography.getPrivateAndPublicKeyFromPassphrase(passphrase).publicKey,
asset: {
amount: BigInt(lisk.transactions.convertLSKToBeddows(amount)),
recipientAddress: lisk.cryptography.getAddressFromLisk32Address(recipient),
data: memo
}
}
// 送信トランザクションをパスフレーズで署名
const signedTx = lisk.transactions.signTransaction(
scheme,
tokenTransferTx,
lisk.cryptography.hexToBuffer(networkIdentifier),
passphrase
);
// 署名後のトランザクションをバイト配列にしたあと16進数表記の文字列に変換 const tx = lisk.cryptography.bufferToHex(lisk.transactions.getBytes(scheme, signedTx));
const res = await fetch(`https://testnet-service.lisk.com/api/v2/transactions?transaction=${tx}`, {method: 'POST'});
const result = await res.json();
if (result.error) {
alert(`送信に失敗しました。\n${result.message}`);
return;
}
alert(`送信に成功しました!\nトランザクションID:${result.transactionId}`);
}
</script>
</body>
</html>
前回からの変更点
HTMLで変わったのは以下の通りです
送信先アドレス入力欄の追加(enter-recipient)
送信枚数入力欄の追加(enter-amount)
メモ欄の追加(enter-memo)
送信ボタンの追加(btn-send)
パスフレーズ入力欄(enter-passphrase)の幅を変更
JavaScriptで変わったのは以下の通りです
送信処理(send)の追加
パスフレーズチェック処理(checkPassphrase)で追加した送信先アドレス欄などを有効なパスフレーズの場合にのみ入力可とするように変更
画面を開くとこんな感じ
ソースコードの説明:HTML
新しく出てくる内容はありません🙂
ソースコードの説明:JavaScript(lisk-client)
lisk.cryptography.validateLisk32Address
正しいアドレスかどうかを判定します。
間違ったアドレスの場合は例外が発生します。
lisk.transactions.convertLSKToBeddows
LSK単位の値をブロックチェーンで保持する単位に変換します。
(100000000倍)
lisk.cryptography.getPrivateAndPublicKeyFromPassphrase
パスフレーズから秘密鍵および公開鍵を取得します。
lisk.cryptography.getAddressFromLisk32Address
lsk始まりのアドレスからBuffer(バッファタイプ)のアドレスに変換します。
lisk.transactions.signTransaction
作成したトランザクション情報に署名します。
lisk.cryptography.bufferToHex
Bufferを16進数表記に変換します。
lisk.cryptography.hexToBuffer
16進数表記の文字列をBufferに変換します。
lisk.transactions.getBytes
バイト配列を取得します。
ソースコードの説明:JavaScript
try { [例外発生の可能性のある処理] } catch([変数]) { [例外時処理] }
発生する例外を受け取り何らかの処理を行う場合に使用します。
BigInt
最大9007199254740991、最小−9007199254740991までの整数値を格納することができます。
function send
送信処理です。
送信先のアドレスと送信枚数の入力チェックを行います。
また、LiskサービスAPIから各種情報を取得し、送信処理用のトランザクションを生成します。
トランザクションはパスフレーズで署名を行い、バイト配列を経由して16進数表記の文字列にしたものをLiskサービスAPIを使って処理しています。
生成するトランザクションは以下のような形式でなくてはいけません。
{
moduleID: 2,
assetID: 0,
nonce: BigInt,
fee: BigInt,
signatures: [],
senderPublicKey: Buffer,
asset: {
amount: BigInt,
recipientAddress: Buffer,
data: String
}
}
ソースコードの説明:Liskサービス API
https://testnet-service.lisk.com/api/v2/transactions/schemas
テストネットのトランザクション用のスキーマ情報を取得する際に使用するLisk公式のLiskサービスAPIです。
詳しくはこちらをご覧ください。
以下のようなJSONが返却されます。
{
"data": [
{
"moduleAssetId": "2:0",
"moduleAssetName": "token:transfer",
"schema": {
"$id": "lisk/transfer-asset",
"title": "Transfer transaction asset",
"type": "object",
"required": [
"amount",
"recipientAddress",
"data"
],
"properties": {
"amount": {
"dataType": "uint64",
"fieldNumber": 1
},
"recipientAddress": {
"dataType": "bytes",
"fieldNumber": 2
},
"data": {
"dataType": "string",
"fieldNumber": 3
}
}
}
},
....
]
}
https://testnet-service.lisk.com/api/v2/network/status
テストネットのネットワーク情報を取得する際に使用するLisk公式のLiskサービスAPIです。
詳しくはこちらをご覧ください。
以下のようなJSONが返却されます。
{
"data": {
"genesisHeight": 14075260,
"height": 16694403,
"finalizedHeight": 16694241,
"networkVersion": "3.1",
"networkIdentifier": "15f0dacc1060e91818224a94286b13aa04279c640bd5d6f193182031d133df7c",
"milestone": "4",
"currentReward": "100000000",
"rewards": {
"milestones": [
"500000000",
"400000000",
"300000000",
"200000000",
"100000000"
],
"offset": 2160,
"distance": 3000000
},
....
}
}
https://testnet-service.lisk.com/api/v2/transactions
テストネットのトランザクション情報を送信する際に使用するLisk公式のLiskサービスAPIです。
詳しくはこちらをご覧ください。
以下のようなJSONが返却されます。
{
"message": "Transaction payload was successfully passed to the network node",
"transactionId": "...."
}
おわりに
今回はここまで!
LSKの送信をやってみましたがいかがでしたでしょうか?
APIやlisk-client周りがいろいろ出てきたので難しかったかもしれませんね😅
ただここまで出来ればLisk Desktopを開かずにサクッとLSKを送信する処理を作ることもできますよ😉(定期的にどこかに送信するような場合とか便利です)
それではお疲れさまでした!
次回もよろしくおねがいしまーす🙂
万博おじについて
Liskに関するツールなど開発したりノード管理したりしています。
何かあればTwitter等でご連絡ください。
個人アカウント
Twitter:ys_mdmg
GitHub:lisknonanika
Discord:ys_mdmg#5646
Lisk Explorer:lisk observer, lisk scan
デリゲートアカウント(共同管理)
Twitter:liskcommulab
Discord:CommuLab#0097
Lisk Explorer:lisk observer, lisk scan
管理
ノード:Mainnet / Testnet
Lisk Service:Mainnet / Testnet
デリゲートサイト:Lisk CommuLab
この記事が気に入ったらサポートをしてみませんか?