見出し画像

Lisk SDK (lisk-client)を使いながらJavaScriptを勉強してみない? - おまけ -

こんばんは!本日2回目の万博おじです。
おなかすいた。。

はじめに

前回までにやったお勉強用ソースコードをほんの少し修正して、CSSを適用したものを貼り付けておきます。
興味のある人はご覧ください🙂

前回

お勉強用ソースコード

JavaScriptの関数は
function [関数名] ([引数]) { [処理] } という実装方法から
const [関数名] = ([引数]) => { [処理] } に変更しています。
こういう風にも書けるのかーとおもいつつご覧ください。

ソースコードはgithubにも置いています。

画面を触ることもできます。

ℹ️手抜きなのでスマホ対応なんかしてません🤣
(操作はできると思いますがレイアウトぇ。。)

index.html

<!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>
		<script src="js/common.js" defer></script>
		<link rel="stylesheet" href="css/common.css">
	</head>
	<body>
        <div id="area-login">
            <h4>パスフレーズ:</h4>
            <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>

        <div id="area-logoff" style="display: none;">
            <button type="button" id="btn-logoff" style="width: 150px;" onclick="logoff()">ログオフ</button>
        </div>

        <div id="area-send" style="display: none;">
            
            <h4>送信先(必須):</h4>
            <div>
                <input type="text" id="enter-recipient" style="width: 750px;" placeholder="送信先アドレスを入力してください (例:lsk9g3k58b3gzcykjyaob9ekbt3a7b3e586h4gkxj)" oninput="showMinFee()"/>
            </div>
            
            <h4>送信枚数(必須):</h4>
            <div>
                <input type="number" id="enter-amount" style="width: 750px;" placeholder="送信枚数を入力してください (0以上の数値)" oninput="showMinFee()"/>
            </div>

            <h4>メモ:</h4>
            <div>
                <input type="text" id="enter-memo" style="width: 750px;" placeholder="メモを入力してください" oninput="showMinFee()"/>
            </div>

            <h4>手数料:</h4>
            <div id="disp-fee">???LSK</div>

            <div>
                <button type="button" id="btn-send" style="width: 150px;" onclick="send()">送信</button>
                <button type="button" id="btn-reload" style="width: 150px;" onclick="reload()">情報を最新化</button>
            </div>
        </div>

        <div id="area-testPassphrase">
            <h4>テスト用パスフレーズ:</h4>
            <div>
                abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
            </div>
        </div>

        <div id="area-accountInfo" style="display: none;">
            <!-- (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>
        </div>

        <div id="area-transactions" style="display: none;">
            <!-- (5)残高の表示場所 -->
            <h4>トランザクション(直近10件):</h4>
            <div id="lisk-transactions"></div>
        </div>

		<script>
            let _account = {};
            
			/*
			 * アカウント情報初期化
			 */
			const clearAccountInfo = () => {
				document.querySelector("#area-accountInfo").style.display = "none";
				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 = "";
			}

			/*
			 * トランザクション情報初期化
			 */
			const clearTransactions = () => {
				document.querySelector("#area-transactions").style.display = "none";
				document.querySelector("#lisk-transactions").innerHTML = "";
			}

            /*
             * クリア処理
             */
            const clear = () => {
                clearAccountInfo();
                clearTransactions();
            }

            /*
             * アカウント作成処理
             */
            const createAccount = () => {
                // パスフレーズを生成して画面に表示
                const mnemonic = lisk.passphrase.Mnemonic.generateMnemonic();
                document.querySelector("#lisk-passphrase").innerHTML = mnemonic;

                // アドレスと公開鍵を取得して画面に表示
                const addressAndPublicKey = lisk.cryptography.getAddressAndPublicKeyFromPassphrase(mnemonic);
                const bufferAddress = addressAndPublicKey.address;
                const publicKey = addressAndPublicKey.publicKey;
                document.querySelector("#lisk-bufferAddress").innerHTML = `(${lisk.cryptography.bufferToHex(bufferAddress)})`;
                document.querySelector("#lisk-publicKey").innerHTML = lisk.cryptography.bufferToHex(publicKey);

                // アドレスを取得して画面に表示
                const address = lisk.cryptography.getLisk32AddressFromAddress(bufferAddress);
                document.querySelector("#lisk-address").innerHTML = address;

                // 残高は0LSKとする
                document.querySelector("#lisk-balance").innerHTML = "0LSK";

                // アカウント情報エリアを表示
				document.querySelector("#area-accountInfo").style.display = "block";
            }

            /*
             * アカウント情報表示
             */
            const showAccount = async(address) => {
                // アカウント情報を取得し、見つからなかった場合は終了
                const account = await getAccount(address);
                if (account.error) {
                    alert("アカウントが見つかりませんでした。");
                    return false;
                }
                _account = account;

                // 見つかった場合は画面に表示
                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 = "ひみつ";

                // 公開鍵とバッファアドレスはパスフレーズから取得して表示
				const passphrase = document.querySelector("#enter-passphrase").value;
                const addressAndPublicKey = lisk.cryptography.getAddressAndPublicKeyFromPassphrase(passphrase);
                document.querySelector("#lisk-bufferAddress").innerHTML = `(${lisk.cryptography.bufferToHex(addressAndPublicKey.address)})`;
                document.querySelector("#lisk-publicKey").innerHTML = lisk.cryptography.bufferToHex(addressAndPublicKey.publicKey);

                // アカウント情報エリアを表示
                document.querySelector("#area-accountInfo").style.display = "block";

                return true;
            }

            /*
             * トランザクション情報表示
             */
            const showTransactions = async(address) => {
                // トランザクション情報を取得し、見つからなかった場合は終了
                const transactions = await getTransactions(address);
                if (transactions.error || transactions.length === 0) {
                    return;
                }
                
                // 取得した情報を画面に表示
                let html_transactions = "";
                for (data of transactions) {
                    html_transactions += `
                        <div class="row-transaction">
                        <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>`
                        }
                        </div>
                    `;
                }
                document.querySelector("#lisk-transactions").innerHTML = html_transactions;

                // トランザクション情報エリアを表示
                document.querySelector("#area-transactions").style.display = "block";
            }

			/*
			 * ログイン 
			 */
			const login = async() => {
                // アカウント情報、トランザクション情報を初期化
                clearAccountInfo();
                clearTransactions();

				// パスフレーズからlsk始まりのアドレスを取得
				const passphrase = document.querySelector("#enter-passphrase").value;
				const address = lisk.cryptography.getLisk32AddressFromPassphrase(passphrase);

				// アカウント情報が取得できたら表示、取得できなければ終了
                if (!await showAccount(address)) return;

				// トランザクション情報取得して表示
				showTransactions(address);

                // ログインエリア、テスト用パスフレーズエリアを非表示
                document.querySelector("#area-login").style.display = "none";
                document.querySelector("#area-testPassphrase").style.display = "none";

                // ログオフエリア、送信エリアを表示
                document.querySelector("#area-logoff").style.display = "block";
                document.querySelector("#area-send").style.display = "block";
			}

            /*
             * ログオフ
             */
            const logoff = () => {
                // アカウント情報、トランザクション情報を初期化
                clearAccountInfo();
                clearTransactions();

                // ログオフエリア、送信エリアを非表示
                document.querySelector("#area-logoff").style.display = "none";
                document.querySelector("#area-send").style.display = "none";

                // 入力値をクリア
				document.querySelector("#enter-passphrase").value = "";
				document.querySelector("#enter-amount").value = "";
				document.querySelector("#enter-recipient").value = "";
				document.querySelector("#enter-memo").value = "";
                document.querySelector("#disp-fee").innerHTML = "???LSK";

                // ログインエリア、テスト用パスフレーズエリアを表示
                document.querySelector("#area-login").style.display = "block";
                document.querySelector("#area-testPassphrase").style.display = "block";

                // 値をクリア
                _account = {};
                initializeCommonValue();
            }

            /*
             * リロード
             */
            const reload = async() => {
				// パスフレーズからlsk始まりのアドレスを取得
				const passphrase = document.querySelector("#enter-passphrase").value;
				const address = lisk.cryptography.getLisk32AddressFromPassphrase(passphrase);

				// アカウント情報が取得できたら表示、取得できなければ終了
                if (!await showAccount(address)) return;

				// トランザクション情報取得して表示
				showTransactions(address);
            }

			/*
			 * パスフレーズチェック
			 */
			const checkPassphrase = (val) => {
                // パスフレーズが正しくない場合はログインボタンを入力不可、正しい場合は入力可に変更
				const ret = lisk.passphrase.Mnemonic.validateMnemonic(val);
				document.querySelector("#btn-login").disabled = !ret;
			}

            /*
             * トランザクション生成
             */
            const createTransaction = () => {
                // 画面の入力値を取得
				const passphrase = document.querySelector("#enter-passphrase").value;
				const amount = document.querySelector("#enter-amount").value || "0";
				const recipient = document.querySelector("#enter-recipient").value;
				const memo = document.querySelector("#enter-memo").value;

				// 送信トランザクション設定
				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
					}
				}
                return tokenTransferTx;
            }

            /*
             * トランザクション手数料取得処理
             */
            const getMinFee = async() => {
                const scheme = await getTransferSchema();
                const networkIdentifier = await getNetworkIdentifier();
                const tokenTransferTx = createTransaction();
                return lisk.transactions.computeMinFee(scheme, tokenTransferTx, {});
            }

            /*
             * トランザクション手数料表示処理
             */
            const showMinFee = async() => {
				// 画面の入力値を取得
				const amount = document.querySelector("#enter-amount").value || "0";
				const recipient = document.querySelector("#enter-recipient").value;

				// 手数料計算できない場合は終了
				try {
					lisk.cryptography.validateLisk32Address(recipient);
				} catch(_err) {
                    document.querySelector("#disp-fee").innerHTML = "???LSK";
					return;
				}

				if (+amount < 0) {
                    document.querySelector("#disp-fee").innerHTML = "???LSK";
					return;
				}

                // 手数料を取得して表示
                const fee = await getMinFee();
                document.querySelector("#disp-fee").innerHTML = `${lisk.transactions.convertBeddowsToLSK(fee.toString())}LSK`;
            }

			/*
			 * 送信処理
			 */
			const send = async() => {
				// 画面の入力値を取得
				const passphrase = document.querySelector("#enter-passphrase").value;
				const amount = document.querySelector("#enter-amount").value || "0";
				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 < 0) {
					alert("送信枚数が不正です。");
					return;
				}

                // 最新のアカウント情報取得するために再読み込み
                await reload();

				// 送信トランザクション設定
				const tokenTransferTx = await createTransaction();

				// 手数料を取得して送信トランザクションに再設定
				const fee = await getMinFee();
				tokenTransferTx.fee = fee;

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

                // 送信処理用のスキーマ情報を取得
                const scheme = await getTransferSchema();

                // Liskネットワーク情報を取得
                const networkIdentifier = await getNetworkIdentifier();

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

js/common.js

let _transferSchema = "";
let _networkIdentifier = "";

/*
 * 初期化
 */
const initializeCommonValue = () => {
    _transferSchema = "";
    _networkIdentifier = "";
}

/*
 * アカウント情報取得
 */
const getAccount = async(address) => {
    const response = await fetch(`https://testnet-service.lisk.com/api/v2/accounts?address=${address}`);
    const json = await response.json();
    if (json.error) return json;
    return json.data[0];
}

/*
 * トランザクション情報取得
 */
const getTransactions = async(address) => {
    const response = await fetch(`https://testnet-service.lisk.com/api/v2/transactions?address=${address}&offset=0&limit=10`);
    const json = await response.json();
    if (json.error) return json;
    return json.data;
}

/*
 * 送信用スキーマ情報取得
 */
const getTransferSchema = async() => {
    if (!_transferSchema) {
        const schemeResponse = await fetch(`https://testnet-service.lisk.com/api/v2/transactions/schemas?moduleAssetId=2:0`);
        const schemes = await schemeResponse.json();
        _transferSchema = schemes.data[0].schema;
    }
    return _transferSchema;
}

/*
 * ネットワーク識別子取得
 */
const getNetworkIdentifier = async() => {
    if (!_networkIdentifier) {
        const networkResponse = await fetch(`https://testnet-service.lisk.com/api/v2/network/status`);
        const network = await networkResponse.json();
        _networkIdentifier = network.data.networkIdentifier;
    }
    return _networkIdentifier;
}

css/common.css

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;
    margin-top: 10px;
    margin-bottom: 10px;
}
h4 {
    margin: 0px;
}

