Cosmos系ブロックチェーン上の送金をプログラムで実行してみよう!CosmJSの使い方を解説!
プッシュ通知

はじめに

本記事は初心者向けにCosmJSの使い方をレクチャーしていきます。CosmJSとは、Cosmos SDKを用いたチェーンに対してクエリやトランザクションの実行を行うためのライブラリのことです。

このライブラリを用いることで、Cosmos系のチェーンやそれらの上にあるスマートコントラクトを利用したアプリケーションの作成やボット作成などができるようになります。

内容としては、Cosmos内のDeFi用スマートコントラクトのためのブロックチェーン『Neutron(ニュートロン)』のテストネットにて、アカウントの作成、ネイティブトークンとCW20トークンの残高の確認、およびトークンの送金までを解説していきます。

今回参照する『CW20トークン』やそれらを発行しているスマートコントラクトである『CosmWasm』に関しては、以前の記事で詳しく触れています!

関心がある方は以下の記事を読んでから本記事を読むことで、より体系的にCosmosのスマートコントラクトを用いたアプリケーション開発の方法を学ぶことができます。

用語の解説

実際の操作方法を解説する前に、本記事を読む上で知っておくべき用語を軽く共有しておきます。

既に知っている方は読み飛ばしていただいて問題ありません。

用語解説
CosmWasmCosmosエコシステム用に作られたスマートコントラクトプラットフォーム(ライブラリ)です。 CosmWasmを使用することで、Cosmos SDKを使用して構築されたブロックチェーンに対して、簡単にスマートコントラクトを作成することが可能です。

公式ドキュメント:https://docs.cosmwasm.com/docs/
NeutronCosmos SDKで構築されたクロスチェーンのスマートコントラクトプラットフォームです。Cosmosエコシステム上におけるDeFiアプリケーションの展開に特化したブロックチェーンとなっています。
CW20トークンの規格です。CW20はCosmWasm環境で動作する、Cosmos SDKベースのブロックチェーンで使用されるカスタムトークンの規格です。

全体の流れ

本記事の流れとしては、以下の5ステップに沿って進んでいきます。

開発環境と利用するツールの準備

②Neutronに接続してみよう

NTRNトークンの残高確認&送金

CW20トークンの残高確認&送金

おわりに

もし何かわからないことがあれば、Cosmos JapanのDiscordにて質問をしていただければ幸いです。親切な人が教えてくれるはずです!

開発環境と利用するツールの準備

まずは本記事の内容を進めるためのツールを準備していきましょう。

必要な言語、ツールのインストール

以下のツールをインストールしていきます。

  • Node.js
  • cosmjs-sandbox

Node.jsのインストール

Javascriptのコードを実行するための環境です。今回利用するCosmJSの実行に利用します。

各OSや環境に合わせて適切なインストール方法があるので、自身の判断でインストールをお願いします。著者はbrewnodeを用いて、v20.10.0を利用しています。

https://nodejs.org/en

cosmjs-sandboxのインストール

適当な場所で以下コマンドを実行し、cosmjsを利用するための基本的な環境を用意します。

本来のパッケージのままですと、クエリを実行した際にバグが発生するので、追加でパッケージの更新を行います。

git clone https://github.com/b9lab/cosmjs-sandbox.git

cd cosmjs-sandbox

npm install

// 以下をインストールしないとバグが発生するので注意
npm install @cosmjs/stargate@^0.32
npm install @cosmjs/cosmwasm-stargate@^0.32

なお、クローンしているファイルは以下のページに存在しています。

https://github.com/b9lab/cosmjs-sandbox.git

以上でCosmJSを実行するための環境の構築は完了です。

Neutronに接続してみよう

今回の記事では、最初からNeutronのテストネットであるpion-1に接続していきます。接続前に接続するためのアカウントを作成します。

CW20を発行する記事にて既にアカウントを生成している場合は、testnet.neutron.mnemonic.keyというファイルを作成し、その中にアカウントのmnemonicを貼り付けるだけで大丈夫です。

アカウントの作成

generateMnemonic.tsファイルを作成し、以下のコードを用意しましょう。

import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";

const generateKey = async (): Promise<void> => {
    const walletOptions = { prefix: "neutron" };
    const wallet: DirectSecp256k1HdWallet = await DirectSecp256k1HdWallet.generate(24, walletOptions);
    process.stdout.write(wallet.mnemonic);
    const accounts = await wallet.getAccounts();
    console.error("Mnemonic with 1st account:", accounts[0].address);
}

generateKey();


