# Your First CosmJS Actions - Send Tokens

synopsis

Take your first steps with CosmJS. Use it to send some simple transactions.

In this section:

  • Download and install CosmJS
  • Create a small experiment
  • Prepare a simple testnet
  • Establish your connection
  • Inspect a balance
  • Send a transactions

Now that you know what CosmJS is, you should take your first steps in using it. A basic feature of a Cosmos chain is the ability to send tokens via the bank module. CosmJS naturally offers functions to cover this facility. You are going to:

  1. Use an existing test network (testnet) with a key of your own.
  2. Run basic CosmJS commands in a script that you run using the CLI.

Additionally, you can choose to:

  1. Start a local chain that exposes RPCs instead of using a testnet.
  2. Run the same basic CosmJS commands, but for this local chain.

Along the way, you will learn the basic CosmJS concepts needed to start interacting with the Cosmos ecosystem.

# Script preparation

A small, ready-made repository exists so you can experiment with CosmJS. Clone it from here (opens new window), and you will need NodeJs (opens new window). If you open the folder in Visual Studio Code (opens new window), the IDE should give you all the coding help you require. In the cloned folder you need to install the required modules:

Copy $ npm install

Create a new file named experiment.ts. In it, put these lines to confirm it works:

Copy const runAll = async(): Promise<void> => { console.log("TODO") } runAll() experiment.ts View source

To execute, this Typescript file needs to be compiled into Javascript before being interpreted by NodeJs. Add this as a run target in package.json:

Copy ... "scripts": { ... "experiment": "ts-node experiment.ts" } ... package.json View source

Confirm that it does what you want:

Copy $ npm run experiment

This returns:

Copy > cosmjs-sandbox@1.0.0 experiment > ts-node experiment.ts TODO

You will soon make this script more meaningful. With the basic script ready, you need to prepare some elements.

# Testnet preparation

The Cosmos ecosystem has a number of testnets running. The Cosmos Hub is currently running a public testnet (opens new window) for the Theta upgrade that you'll be connecting to and running your script on. You'll need to connect to a public node so that you can query information and broadcast transactions. One of the available nodes is:

Copy RPC: https://rpc.sentry-01.theta-testnet.polypore.xyz v7-theta View source

You'll need a wallet address on the testnet and you must create a 24-word mnemonic in order to do so. CosmJS can generate one for you. Create a new file generate_mnemonic.ts with the following script:

Copy import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing" const generateKey = async (): Promise<void> => { const wallet: DirectSecp256k1HdWallet = await DirectSecp256k1HdWallet.generate(24) process.stdout.write(wallet.mnemonic) const accounts = await wallet.getAccounts() console.error("Mnemonic with 1st account:", accounts[0].address) } generateKey() generate_mnemonic.ts View source

Now create a key for our imaginary user Alice:

Copy $ npx ts-node generate_mnemonic.ts > testnet.alice.mnemonic.key

When done, it should also tell you the address of the first account:

Copy Mnemonic with 1st account: cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf

Temporarily keep this address for convenience, although CosmJS can always recalculate it from the mnemonic. Privately examine the file to confirm it contains your 24 words.

  1. process.stdout.write was used to avoid any line return. Be careful not to add any empty lines or any other character in your .key file (this occurs with VSCode under certain conditions). If you add any characters, ComsJs may not be able to parse it.

  2. Adjust the .gitignore file to not commit your .key file by mistake:

    Copy node_modules *.key .gitignore View source

For your convenience, we have a branch available here (opens new window) that contains all the code and files you've added so far.

# Add your imports

You need a small, simple interface to a blockchain, one which could eventually have users. Good practice is to refrain from requesting a user address until necessary (e.g. when a user clicks a relevant button). Therefore, in experiment.ts you will first use the read-only client. Import it at the top of the file:

Copy import { StargateClient } from "@cosmjs/stargate" experiment.ts View source

Note that VSCode assists you to auto-complete StargateClient (opens new window) if you type CTRL-Space inside the {} of the import line.

