この記事は nem /symbol Advent Calendar 2022 18日目の記事です。
前回の記事が2019年で3年前でしたので、だいぶ久しぶりになります。
Deno Symbol Cli
Symbol Block Chainを扱うcliをDenoで書きました。
https://github.com/naoki-maeda/deno-symbol-cli
公式が開発したcliがあったのですが、なぜかarchiveされていたのと出力がちょっとクセがある感じだったので、シンプルにjsonで出力したかったというのもあり、作ってみました。
https://github.com/symbol/symbol-cli
あとはDenoがnpm対応をして、Denoを使ってみたかったという気持ちがありました。
https://deno.com/blog/changes
Denoを使ってみての感想としては、Denoええなあというところでそのあたりはまた後ほど書きます。
deno-symbol-cliはsymbol-cliほど多機能ではなく、とりあえずは単にAPIを叩いてトランザクションやアカウントの確認とか、payloadの中身確認などの表示ぐらいです。開発補助ツールとして使用しています。
エラーハンドリングとかもあまりしていませんので、ご使用の際はご注意ください。
Install
deno
がまず必要です。
このあたりを参考にインストールしてください。
https://deno.land/manual@v1.29.0/getting_started/installation
インストールできれば deno install
で deno-symbol-cli
をインストールします。
--name
はcliのコマンド名を自由に設定してください。
良い名前が思いつかなくて、私はdeno-symbolを略して desy
で実行しています。
--unstable
はnpmのパッケージを使うために必要です。
symbol-sdkやsymbol-openapi-typescript-fetch-clientなどを使用しています。
--allow-*
は実行するパーミッションを設定します。
パーミッションを設定できるのはDenoの良いところだと思います。
後述しますが、接続先を制限することなどもできます。
deno install --force --name desy --allow-env --allow-read --allow-net --unstable https://deno.land/x/deno_symbol_cli@0.1.2/main.ts
インストールできれば、PATHを設定します。
すでに設定している場合は不要です。
コマンド例
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
参考
https://deno.land/manual@v1.29.0/tools/script_installer#script-installer
Usage
helpを表示しています。
Usage: Deno Symbol Cli
Version: 0.1.2
Description:
Symbol Cli tool with Deno
Options:
-h, --help - Show this help.
-V, --version - Show the version number for this program.
-e, --endpoint [endpoint] - Symbol API Endpoint (Default: "")
Commands:
help [command] - Show this help or the help of a sub-command.
tx <hash> - Get transaction info from hash
payload <payload> - Get decoded transaction from payload
account <account> - Get account info from address or public key
block <height> - Get block info from block number
mosaic <id> - Get mosaic info from mosaic id
chain - Get current chain info
validate <address> - Check if valid address
一部サンプルを紹介します。
tx
トランザクションの詳細を表示します。
-eにエンドポイント、引数にトランザクションハッシュを設定します。
Explorerに設定されていたノードの1つを使用させていただいてます、ありがとうございます。
desy tx -e https://001-sai-dual.symboltest.net:3001 80C1EA76D7CB2E6176965D9163B26EBD5B29441E9C1667ACC9BEA0970DECF943
{
"id": "639AC24D8EFA5A77741C0504",
"meta": {
"height": "86590",
"hash": "80C1EA76D7CB2E6176965D9163B26EBD5B29441E9C1667ACC9BEA0970DECF943",
"merkleComponentHash": "80C1EA76D7CB2E6176965D9163B26EBD5B29441E9C1667ACC9BEA0970DECF943",
"index": 0,
"timestamp": "3836154260",
"feeMultiplier": 1136363
},
"transaction": {
"size": 176,
"signature": "8F48269B848CA61E313C53C9DED53FE587FB196B7773578C38490580E8BA16A84AE343595AC0192AA855687A986B586147E8EF092696EA23926F8FD1717BB105",
"signerPublicKey": "81EA7C15E7EC06261C9F654F54EAC4748CFCF00E09A8FE47779ACD14A7602004",
"version": 1,
"network": 152,
"type": 16724,
"maxFee": "200000000",
"deadline": "3843329258",
"recipientAddress": "98AD63F8D6D8F6D81DD84889E021D4DAFFE11625B3BFC6F7",
"mosaics": [
{
"id": "72C0212E67A08BCE",
"amount": "300000000"
}
]
}
}
payload
payloadからトランザクションの詳細を取得します。
desy payload B800000000000000B32D24AC856CC7DF377D17665956FAB3DB3566E696A19217F5CAF58FA8FFE794BDEFCC91A7275BD60D1FAB2F7F2E564918105003B4D08
269CCEB8FC997E50E01064067C0A7D35B79128562F9B0B88D9309A234EA362DC8E91D181A4AB36A510C0000000001985441E047000000000000990531AA0C000000986E2B2B7DEBD6D3D311D58BD5272E71EAFF69FD1A600B4808000
100000000003CE19A057E831F090100000000000000006D657373616765
{
"transaction": {
"type": 16724,
"network": 152,
"version": 1,
"maxFee": "18400",
"deadline": "54394946969",
"signature": "B32D24AC856CC7DF377D17665956FAB3DB3566E696A19217F5CAF58FA8FFE794BDEFCC91A7275BD60D1FAB2F7F2E564918105003B4D08269CCEB8FC997E50E01",
"signerPublicKey": "064067C0A7D35B79128562F9B0B88D9309A234EA362DC8E91D181A4AB36A510C",
"recipientAddress": {
"address": "TBXCWK355PLNHUYR2WF5KJZOOHVP62P5DJQAWSA",
"networkType": 152
},
"mosaics": [
{
"amount": "1",
"id": "091F837E059AE13C"
}
],
"message": "006D657373616765"
}
}
account
$ desy account -e https://001-sai-dual.symboltest.net:3001 TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY
{
"id": "63616A02E1C4141A71FF485C",
"account": {
"version": 1,
"address": "98223AF34A98119217DC2427C6DE7F577A33D8242A2F54C3",
"addressHeight": "1",
"publicKey": "81EA7C15E7EC06261C9F654F54EAC4748CFCF00E09A8FE47779ACD14A7602004",
"publicKeyHeight": "4706",
"accountType": 0,
"supplementalPublicKeys": {},
"activityBuckets": [
{
"startHeight": "86580",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351774314155995"
},
{
"startHeight": "86400",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351776311901277"
},
{
"startHeight": "86220",
"totalFeesPaid": "0",
"beneficiaryCount": 0,
"rawScore": "351777859753096"
},
{
"startHeight": "86040",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351780520028457"
},
{
"startHeight": "85860",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351783136454326"
}
],
"mosaics": [
{
"id": "72C0212E67A08BCE",
"amount": "370971832675744"
}
],
"importance": "351774314155995",
"importanceHeight": "86580"
}
}
-cオプションを使うとMosaicをdivisibilityを考慮した数字で表示します。
desy account -e https://001-sai-dual.symboltest.net:3001 TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY -c
{
"id": "63616A02E1C4141A71FF485C",
"account": {
"version": 1,
"address": "98223AF34A98119217DC2427C6DE7F577A33D8242A2F54C3",
"addressHeight": "1",
"publicKey": "81EA7C15E7EC06261C9F654F54EAC4748CFCF00E09A8FE47779ACD14A7602004",
"publicKeyHeight": "4706",
"accountType": 0,
"supplementalPublicKeys": {},
"activityBuckets": [
{
"startHeight": "86580",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351774314155995"
},
{
"startHeight": "86400",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351776311901277"
},
{
"startHeight": "86220",
"totalFeesPaid": "0",
"beneficiaryCount": 0,
"rawScore": "351777859753096"
},
{
"startHeight": "86040",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351780520028457"
},
{
"startHeight": "85860",
"totalFeesPaid": "199999888",
"beneficiaryCount": 0,
"rawScore": "351783136454326"
}
],
"mosaics": [
{
"id": "72C0212E67A08BCE",
"amount": "370971832.675744" // ここがconvertされている
}
],
"importance": "351774314155995",
"importanceHeight": "86580"
}
}
こういう感じでシンプルにjsonで表示するだけのツールです。
Denoの良いところ
Denoを触ってみて、とても開発体験が良かったので私が思った良いとこを紹介したいと思います。
- パーミッションをコマンドごとに設定できる
- typescriptをすぐ実行できる
- コマンドラインツールのcliffyが使いやすい
- deno.landのpublishが楽
パーミッションを設定できる
ファイルシステムやネットワークアクセスなどは明示的に許可しないと実行できません。
参考 https://deno.land/manual@v1.28.3/basics/permissions
例えば今回でいうと、特定のノードのエンドポイントにしかアクセスしたくない場合は(そういうケースがあるのかは置いておいて)以下のようにできます。
deno install --force --name desy --allow-env --allow-read --allow-net=001-sai-dual.symboltest.net --unstable https://deno.land/x/deno_symbol_cli@0.1.2/main.ts
typescriptをすぐ実行できる
ts-nodeなどの選択肢はあるかと思いますが、typescriptを実行するのは準備が面倒だと思ってます。
サクッと deno run
で実行できるのはとても良いです。
開発するにはeslintとかprettierとかも必要になるかと思いますが、denoにはデフォルトで deno fmt
と deno lint
があるのも嬉しいところです。
コマンドラインツールのcliffyが使いやすい
私が知らないだけかもですが、TypescriptやNode.jsでコマンドライン作成ツールで使いやすいと感じるものはあまりありませんでした。
今回使用したcliffyはとても使いやすく、これから簡単なcliツールはこれで作ろうと思いました。
cliffy
https://cliffy.io/
https://deno.land/x/cliffy@v0.25.5
今回のdeno-symbol-cliの例を載せておきます。
endpointだけglobalなoptionとして設定しています。
new Command()
.name("Deno Symbol Cli")
.version("0.1.2")
.description("Symbol Cli tool with Deno")
.default("help")
.globalOption("-e, --endpoint [endpoint:string]", "Symbol API Endpoint", {
default: "",
})
.globalType("endpoint", new StringType())
// help
.command("help", new HelpCommand())
// tx
.command("tx <hash:string>", "Get transaction info from hash")
.option("-s, --status [status:boolean]", "Get transaction status info only", {
default: false,
})
.action(async (option, hash) => await tx(String(option.endpoint), hash, option.status))
// payload
.command("payload <payload:string>", "Get decoded transaction from payload")
.action((_, payloadStr) => payload(payloadStr))
// account
.command(
"account <account:string>",
"Get account info from address or public key",
)
.option(
"-c, --convert [convert:boolean]",
"Get converted Mosaics account info",
{
default: false,
},
)
.action(async (option, accountStr) => await account(String(option.endpoint), accountStr, option.convert))
// block
.command("block <height:string>", "Get block info from block number")
.action(async (option, height) => await block(String(option.endpoint), height))
// mosaic
.command("mosaic <id:string>", "Get mosaic info from mosaic id")
.action(async (option, id) => await mosaic(String(option.endpoint), id))
// chain
.command("chain", "Get current chain info")
.action(async (option) => await chain(String(option.endpoint)))
// validate
.command("validate <address:string>", "Check if valid address")
.action((_, address) => validate(address))
.parse(Deno.args);
このようにメソッドチェインで繋げていきます。
actionを設定することで、設定したコマンドで実行してくれます。
helpも自動で作成してくれ、Usageに載せているのがhelpを実行したものです。
cliffyシンプルでとても良いと思います。
deno.landのpublishが楽
こちらの通りにGitHubにwebhookを設定するだけです。
https://deno.land/add_module
あとはtag切ってpushすれば自動で deno.land/x
に反映されます。
deno_symbol_cliです。
https://deno.land/x/deno_symbol_cli@0.1.2
Deno所感
Denoについては、やはりnpmに対応してくれたのは既存のpackageを使えますし、非常にありがたいです。
npmのcompileにも対応してくれて、バイナリ化できればさらにありがたいです。
issueはあってそのうち対応されると思うので、気長に待ちたいと思います。
https://github.com/denoland/deno/issues/16632
今回触ってみてDenoはとても開発体験が良いと思いました。
簡単なscript的なものはPythonで書くことが多かったのですが、これからはDenoで書くようになる気がします。
まとめ
deno-symbol-cliとDenoで開発してみて良いと感じたところを紹介しました。
多分これから機能もちょこちょこ追加していくと思います。
バグや要望などあれば気軽にコメントやissueなどあげていただければと思います。