乱数の生成を行い、mnemonicの生成とneutronの形式のアカウントの用意をしてくれています。

prefixの設定部分を他のチェーンに対応するように変更すると、それぞれのチェーンに対応したアカウントを利用できるようになります。

今回は以下のようにコマンドを実行することで、生成したmnemonicを別のファイルとして保存しています。これ以降は、ここで生成したmnemonicのファイルを参照しながらアカウントの操作を行います。

npx ts-node generateMnemonic.ts > testnet.neutron1.mnemonic.key

Mnemonic with 1st account: neutron1g52tvje8war7hn37zuzjljsefns29gz3sqxyh7

コマンド実行後、testnet.neutron.mnemonic.keyが生成されているはずです。

ターミナルに生成したアカウントの最初のアドレスが出力されるので、メモしておきましょう。後からテストネットトークンのfaucetなどで何度か利用することになります。

生成したmnemonicのファイルを間違って公開しないように、.gitignoreのファイルを以下のように追加編集しておきましょう。

node_modules
*.key

ブロック高を参照

アカウントの生成ができたので、実際にCosmJSを利用してブロックチェーン上の情報を参照していきましょう。まずは、現在のブロック高を参照してみます。

getBlockNumber.tsファイルを作成し、以下のコードを用意しましょう。

import { StargateClient } from "@cosmjs/stargate"

const rpc = "https://rpc-t.neutron.nodestake.top:443"


const runAll = async(): Promise<void> => {
    const client = await StargateClient.connect(rpc)
    console.log("With client, chain id:", await client.getChainId(), ", height:", await client.getHeight())
}

runAll()

以下のコマンドを実行することで、NeutronのテストネットのRPCを参照して作成したクライアントを用いて、チェーンのIDとブロック高を参照しています。

npx ts-node getBlockNumber.ts

With client, chain id: pion-1 , height: 8206570

以上の情報が参照できれば、問題なくrpcを介してNeutronのテストネットpion-1に接続できています。もしこれらがうまく参照できない場合は、rpcのノードを変更することをおすすめします。

rpcノード参考

https://github.com/cosmos/chain-registry/blob/master/testnets/neutrontestnet/chain.json#L119

NTRNトークンの残高確認&送金

NTRNの残高確認

アカウントの生成と、テストネットへの接続もできたので、実際にそのアカウントが持つトークンの残高を参照していきます。CW20トークンを発行したアカウントのmnemonicを参照している方は既にNTRNのfaucetを実行済みでトークンを持っているかもしれませんが、本記事で初めてアカウントを作成した場合はまだ何もトークンを持っていないので、残高は表示されないはずです。

getNtrnBalance.tsファイルを作成し、以下のコードを用意しましょう。

残高を知りたいアドレスを適宜入力してあげてください。

import { StargateClient } from "@cosmjs/stargate"

const rpc = "https://rpc-t.neutron.nodestake.top:443"


const runAll = async(): Promise<void> => {
  const client = await StargateClient.connect(rpc)
  console.log(
       "balances:",
       await client.getAllBalances("(自分のアカウントのアドレスを入れる)"), 
  )
}

runAll()

以下のコマンドを実行することで、先のブロック高参照と同じように、クライアントを利用して、指定したアドレスのネイティブトークンの残高を参照してくれています。Cosmosエコシステムの場合、IBCを経由して手に入れた別チェーンのトークンも、基本的にはこのネイティブトークンとして扱われます。このような仕様により、複数のネイティブトークンの残高が表示されることもあります。

pion-1ではdenom(暗号資産の最小単位)がuntrnであることも参照できますね。

npx ts-node getNtrnBalance.ts

// まだfaucetしていないアカウントでの表示
balances: []

// faucet済みのアカウントでの表示
balances: [ { denom: 'untrn', amount: '1867000' } ]

NTRNをfaucet

Faucet(フォーセット)とは、暗号資産やトークンを無料で配布するシステムです。今回は、テストネットで使用するトークンを入手するためにFaucetを使用します。

ここで入手するトークンは、後ほど行うコントラクトのデプロイなどに必要なガス代に使用します。先ほど作成したNeutronのアカウントに対し、Faucetを用いてトークンを入金しましょう。

以下より示す手順に従って、Faucetを使用してください。

まずNeutronの公式サイトに行き、一番下のDiscordのリンクをクリックして公式のDiscordに入ります。

Discordへの招待リンクは変更される可能性があるので、公式サイトからの案内を行なっています。

NeutronのDiscordに入れたら、#role-stationチャンネルでCommunity Rolesにて、虫の絵文字を押して、Developer Roleを獲得してください。