# Define your connection

Next, you need to tell the client how to connect to the RPC port of your blockchain:

Copy const rpc = "https://rpc.sentry-01.theta-testnet.polypore.xyz" experiment.ts View source

Inside the runAll function you can initialize the connection (opens new window) and immediately check (opens new window) you connected to the right place:

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

Run again to check with npm run experiment, and you get:

Copy With client, chain id: theta-testnet-001 , height: 9507032

# Get a balance

Normally you would not yet have access to your user's address. However, for this exercise you need to know how many tokens Alice has, so add a temporary new command inside runAll:

Copy console.log( "Alice balances:", await client.getAllBalances("cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf"), ) experiment.ts View source

getAllBalances is used because the default token name is not yet known. When you run it again, you get:

Copy Alice balances: []

If you just created this account, Alice's balance is zero. Alice will need tokens to be able to send transactions and participate in the network. A common practice with testnets is to expose faucets (services that send you test tokens for free, within limits).

The Cosmos Hub Testnet faucet has a dedicated Discord channel (opens new window) where you can ask for tokens once per day per Discord user.

Go to the faucet channel and request tokens for Alice by entering this command in the channel:

Copy $request [Alice's address] theta // For example: $request cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf theta

The faucet bot replies with a link to the transaction from the block explorer:

Copy ✅ https://explorer.theta-testnet.polypore.xyz/transactions/540484BDD342702F196F84C2FD42D63FA77F74B26A8D7383FAA5AB46E4114A9B

Check that Alice received the tokens with npm run experiment, which should return:

Copy Alice balances: [ { denom: 'uatom', amount: '10000000' } ]

uatom is the indivisible token unit on the Testnet. It is short for micro-ATOM, or µ-ATOM. So 10 million uatom equal 10 ATOM. After this confirmation you can comment out the balance query.

# Get the faucet address

As an exercise you want Alice to send some tokens back to the faucet, so you will need its address. You can request this from the faucet bot, but it is also possible to get it using the transaction hash in experiment.ts.

First you need to get the transaction.

Add the necessary import at the top:

Copy import { IndexedTx, StargateClient } from "@cosmjs/stargate" experiment.ts View source

Then, make sure you replace the hash with the one you received from the faucet bot.

Copy const faucetTx: IndexedTx = (await client.getTx( "540484BDD342702F196F84C2FD42D63FA77F74B26A8D7383FAA5AB46E4114A9B", ))! experiment.ts View source

# Deserialize the transaction

What does faucetTx contain? Add the following line to find out:

Copy console.log("Faucet Tx:", faucetTx) experiment.ts View source

Then run the script again using npm run experiment. You'll find the the following output which in your case will contain different values:

Copy Faucet Tx: { height: 9487785, hash: '540484BDD342702F196F84C2FD42D63FA77F74B26A8D7383FAA5AB46E4114A9B', code: 0, rawLog: '[{"events":[{"type":"coin_received","attributes":[{"key":"receiver","value":"cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf"},{"key":"amount","value":"10000000uatom"}]},{"type":"coin_spent","attributes":[{"key":"spender","value":"cosmos15aptdqmm7ddgtcrjvc5hs988rlrkze40l4q0he"},{"key":"amount","value":"10000000uatom"}]},{"type":"message","attributes":[{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},{"key":"sender","value":"cosmos15aptdqmm7ddgtcrjvc5hs988rlrkze40l4q0he"},{"key":"module","value":"bank"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf"},{"key":"sender","value":"cosmos15aptdqmm7ddgtcrjvc5hs988rlrkze40l4q0he"},{"key":"amount","value":"10000000uatom"}]}]}]', tx: Uint8Array(321) [ 10, 148, 1, 10, 145, 1, 10, 28, 47, 99, 111, 115, 109, 111, 115, 46, 98, 97, 110, 107, 46, 118, 49, 98, 101, 116, 97, 49, 46, 77, 115, 103, 83, 101, 110, 100, 18, 113, 10, 45, 99, 111, 115, 109, 111, 115, 49, 53, 97, 112, 116, 100, 113, 109, 109, 55, 100, 100, 103, 116, 99, 114, 106, 118, 99, 53, 104, 115, 57, 56, 56, 114, 108, 114, 107, 122, 101, 52, 48, 108, 52, 113, 48, 104, 101, 18, 45, 99, 111, 115, 109, 111, 115, 49, 55, 116, 118, 100, 52, 104, ... 221 more items ], gasUsed: 76657, gasWanted: 200000 }

As you can see, the structure of this output is JSON. You see that there is a serialized faucetTx.tx of the type Uint8Array. The serialized transaction are the bytes (i.e. Uint8) of the actual transaction that was sent over the testnet by the faucet. It is unintelligible to humans until you deserialize it properly. Since it is a serialized transaction, use the methods offered by cosmjs-types Tx (opens new window) to deserialize it.

Add the necessary import at the top:

Copy import { Tx } from "cosmjs-types/cosmos/tx/v1beta1/tx" experiment.ts View source

Then deserialize the transaction:

Copy const decodedTx: Tx = Tx.decode(faucetTx.tx) console.log("DecodedTx:", decodedTx) experiment.ts View source

Which, on your next npm run experiment, prints:

Copy DecodedTx: { signatures: [ Uint8Array(64) [ 106, 244, 26, 232, 175, 96, 235, 168, 96, 55, 157, 222, 49, 142, 64, 207, 67, 109, 40, 45, 153, 232, 112, 134, 251, 97, 72, 162, 169, 62, 245, 134, 59, 241, 75, 31, 146, 11, 176, 159, 185, 41, 100, 171, 175, 78, 120, 186, 24, 136, 103, 160, 205, 64, 180, 131, 9, 137, 178, 221, 68, 28, 122, 169 ] ], body: { memo: '', timeoutHeight: Long { low: 0, high: 0, unsigned: true }, messages: [ [Object] ], extensionOptions: [], nonCriticalExtensionOptions: [] }, authInfo: { signerInfos: [ [Object] ], fee: { gasLimit: [Long], payer: '', granter: '', amount: [Array] } } }

The faucet address information you are looking for is inside the body.messages. So print that too, add:

Copy console.log("Decoded messages:", decodedTx.body!.messages) experiment.ts View source

Which, on your next npm run experiment, prints:

Copy Decoded messages: [ { typeUrl: '/cosmos.bank.v1beta1.MsgSend', value: Uint8Array(113) [ 10, 45, 99, 111, 115, 109, 111, 115, 49, 53, 97, 112, 116, 100, 113, 109, 109, 55, 100, 100, 103, 116, 99, 114, 106, 118, 99, 53, 104, 115, 57, 56, 56, 114, 108, 114, 107, 122, 101, 52, 48, 108, 52, 113, 48, 104, 101, 18, 45, 99, 111, 115, 109, 111, 115, 49, 55, 116, 118, 100, 52, 104, 99, 115, 122, 113, 55, 108, 99, 120, 117, 119, 122, 114, 113, 107, 101, 112, 117, 97, 117, 57, 102, 121, 101, 51, 100, 97, 108, 54, 48, 54, 122, 102, 26, 17, 10, 5, 117, 97, ... 13 more items ] } ]

Deserializing the transaction has not fully deserialized the message(s) that it contains, nor the message's value, which is again a Uint8Array. The transaction deserializer knows how to properly decode any transaction, but it does not know how to do the same for messages. Messages can in fact be of any type, and each type has its own deserializer. This is not something that the Tx.decode transaction deserializer function knows.

# What is this long string?

Note the typeUrl: "/cosmos.bank.v1beta1.MsgSend" string. This comes from the Protobuf definitions and is a mixture of:

  1. The package where MsgSend is initially declared:

    Copy package cosmos.bank.v1beta1; proto cosmos ... v1beta1 tx.proto View source
  2. And the name of the message itself, MsgSend:

    Copy message MsgSend { ... } proto cosmos ... v1beta1 tx.proto View source

This typeUrl string you see in the decoded message is the canonical identifier of the type of message that's serialized under the value array. There are many different message types, each coming from different modules or base layers from the Cosmos SDK, and you can find an overview of them here (opens new window).

The blockchain client itself knows how to serialize or deserialize it only because this "/cosmos.bank.v1beta1.MsgSend" string is passed along. With this typeUrl, the blockchain client and CosmJS are able to pick the right deserializer. This object is also named MsgSend in cosmjs-types. But in this tutorial, you have picked the deserializer manually.

To learn how to make your own types for your own blockchain project, head to Create my own CosmJS objects.

# Deserialize the message

Now that you know the only message in the transaction is a MsgSend, you need to deserialize it. First add the necessary import at the top:

Copy import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx" experiment.ts View source

Then you can deserialize the message. Add:

Copy const sendMessage: MsgSend = MsgSend.decode(decodedTx.body!.messages[0].value) console.log("Sent message:", sendMessage) experiment.ts View source

Which, on your next npm run experiment, prints:

Copy Sent message: { fromAddress: 'cosmos15aptdqmm7ddgtcrjvc5hs988rlrkze40l4q0he', toAddress: 'cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf', amount: [ { denom: 'uatom', amount: '10000000' } ] }

In this message, the fromAddress is that of the faucet:

Copy const faucet: string = sendMessage.fromAddress experiment.ts View source

Similar to how you got the balance for Alice, you can get the faucet's balance as well. Have a go at trying this yourself by copying (opens new window) the code to print Alice's balances. When running, you should get:

Copy Faucet balances: [ { denom: 'uatom', amount: '867777337235' } ]

Instead of using the decode functions that come with the Tx and MsgSend imports, you can process the data yourself via alternative means. If you'd like to experiment more, you can parse the rawLog manually as opposed to deserializing the transaction as suggested above.

Note the conceptual difference between Tx and the rawLog. The Tx, or MsgSend, object is an input to the computation that takes place when the transaction is included in a block. The rawLog is the resulting output of said computation and its content depends on what the blockchain code emitted when executing the transaction.

From the IndexedTx you see that there is a rawLog (opens new window), which happens to be a stringified JSON.

Copy const rawLog = JSON.parse(faucetTx.rawLog) console.log("Raw log:", JSON.stringify(rawLog, null, 4)) experiment.ts View source

The structure of the raw log is not always obvious, but in this example it contains:

Copy [ { "events": [ ... { "type": "coin_spent", "attributes": [ { "key": "spender", "value": "cosmos15aptdqmm7ddgtcrjvc5hs988rlrkze40l4q0he" }, ... ] ... } ... ] } ]

Because this is a JSON file, you're able to fetch the faucet's address as follows:

Copy const faucet: string = rawLog[0].events .find((eventEl: any) => eventEl.type === "coin_spent") .attributes.find((attribute: any) => attribute.key === "spender").value experiment.ts View source

Although the above is a perfectly valid way to extract specific values from a transaction's message, it is not the recommended way to do so. The benefit of importing the message types is that you don't have to manually dig through the raw log of each Tx you're looking to use in your application.

These actions are example uses of the read-only StargateClient and of the serialization tools that come with CosmJS.

You can get the result of the above steps here (opens new window).

Now it is time for Alice to send some tokens back to the faucet.

# Prepare a signing client

If you go through the methods inside StargateClient (opens new window), you see that it only contains query-type methods and none for sending transactions.

Now, for Alice to send transactions, she needs to be able to sign them. And to be able to sign transactions, she needs access to her private keys or mnemonics. Or rather she needs a client that has access to those. That is where SigningStargateClient (opens new window) comes in. Conveniently, SigningStargateClient inherits from StargateClient.

Update your import line:

Copy import { IndexedTx, SigningStargateClient, StargateClient } from "@cosmjs/stargate" experiment.ts View source

Let's have a look at its declaration by right-clicking on the SigningStargateClient in your imports and choosing Go to Definition.

As you can see, when you instantiate SigningStargateClient by using the connectWithSigner (opens new window) method, you need to pass it a signer (opens new window). In this case, we'll be using the OfflineDirectSigner (opens new window) interface.

The recommended way to encode messages is by using OfflineDirectSigner, which uses Protobuf. However, hardware wallets such as Ledger do not support this and still require the legacy Amino encoder. If your app requires Amino support, you'll have to use the OfflineAminoSigner.

You can read more about encoding here (opens new window).

The signer will need access to Alice's private key, and there are several ways to accomplish this. In this example, you will use Alice's saved mnemonic. To load the mnemonic as text in your code you will need this import:

Copy import { readFile } from "fs/promises" experiment.ts View source

There are several implementations of OfflineDirectSigner available. The DirectSecp256k1HdWallet (opens new window) implementation is most relevant to us due to its fromMnemonic (opens new window) method. Add the import:

Copy import { DirectSecp256k1HdWallet, OfflineDirectSigner } from "@cosmjs/proto-signing" experiment.ts View source

The fromMnemonic factory function needs a string with the mnemonic. You read this string from the mnemonic file. Create a new top-level function that returns an OfflineDirectSigner:

Copy const getAliceSignerFromMnemonic = async (): Promise<OfflineDirectSigner> => { return DirectSecp256k1HdWallet.fromMnemonic((await readFile("./testnet.alice.mnemonic.key")).toString(), { prefix: "cosmos", }) } experiment.ts View source

The Cosmos Hub Testnet uses the cosmos address prefix. This is the default used by DirectSecp256k1HdWallet, but you are encouraged to explicitly define it as you might be working with different prefixes on different blockchains. In your runAll function, you can now add:

Copy const aliceSigner: OfflineDirectSigner = await getAliceSignerFromMnemonic() experiment.ts View source

As a first step, you can confirm that it recovers Alice's address as expected:

Copy const alice = (await aliceSigner.getAccounts())[0].address console.log("Alice's address from signer", alice) experiment.ts View source

Now add the line that finally creates the signing client:

Copy const signingClient = await SigningStargateClient.connectWithSigner(rpc, aliceSigner) experiment.ts View source

Check that it works like the read-only client that you used earlier, and from which it inherits (opens new window), by adding:

Copy console.log( "With signing client, chain id:", await signingClient.getChainId(), ", height:", await signingClient.getHeight() ) experiment.ts View source

Run it with npm run experiment.

You can get the result of the above steps here (opens new window).

# Send tokens

Alice can now send some tokens back to the faucet, but to do so she will also need to pay the network's gas fee. How much gas should she use, and at what price?

She can copy what the faucet did. To discover this, add:

Copy console.log("Gas fee:", decodedTx.authInfo!.fee!.amount) console.log("Gas limit:", decodedTx.authInfo!.fee!.gasLimit.toString(10)) experiment.ts View source

When you run it, it prints:

Copy Gas fee: [ { denom: 'uatom', amount: '500' } ] Gas limit: 200000

With the gas information now decided, how does Alice structure her command so that she sends 1% of her holdings, i.e. 100000uatom, back to the faucet? SigningStargateClient's sendTokens (opens new window) function takes a Coin[] as input. Coin is simply defined as:

Copy export interface Coin { denom: string; amount: string; } src cosmos ... v1beta1 coin.ts View source

Alice can pick any denom and any amount as long as she owns them, the signing client will sign the transaction and broadcast it. In this case it is:

Copy { denom: "uatom", amount: "100000" }

With this gas and coin information, add the command:

Copy // Check the balance of Alice and the Faucet console.log("Alice balance before:", await client.getAllBalances(alice)) console.log("Faucet balance before:", await client.getAllBalances(faucet)) // Execute the sendTokens Tx and store the result const result = await signingClient.sendTokens( alice, faucet, [{ denom: "uatom", amount: "100000" }], { amount: [{ denom: "uatom", amount: "500" }], gas: "200000", }, ) // Output the result of the Tx console.log("Transfer result:", result) experiment.ts View source

To confirm that it worked, add another balance check:

Copy console.log("Alice balance after:", await client.getAllBalances(alice)) console.log("Faucet balance after:", await client.getAllBalances(faucet)) experiment.ts View source

Run this with npm run experiment and you should get:

Copy ... Transfer result: { code: 0, height: 9507151, rawLog: '[{"events":[{"type":"coin_received","attributes":[{"key":"receiver","value":"cosmos15aptdqmm7ddgtcrjvc5hs988rlrkze40l4q0he"},{"key":"amount","value":"100000uatom"}]},{"type":"coin_spent","attributes":[{"key":"spender","value":"cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf"},{"key":"amount","value":"100000uatom"}]},{"type":"message","attributes":[{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},{"key":"sender","value":"cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf"},{"key":"module","value":"bank"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"cosmos15aptdqmm7ddgtcrjvc5hs988rlrkze40l4q0he"},{"key":"sender","value":"cosmos17tvd4hcszq7lcxuwzrqkepuau9fye3dal606zf"},{"key":"amount","value":"100000uatom"}]}]}]', transactionHash: '7F770F24CB3C805FE45A8D26DD5EC5AA3F7B906AA7D6CB1F3FE8B554CBA93E12', gasUsed: 74190, gasWanted: 200000 } Alice balance after: [ { denom: 'uatom', amount: '9899500' } ] Faucet balance after: [ { denom: 'uatom', amount: '867777437235' } ]

According to the rawLog, the faucet received 100000uatom. Since Alice ends up with "9899500uatom", it means she also paid 500uatom for gas.

This concludes your first use of CosmJS to send tokens. You can find the result of all the steps above here (opens new window).

Note that you connected to a publicly running testnet. Therefore, you depended on someone else to have a blockchain running with an open and publicly available RPC port and faucet. What if you wanted to try connecting to your own locally running blockchain?

# With a locally started chain

The easiest option is to reuse the simd chain that you started in a previous module. Make sure that you have created two accounts, Alice and Bob. You also sent tokens using simd. Be sure to credit enough tokens to Alice.

When you finally launch simd, you see the line:

Copy $ ./build/simd start ... 4:37PM INF Starting RPC HTTP server on 127.0.0.1:26657 module=rpc-server ...

Port 26657 is the default port for RPC endpoints built with the SDK, unless otherwise configured in ~/.simapp/config/config.toml. 127.0.0.1:26657 will be the URL you'll need to add to your script later.

You can make a copy of your experiment.ts script, with some adjustments. Name it experiment-local.ts. Add a new run target in package.json:

Copy { ... "scripts": { ... "experiment-local": "ts-node experiment-local.ts", ... } } package.json View source

# Preparing your keys

Although you have Alice's address, you may not have her mnemonic or private key. The private key is stored in your operating system's keyring backend. For the purpose of this exercise you can extract it, but generally this is an unsafe operation:

Copy $ ./build/simd keys export alice --unsafe --unarmored-hex

You get a 64-digit-long hex value. Copy-paste it into a new simd.alice.private.key file in your cosmjs-sandbox folder. The .gitignore was already configured earlier to ignore it, which mitigates the risk.

If you cannot remember which alias you gave your keys, list them:

Copy $ ./build/simd keys list - address: cosmos1c3srguwnzah5nd4cn49shltvr6tsrcl2jwn8je name: alice pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AhR7SWWDsaSxBD9r/mIhbVOWap70jA3WpBIqjOJo4Dwp"}' type: local - address: cosmos1umpxwaezmad426nt7dx3xzv5u0u7wjc0kj7ple name: bob pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Av1VW23/laXWtbwWwOUHCvjjeLLqbdzazRneeRsE/shL"}' type: local