#area-testPassphrase {
    margin-top: 10px;
    border-radius: 5px;
    border: 1px solid #99ad6e;
    background-color: #d8e9b2;
    padding: 10px;
}

#area-accountInfo {
    margin-top: 10px;
    border-radius: 5px;
    border: 1px solid #6e84ad;
    background-color: #dbecff;
    padding: 10px;
}

#lisk-passphrase,
#lisk-bufferAddress,
#lisk-publicKey,
#lisk-balance {
    margin-bottom: 10px;
}

#area-transactions {
    margin-top: 10px;
    border-radius: 5px;
    border: 1px solid #bd8484;
    background-color: #ffd9d9;
    padding: 10px;
}

#lisk-transactions .row-transaction {
    margin-top: 5px;
    margin-bottom: 5px;
    border-radius: 5px;
    border: 1px solid #696464;
    background-color: #f3eaea;
    padding: 10px;
}

画面を表示するとこんな感じ

ログイン前
アカウント作成ボタン押下後
ログインボタン押下後

おわりに

前回までにやったお勉強用ソースコードを少し手直しするだけでだいぶWEBシステムっぽくできますね。
このシリーズでやったこととインターネットさんに聞きつついろいろお勉強を続けてみてはいかがでしょうか?
ではお疲れさまでした!

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

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

万博おじについて

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

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

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