見出し画像

Lisk SDK (lisk-client)を使いながらJavaScriptを勉強してみない? - その6 -

こんばんは帰ってモンハンやりたい万博おじです。

今回は7回目、内容は「トランザクション手数料の取得」です。

  1. 準備

  2. JavaScriptの基本:パスフレーズを生成

  3. 文字列操作:パスフレーズからアドレスを取得

  4. 非同期処理:APIからアカウント情報を取得

  5. ループ処理:APIからトランザクション情報を取得

  6. トランザクションの生成と送信

  7. トランザクション手数料の取得

はじめに

前回はLSKを送信しましたが、手数料を固定値で設定していました。
Lisk Core v3でせっかく動的手数料に手数料が固定だなんて、ものすごくもったいない気がしますね😅
ということで、動的手数料化する方法です。
ではのんびりご覧ください🙂

前回

今回のお勉強用ソースコード

<!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 fee = lisk.transactions.computeMinFee(scheme, tokenTransferTx, {});
				tokenTransferTx.fee = fee;

				// 送信確認
				const answer = confirm(`${recipient} へ ${amount}LSK 送信します。\n送信手数料は${lisk.transactions.convertBeddowsToLSK(fee.toString())}LSKです。\nよろしいですか?`);
				if (!answer) return;

				// 送信トランザクションをパスフレーズで署名
				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>

前回からの変更点

JavaScriptで変わったのは以下の通りです

  • 送信処理(send)に手数料計算処理を追加(lisk.transactions.computeMinFee)

  • 送信前に確認ダイアログを表示するように変更(confirm)

ℹ️HTMLの変更点はありません

手数料計算と確認ダイアログはこんな感じ

手数料が固定値0.1LSKではないことがわかりますね~

ソースコードの説明:HTML

新しく出てくる内容はありません🙂

ソースコードの説明:JavaScript(lisk-client)

lisk.transactions.computeMinFee

手数料計算機能です。
送信したいトランザクションに合ったスキーマ情報と署名前のトランザクション情報を渡すことで最小の手数料が取得できます。

ℹ️送信する情報量で手数料が決まります。
LSKの送信処理(token:transfer)では送信する枚数、データフィールドの設定値(メモ欄)、マルチシグネチャなどが手数料に影響します。

※ブロックチェーンに画像や動画を保存するようなサイドチェーンプロジェクトが出来た場合はもっと高くなるかもしれませんね。

⚠️デリゲート登録などは別途固定の手数料があるので注意

ソースコードの説明:JavaScript

alert

メッセージダイアログを表示します。

ℹ️お勉強用ソースコードではエラーの内容などの表示に使っています。
※前回出ていましたがすっかり忘れていました😅

confirm

確認ダイアログを表示します。
OKボタンが押されるとtrue、キャンセルボタンが押されるとfalseが返却されます。

ℹ️お勉強用ソースコードでは送信ボタンを押した後の確認に使っています。

おわりに

ということで、予定していた内容がすべて終わりました!
いかがでしたでしょうか?
ちょっとわかりにくい部分もあると思うので、不明点などは万博おじに気軽に聞いてください😉

ではお疲れさまでした!

このシリーズはマガジンにしています。読み直す際にご利用ください。

※画面下部の「ピックアップされています」からもアクセスできます。

万博おじについて

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

個人やデリゲート宛ての寄付ありがとうございます!
ノード管理や開発資金に充てさせて頂いています😊

この記事が気に入ったらサポートをしてみませんか?