# Update your script

With the new elements in place, you can update your experiment-local.ts script. Change rpc:

Copy const rpc = "http://127.0.0.1:26657" experiment-local.ts View source

And skip the lengthy process to get the faucet address. Just set faucet to Bob's address:

Copy const faucet: string = "cosmos1umpxwaezmad426nt7dx3xzv5u0u7wjc0kj7ple" experiment-local.ts View source

Next, you'll need to replace the function to create Alice's signer because you're using a private key instead of a mnemonic, so the fromMnemonic method that comes with DirectSecp256k1HdWallet won't work. The fromKey (opens new window) method that comes with DirectSecp256k1Wallet is the more appropriate choice this time.

Adjust the import:

Copy import { DirectSecp256k1Wallet, OfflineDirectSigner } from "@cosmjs/proto-signing" experiment-local.ts View source

In DirectSecp256k1Wallet the fromKey factory function needs a Uint8Array. Fortunately, CosmJS includes a utility to convert a hexadecimal string into a Uint8Array. Import it:

Copy import { fromHex } from "@cosmjs/encoding" experiment-local.ts View source

Now create a new function to get a signer to replace the previous one:

Copy const getAliceSignerFromPriKey = async(): Promise<OfflineDirectSigner> => { return DirectSecp256k1Wallet.fromKey( fromHex((await readFile("./simd.alice.private.key")).toString()), "cosmos", ) } experiment-local.ts View source