アカウント認証が終わったら、公式Discordの#testnet-faucetチャンネルに入り、faucetのコマンドを実行します。

以下のようにfaucetのコマンドを実行してください。

faucetのコマンドは、一度実行した後、同じアカウントからは24時間経たないと再実行できないため、間違ったアドレスを入力しないように注意が必要です。

$request (自分のアカウントのアドレス)

これらのコマンドを実行をすると、少しまった後にBotが指定したアドレスに対してNTRNの送金を行なってくれます。送金が完了していたら、先ほどのgetNtrnBalance.tsファイルを再度実行して、残高を参照してみましょう。

もう一度残高を参照

$ npx ts-node getNtrnBalance.ts

NTRNを送金してみる

テストネット上のNTRNトークンを持っている状態になったので、次はそれらを送金するコードを用意してみましょう。

実際に送金のコードを用意する前に、以下コマンドを実行して、受け取り側のもう一つのアカウントも作成しておきましょう。この際に表示されるアドレスも受け取り側のアドレスとして控えておきましょう。

npx ts-node generateMnemonic.ts > testnet.neutron2.mnemonic.key

Mnemonic with 1st account: neutron1q0ukshk36k8dy6vn4nc8m7lyjjnpr9jffqkurh

transferNtrn.tsファイルを作成し、以下のコードを用意しましょう。

import { SigningStargateClient, StargateClient } from "@cosmjs/stargate"
import { DirectSecp256k1HdWallet, OfflineDirectSigner } from "@cosmjs/proto-signing"
import { readFile } from "fs/promises"

const rpc = "https://rpc-t.neutron.nodestake.top:443"

const getSignerFromMnemonicFile = async (filePath: string): Promise<OfflineDirectSigner> => {
  const mnemonic = (await readFile(filePath)).toString().trim();
  return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
    prefix: "neutron",
  });
};

const runAll = async(): Promise<void> => {
  const aliceSigner: OfflineDirectSigner = await getSignerFromMnemonicFile("./testnet.neutron1.mnemonic.key")
  const alice = (await aliceSigner.getAccounts())[0].address
  const bobSigner: OfflineDirectSigner = await getSignerFromMnemonicFile("./testnet.neutron2.mnemonic.key")
  const bob = (await bobSigner.getAccounts())[0].address
  console.log("sender address", alice)
  console.log("recipient address", bob)
  const signingClient = await SigningStargateClient.connectWithSigner(rpc, aliceSigner)
  // Execute the sendTokens Tx and store the result
  const result = await signingClient.sendTokens(
    alice,
    bob,
    [{ denom: "untrn", amount: "10000" }],
    {
      amount: [{ denom: "untrn", amount: "5000" }],
      gas: "200000",
    },
  )
	console.log(result);
}

runAll()

getSignerFromMnemonicFileという関数を用意して、先ほど用意していたmnemonicのファイルを読み込み、アカウントを作成しています。

2つのmnemonicのファイルから2つのアカウントを再度作成し、一つのアカウントを用いて、署名を行うクライアントを作成。そのクライアントが持つsendTokens関数経由でNTRNトークンを送金するための署名付きトランザクションの作成とそのブロードキャストを実行しています。

ここでは仮に送金者のアドレスをalice,受金者のアドレスをbobとしています。

sendTokens関数の中は、送金者アドレス、受金者アドレス、送金するトークンに関する情報(denomと送金量)、ガス関連の設定を指定していることがわかります。

ちなみに、Cosmos系のネイティブトークンのアセットのdecimalは6がデフォルトなので、上記のコードでは、0.01NTRNを送金するコードになっています。

コマンドを実行すると、以下のように、送金者と受金者のアドレスを表示されます。問題なくトランザクションが実行できれば、以下のようにログが表示されます。

npx ts-node transferNtrn.ts

