猫でもできるモナカードビューアーの作り方
こんにちは。モナパちゃんです。
自分で「ちゃん」を付けるのは恥ずかしいのですが、「モナパ」だけでは単にMonapartyを指しているように聞こえるし、「ちゃん」まで含めて名前ということなのでこれはもう諦める他にありません。この悩みについてモナコインちゃんと愚痴り始めるといつもエンドレスになります。
閑話休題。今回はMonapartyを使ったアプリケーションの開発者を増やしたいということで、開発未経験の方にもできるチュートリアルとしてモナカードのビューアーを作ってみます。誰にでも必ず動かせるようにテキストエディタだけで作れるプリミティブな構成にして、変数名も意味の通りやすいものを使うようにしていますが、さすがにHTML/JavaScriptの仕様に関する細かい注釈は加えませんのでゼロからちゃんと理解するためには復習が要ることをご了承ください。もし要望がありそうなら、ほしいMONAリストで詳説や続編を出品します。
ポイント
Webアプリを作るのはとても簡単。MonapartyのAPIを利用するのもとても簡単。なので、Monapartyのアプリを作るのもとても簡単です。
作り方
モナコインアドレスを入力してボタンを押すとアドレスに入っているモナカードの画像が表示されるだけの、最低限のモナカードビューアーを作ります。まずは同じフォルダの中に以下の2つのファイルを作りましょう。
index.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>モナカードビューアー</title>
</head>
<body>
<input id="address_input" type="text" placeholder="アドレス">
<button id="show_button" type="button">モナカードを表示する</button>
<div id="show_frame"><!-- ここにモナカードを表示 --></div>
<script src="monacardviewer.js"></script>
</body>
</html>
monacardviewer.js バージョン1
{
const showMonacards = async() => {
const address = document.getElementById('address_input').value;
const showFrame = document.getElementById('show_frame');
const textTag = document.createTextNode(address);
showFrame.appendChild(textTag);
};
document.getElementById('show_button').addEventListener('click', showMonacards, false);
}
HTMLファイル(index.html)では
・アドレス入力欄(ID: address_input)
・ボタン(ID: show_button)
・モナカードを表示する場所(ID: show_frame)
を定義してからJavaScript(monacardviewer.js)を読み込み、JavaScriptでは関数showMonacardsを定義してから、ボタンが押されるとshowMonacardsが実行されるように設定しています。
ではさっそくindex.htmlをブラウザで開いてアドレスを入力しボタンを押してみましょう。アドレスは何でも構いませんが、私がこちらのアドレスに手頃なモナカードを入れておいたのでよかったらご利用ください。
MVqTaD2QY7DX5VKYo65M1VzqpCCvQ6ux6e
実行結果 バージョン1
さすがにまだ、モナカードが表示されたりはしません。これはshowMonacardsの中に以下の処理しか書いていないからです。
1. address_inputからアドレスを読み取る
(2). show_frameの中に読み取ったアドレスを表示する
モナカードの画像を表示するためには、以下の処理が必要です。
1. address_inputからアドレスを読み取る
2. アドレスに入っているトークンのリストを取得する
3. トークンに設定されたモナカードの情報を取得する
4. モナカードの画像をshow_frameの中に表示する
処理2にはMonapartyサーバのCounterblock APIを、処理3にはMonacardサーバのAPIをそれぞれ利用することができます。
Counterblock API
get_normalized_balancesでアドレスに入っているトークンを取得できる
Monacard API
card_detail?assets=...でトークンに設定されたモナカード情報を取得できる
それでは処理2から順に実装してみましょう。Counterblock APIへのアクセスはJSON-RPCという仕様に沿って行いますが、とくに難しいことはありません。monacardviewer.jsを以下のように書き換えましょう。
※ ここでは脇P先生が運用しているMonapartyサーバ(https://monapa.electrum-mona.org/_api)を利用します。脇山珠美ちゃんかわいい!
monacardviewer.js バージョン2
{
const showMonacards = async() => {
const address = document.getElementById('address_input').value;
const balances = await getMonapartyBalances(address);
const showFrame = document.getElementById('show_frame');
const textTag = document.createTextNode(JSON.stringify(balances));
showFrame.appendChild(textTag);
};
const getMonapartyBalances = async(address) => {
const url = 'https://monapa.electrum-mona.org/_api';
const body = {
jsonrpc: '2.0',
id: 0,
method: 'get_normalized_balances',
params: {
addresses: [address],
},
};
const requestParams = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(body),
};
const responseObject = await fetch(url, requestParams);
const response = await responseObject.json();
return response.result;
};
document.getElementById('show_button').addEventListener('click', showMonacards, false);
}
関数getMonapartyBalancesではJSON-RPCの作法に従ってCounterblock APIにリクエストを送り、レスポンスをパースして返します。showMonacardsのほうはアドレスをgetMonapartyBalancesに渡して、返ってきたものをshow_frameに表示するように書き換えました。
HTMLを再読込してバージョン1と同様に実行すると、トークンの入ったアドレスであれば次のような表示になるはずです。
実行結果 バージョン2
アドレスに入っているトークンのリストが取得できたので、次はトークンに設定されたモナカードの情報を取得します(処理3)。Monacard APIはクエリにトークン名を入れてリクエストするだけです。
monacardviewer.js バージョン3
{
const showMonacards = async() => {
const address = document.getElementById('address_input').value;
const balances = await getMonapartyBalances(address);
const assetReadableNames = balances.map(bala => bala.asset_longname || bala.asset);
const monacardDetails = await getMonacardDetails(assetReadableNames);
const showFrame = document.getElementById('show_frame');
const textTag = document.createTextNode(JSON.stringify(monacardDetails));
showFrame.appendChild(textTag);
};
const getMonapartyBalances = async(address) => {
const url = 'https://monapa.electrum-mona.org/_api';
const body = {
jsonrpc: '2.0',
id: 0,
method: 'get_normalized_balances',
params: {
addresses: [address],
},
};
const requestParams = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(body),
};
const responseObject = await fetch(url, requestParams);
const response = await responseObject.json();
return response.result;
};
const getMonacardDetails = async(assetReadableNames) => {
if (assetReadableNames.length === 0) return [];
const url = `https://card.mona.jp/api/card_detail?assets=${ assetReadableNames.join(',') }`;
const responseObject = await fetch(url);
const response = await responseObject.json();
if (response.error) throw response.error.message;
return response.details;
};
document.getElementById('show_button').addEventListener('click', showMonacards, false);
}
関数getMonacardDetailsではMonacard APIにリクエストを送り、レスポンスをパースして返します。showMonacardsのほうはトークンのリストからトークン名(サブトークンの場合はasset_longnameの値、サブトークンでない場合はassetの値)を抽出してgetMonacardDetailsに渡して、返ってきたものをshow_frameに表示するように書き換えました。
実行結果 バージョン3
ついにモナカードの情報を取得できました。あとは画像を表示するだけです(処理4)。
monacardviewer.js バージョン4(完成)
{
const showMonacards = async() => {
const address = document.getElementById('address_input').value;
const balances = await getMonapartyBalances(address);
const assetReadableNames = balances.map(bala => bala.asset_longname || bala.asset);
const monacardDetails = await getMonacardDetails(assetReadableNames);
const monacardImageURLs = monacardDetails.map(detail => detail.imgur_url);
refreshMonacardImages(monacardImageURLs);
};
const getMonapartyBalances = async(address) => {
const url = 'https://monapa.electrum-mona.org/_api';
const body = {
jsonrpc: '2.0',
id: 0,
method: 'get_normalized_balances',
params: {
addresses: [address],
},
};
const requestParams = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(body),
};
const responseObject = await fetch(url, requestParams);
const response = await responseObject.json();
return response.result;
};
const getMonacardDetails = async(assetReadableNames) => {
if (assetReadableNames.length === 0) return [];
const url = `https://card.mona.jp/api/card_detail?assets=${ assetReadableNames.join(',') }`;
const responseObject = await fetch(url);
const response = await responseObject.json();
if (response.error) throw response.error.message;
return response.details;
};
const refreshMonacardImages = (imageURLs) => {
const showFrame = document.getElementById('show_frame');
const fragment = document.createDocumentFragment();
for (const url of imageURLs) {
const imageTag = document.createElement('img');
imageTag.width = '280';
imageTag.src = url;
fragment.appendChild(imageTag);
}
while (showFrame.firstChild) showFrame.removeChild(showFrame.firstChild);
showFrame.appendChild(fragment);
};
document.getElementById('show_button').addEventListener('click', showMonacards, false);
}
関数refreshMonacardImagesでは各画像のURLに対してimgタグを生成しshow_frameに追加しています。showMonacardsのほうはモナカードのリストから画像のURL(imgur_url)を抽出してrefreshMonacardImagesに渡すように書き換えました。
実行結果 バージョン4(完成)
ちゃんとできていればこのようにモナカードの画像が表示されるはずです。オダイロイド1号ちゃんぺろぺろ。お疲れ様でした。
補足と蛇足
真面目に使えるものにするためにはもっとカード名を表示したりエラーが起きた場合の処理を足すところですが、それはどんどんMonapartyとは関係のない話になってしまうので今回はここまでにさせてください。
MonapartyのAPIとしてCounterblock APIだけを利用しましたが、もうひとつCounterparty APIというのも利用することができます。ちょっと複雑ですが、Counterparty APIのエンドポイントが直接公開されていない場合でもproxy_to_counterpartydというCounterblock APIのメソッドを経由してアクセスすることができます。
とにかく初めての方でも手が付けられるようにしたかったのでプリミティブな方法でご紹介しましたが、ちゃんと教育を受けたフロントエンドエンジニアは何かしらのフレームワークを利用するのが普通のようです。それでもすなぎもさんはつい先日まで全くフレームワークを使わずに(つまり上のコードの延長線でごりごり書き足していくだけで)いろいろと作っていたので、作りたいものがあるのなら作り方に気を遣う必要はないのかもしれませんね。
続編などの希望があればコメントください。ほしいMONAリストに反映します。