Replace getAliceSignerFromMnemonic with the newly created getAliceSignerFromPriKey:

Copy const aliceSigner: OfflineDirectSigner = await getAliceSignerFromPriKey() experiment-local.ts View source

Also change the token unit from uatom to stake (opens new window) in your sendTokens transaction, because this is the default token when using simapp. Experiment with adjusting the values as desired. Run it with:

Copy $ npm run experiment-local

And confirm the output is as expected. For instance something like:

Copy > cosmjs-sandbox@1.0.0 experiment-local > ts-node experiment-local.ts With client, chain id: demo , height: 44883 Alice balances: [] Alice's address from signer cosmos1c3srguwnzah5nd4cn49shltvr6tsrcl2jwn8je With signing client, chain id: demo , height: 44883 Alice balance before: [ { denom: 'stake', amount: '19891835' } ] Faucet balance before: [ { denom: 'stake', amount: '10010000' } ] Transfer result: { code: 0, height: 44885, rawLog: '[{"events":[{"type":"coin_received","attributes":[{"key":"receiver","value":"cosmos1umpxwaezmad426nt7dx3xzv5u0u7wjc0kj7ple"},{"key":"amount","value":"100000stake"}]},{"type":"coin_spent","attributes":[{"key":"spender","value":"cosmos1c3srguwnzah5nd4cn49shltvr6tsrcl2jwn8je"},{"key":"amount","value":"100000stake"}]},{"type":"message","attributes":[{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},{"key":"sender","value":"cosmos1c3srguwnzah5nd4cn49shltvr6tsrcl2jwn8je"},{"key":"module","value":"bank"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"cosmos1umpxwaezmad426nt7dx3xzv5u0u7wjc0kj7ple"},{"key":"sender","value":"cosmos1c3srguwnzah5nd4cn49shltvr6tsrcl2jwn8je"},{"key":"amount","value":"100000stake"}]}]}]', transactionHash: 'A49EBD41E37CDACF258F0BCD0954C52138FB5121C9A3B58138A2279EDB526B6D', gasUsed: 72702, gasWanted: 200000 } Alice balance after: [ { denom: 'stake', amount: '19791335' } ] Faucet balance after: [ { denom: 'stake', amount: '10110000' } ]

You have now used CosmJS's bank module on a locally running Cosmos blockchain.

You can find the complete set of files here (opens new window).

# Next up

You have sent a transaction with a single message. How about you send a transaction with more than one message? That is the object of the next section. Or skip ahead and send a simple transaction, but this time from a Web browser with the help of Keplr.