sender address neutron1g52tvje8war7hn37zuzjljsefns29gz3sqxyh7
recipient address neutron1q0ukshk36k8dy6vn4nc8m7lyjjnpr9jffqkurh
{
  code: 0,
  height: 8207867,
  txIndex: 0,
  events: [
    { type: 'coin_spent', attributes: [Array] },
    { type: 'coin_received', attributes: [Array] },
    { type: 'transfer', attributes: [Array] },
    { type: 'message', attributes: [Array] },
    { type: 'tx', attributes: [Array] },
    { type: 'tx', attributes: [Array] },
    { type: 'tx', attributes: [Array] },
    { type: 'message', attributes: [Array] },
    { type: 'coin_spent', attributes: [Array] },
    { type: 'coin_received', attributes: [Array] },
    { type: 'transfer', attributes: [Array] },
    { type: 'message', attributes: [Array] }
  ],
  rawLog: '[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},{"key":"sender","value":"neutron1g52tvje8war7hn37zuzjljsefns29gz3sqxyh7"},{"key":"module","value":"bank"}]},{"type":"coin_spent","attributes":[{"key":"spender","value":"neutron1g52tvje8war7hn37zuzjljsefns29gz3sqxyh7"},{"key":"amount","value":"10000untrn"}]},{"type":"coin_received","attributes":[{"key":"receiver","value":"neutron1q0ukshk36k8dy6vn4nc8m7lyjjnpr9jffqkurh"},{"key":"amount","value":"10000untrn"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"neutron1q0ukshk36k8dy6vn4nc8m7lyjjnpr9jffqkurh"},{"key":"sender","value":"neutron1g52tvje8war7hn37zuzjljsefns29gz3sqxyh7"},{"key":"amount","value":"10000untrn"}]},{"type":"message","attributes":[{"key":"sender","value":"neutron1g52tvje8war7hn37zuzjljsefns29gz3sqxyh7"}]}]}]',
  transactionHash: 'D3F83641B073BAD011D11B58ED10988B37216D2C6683E90D49208FC15BA2230D',
  msgResponses: [
    {
      typeUrl: '/cosmos.bank.v1beta1.MsgSendResponse',
      value: Uint8Array(0) []
    }
  ],
  gasUsed: 72694n,
  gasWanted: 200000n
}

これにてNTRNトークンの残高確認と送金がCosmJSからできるようになりました。

CW20トークンの残高確認&送金

CW20トークンの残高参照

次は、CosmosのスマートコントラクトモジュールCosmWasm上で発行したCW20という規格のトークンの残高参照と、それらの送金をCosmJSから行なってみましょう。

CW20トークン自体の発行に関しては、以前の記事で解説を行なっています。

getCw20Balance.tsファイルを作成し、以下のコードを用意しましょう。

残高を知りたいアドレスを適宜入力してあげてください。

import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'

const rpc = "https://rpc-t.neutron.nodestake.top:443"

const cw20ContractAddress = "(CW20トークンのコントラクトアドレス)";

const queryBalance = async () => {
  const client = await SigningCosmWasmClient.connect(rpc)
  const result = await client.queryContractSmart(cw20ContractAddress, {
    balance: { address: "(CW20トークンを所有しているアドレス)" },
  })
  console.log(result)
};

queryBalance().catch(console.error);

NTRNの残高を参照していた際とは変わり、SigningCosmWasmClientという、CosmWasm用のクライアントを利用していることがわかります。 queryContractSmartという関数を用いてクエリを実行し、CW20トークンの残高を参照していますが、この関数を用いることによって、CW20トークンのコントラクト以外でも、正しいコントラクトアドレスを指定し、それらで用意されているクエリのパラメータを適切に叩くことによって、コントラクト上の情報を参照することができるようになっています。

以下のようにコマンドを実行することで、所有しているCW20トークンの残高を参照できます。

npx ts-node getCw20Balance.ts

{ balance: '995' }

CW20トークンの送金

CW20トークンを持っていることが確認できたら、そららの送金もCosmJSから実行できるようにしましょう。

transferCw20.tsファイルを作成し、以下のコードを用意します。

getSignerFromMnemonicFile関数の引数には、CW20トークンを保有しているアカウントのmnemonicファイルへのパスを指定してあげる必要があります。

また、送金するCW20トークンのコントラクトアドレス、トークンを受け取るアドレスを適宜入力してあげてください。

import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { DirectSecp256k1HdWallet, OfflineDirectSigner } from "@cosmjs/proto-signing"
import { toUtf8 } from "@cosmjs/encoding";

import { readFile } from "fs/promises"

const rpc = "https://rpc-t.neutron.nodestake.top:443"

const cw20ContractAddress = "(CW20トークンのコントラクトアドレス)";

const getSignerFromMnemonicFile = async (filePath: string): Promise<OfflineDirectSigner> => {
  const mnemonic = (await readFile(filePath)).toString().trim();
  return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
    prefix: "neutron",
  });
};

const sendCw20Token = async () => {
  const aliceSigner: OfflineDirectSigner = await getSignerFromMnemonicFile("./testnet.neutron1.mnemonic.key")
  const client = await SigningCosmWasmClient.connectWithSigner(rpc, aliceSigner);
  const alice = (await aliceSigner.getAccounts())[0].address

  // CW20送金トランザクション用のメッセージを作成
  const sendMsg = {
    transfer: {
      recipient: "(トークンを受け取るアドレス)",
      amount: "5",
    },
  };
  const msgExecuteContract = {
    typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract",
    value: {
      sender: alice,
      contract: cw20ContractAddress,
      msg: toUtf8(JSON.stringify(sendMsg)),
      funds: [],
    },
  };

  const fee = {
    amount: [{ denom: "untrn", amount: "5000" }],
    gas: "200000", // ガスリミット
  };
  const result = await client.signAndBroadcast(alice, [msgExecuteContract], fee);
  console.log("Transaction result:", result);
};

sendCw20Token().catch(console.error);

NTRNトークンの送金時と同様に、トランザクションに署名を行うアカウントを利用したクライアントを用意し、コントラクトに対して送金するメッセージの作成、それらメッセージのエンコードを行なっています。

signAndBroadcast関数で、トランザクションの作成と署名、またそのブロードキャストを行なっています。

コントラクトごとにどういったメッセージを受け付けているのかは、それぞれのCosmWasmのコントラクト自体がどういう実装になっているのかを参照する必要があります。

コマンドを実行し、正しく処理が実行されると、以下のようにログが表示されます。

npx ts-node transferCw20.ts


Transaction result: {
  code: 0,
  height: 8209684,
  txIndex: 0,
  rawLog: '[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action","value":"/cosmwasm.wasm.v1.MsgExecuteContract"},{"key":"sender","value":"neutron18xym73lshswvhvfkmx0ykn4pfnu2ee0cc8adgn"},{"key":"module","value":"wasm"}]},{"type":"execute","attributes":[{"key":"_contract_address","value":"neutron1u4mnv94kqahd4pl8khv423jdte7etrladjcw42zdydpg52v6n47sl2k4zp"}]},{"type":"wasm","attributes":[{"key":"_contract_address","value":"neutron1u4mnv94kqahd4pl8khv423jdte7etrladjcw42zdydpg52v6n47sl2k4zp"},{"key":"action","value":"transfer"},{"key":"from","value":"neutron18xym73lshswvhvfkmx0ykn4pfnu2ee0cc8adgn"},{"key":"to","value":"neutron16p4tq6p96qvcm4cwsa6yttcp94ynm0n84fqflt"},{"key":"amount","value":"5"}]}]}]',
  transactionHash: '5B7AE9D570E8B2D8E97BF73DE658B17B1CF2608A3EA7F043D7A34AA788C4B57B',
  events: [
    { type: 'coin_spent', attributes: [Array] },
    { type: 'coin_received', attributes: [Array] },
    { type: 'transfer', attributes: [Array] },
    { type: 'message', attributes: [Array] },
    { type: 'tx', attributes: [Array] },
    { type: 'tx', attributes: [Array] },
    { type: 'tx', attributes: [Array] },
    { type: 'message', attributes: [Array] },
    { type: 'execute', attributes: [Array] },
    { type: 'wasm', attributes: [Array] }
  ],
  msgResponses: [
    {
      typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContractResponse',
      value: Uint8Array(0) []
    }
  ],
  gasUsed: 133012n,
  gasWanted: 200000n
}

これにてCosmWasm上のCW20トークンの残高確認と送金がCosmJSからできるようになりました。

送金後にそれぞれのアカウントのCW20トークンの残高が更新されているか確認してみてください。

おわりに

以上がCosmJSを用いてのネイティブトークンとCW20トークンの残高参照と送金を実行する方法になります。

次に進むものとしては、Neutron以外のチェーンに繋いでみたり、IBCを利用したチェーン間送金を試してみるのも面白いかもしれません。

これらCosmJSの利用方法をさらに知っていくことで、Cosmos SDKやCosmWasmを用いたDeFiやDappsなどのアプリケーションの構築や、自動でDEXで売買するためのBot作成などもできるようになるはずです。

一緒にCosmosの世界を切り拓いて行きましょう!

運営者情報

Stir lab運営元のSTIR (スター)は、ETHERSECURITY PACIFIC HOLDINGS PTE. LTD.(本社:シンガポール、代表取締役:加門昭平)及びその100%子会社である株式会社イーサセキュリティ(本社:東京都渋谷区、代表取締役:加門昭平、紫竹佑騎)が運営するWeb3 Consulting & Development Teamです。

 

X (Twitter)@Stir_Network_JP

LinkedInhttps://www.linkedin.com/company/14613801

運営元https://stir.network/

Twitterでフォローしよう

おすすめの記事