Using CosmJS with WASM Contracts

The tutorial provides example on how to use the CosmJS package to instantiate, execute and query CosmWASM Smart Contracts deployed on the Coreum blockchain.

Installing dependencies

Install the CosmJS dependency.

npm install @cosmjs/stargate @cosmjs/cosmwasm-stargate

Preparing test account

Before you may broadcast transactions, you need to have access to a funded account. Normally you would create a private key stored securely in a wallet. Here, for simplicity, we will use mnemonic generated by our faucet. Don't use the mnemonic directly in code and never ever use the key generated by the faucet in mainnet. It might cause complete funds loss!

To get a funded account, go to our faucet website, that you can find here and click on "Generate Funded Wallet" button in "Testnet" section. Assign mnemonic to the constant senderMnemonic in the code snippet below.

Generate TS based on Smart Contract messages

For convenience, there are CosmWasm macros that There are CosmWasm macros that allow you to generate the JSON schemas of your Smart Contract, which can be used by code generation tools to generate TypeScript SDKs for your Smart Contracts. Follow these steps:

npm install -g @cosmwasm/ts-codegen
  • Inside the src folder of your Smart Contract, create a bin folder with the following code:
use cosmwasm_schema::write_api;

use your_contract_name::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

fn main() {
    write_api! {
        instantiate: InstantiateMsg,
        execute: ExecuteMsg,
        query: QueryMsg,
    }
}
  • Generate the schema running the following command inside your contract's folder:
cargo run --bin schema

This will generate another folder called schema with your JSON files inside.

  • Run the cosmwasm-ts-codegen command to generate your code:
cosmwasm-ts-codegen generate \
          --plugin client \
          --schema ./schema \
          --out ./ts \
          --name MyProject \
          --no-bundle

This will generate two TypeScript files. One with all the message types that your contract supports and one client file that you will initialize with your SigningClient and your contract address and that contains all the functions that will interact with your Smart Contract, for convenience.

Preparing coreum settings

Before we are able to broadcast transaction, we must set up chain specific configuration:

const coreumAccountPrefix = "testcore"; // the address prefix (different for different chains/environments)
const coreumHDPath = "m/44'/990'/0'/0/0"; // coreum HD path (same for all chains/environments)
const coreumDenom = "utestcore"; // core denom (different for different chains/environments)
const coreumRpcEndpoint = "https://full-node.testnet-1.coreum.dev:26657"; // rpc endpoint (different for different chains/environments)
const senderMnemonic = "putYourMnemonicHere"; // put mnemonic here

This configuration is for testnet. Parameters of other networks are available at network variables.

Prepare RPC/tendermint clients.

The clients will be reused by multiple samples later.

const httpClient = new HttpBatchClient(coreumRpcEndpoint);
const tendermintClient = await Tendermint34Client.create(httpClient);
const queryClient = QueryClient.withExtensions(tendermintClient, setupWasmExtension);
const rpcClient = createProtobufRpcClient(queryClient);
const feemodelQueryClient = new FeemodelQueryClient(rpcClient)

GasPrice

To obtain the correct GasPrice we can use this function.

export async function getGasPriceWithMultiplier(feemodelQueryClient: FeemodelQueryClient) {
    const gasPriceMultiplier = 1.1
    const recommendedGasPriceRes = await feemodelQueryClient.RecommendedGasPrice({ AfterBlocks: 10 })
    const recommendedGasPrice = decodeCosmosSdkDecFromProto(recommendedGasPriceRes.low?.amount || "")
    let gasPrice = recommendedGasPrice.toFloatApproximation() * gasPriceMultiplier
    return GasPrice.fromString(`${gasPrice}${recommendedGasPriceRes.low?.denom || ""}`);
}

Prepare sender client

To sign transactions, you need to set up the new account-specific CosmWsam client.

console.log("preparing sender wallet");
const senderWallet = await DirectSecp256k1HdWallet.fromMnemonic(senderMnemonic, {
    prefix: coreumAccountPrefix,
    hdPaths: [stringToPath(coreumHDPath)],
});
const [sender] = await senderWallet.getAccounts();
console.log(`sender address: ${sender.address}`);

const senderClient = await SigningCosmWasmClient.connectWithSigner(
    coreumRpcEndpoint,
    senderWallet,
    { gasPrice: getGasPriceWithMultiplier(feemodelQueryClient) }
);

This senderClient can be provided to the client that we generated before using ts-codegen along with the contract address to provide another layer of abstraction to ease or Smart Contract interactions. If we didn't generate the code with ts-codegen then we can create the JSON messages manually.

Instantiating a Smart Contract

  • Using generated types:
import { InstantiateMsg } from "./MyProject.types";

const code_id = 547; // Here we put the code id of the contract that we uploaded on the Coreum blockchain.
const instantiateMsg: InstantiateMsg = { field1: "value", field2: "value" };
const instantiateResult = await senderClient.instantiate(sender.address, code_id, instantiateMsg, "yourlabel", "auto", {});
  • Without generated types:
const code_id = 547; // Here we put the code id of the contract that we uploaded on the Coreum blockchain.
const instantiateMsg = { field1: "value", field2: "value" }; // Here we input a correct JSON corresponding to our contract's instantiate message.
const instantiateResult = await senderClient.instantiate(sender.address, code_id, instantiateMsg, "yourlabel", "auto", {});

Execute a Smart Contract

  • Using generated types:
import { MyProjectClient } from "./MyProject.client";
const client = new MyProjectClient(senderClient, sender.address, instantiateResult.contractAddress);
const executeResult = client.messageName({ field1: "value", field2: "value"})
  • Without generated types:
const executeMsg = { message_name : { field1: "value", field2: "value" }} //Example of a possible execute msg
const executeResult = senderClient.execute(sender.address, instantiateResult.contractAddress, executeMsg, "auto", "memo");

Query a Smart Contract

  • Using generated types:
import { MyProjectQueryClient } from "./MyProject.client";

const cwClient = await CosmWasmClient.connect(coreumRpcEndpoint);
const qClient = new MyProjectQueryClient(cwClient, instantiateResult.contractAddress);
const queryResponse = qClient.queryName({ field1: "value", field2: "value"})
  • Without generated types:
const queryMsg = { query_name: { field1: "value", field2: "value"} } //Example of a possible query msg
const queryResponse = queryClient.queryContractSmart(instantiateResult.contractAddress, queryMsg);

Using browser wallet extension APIs

If we want to create a sender client but instead of providing the wallet ourselves we want to use the wallet we are already using in our browser, we can use the APIs of each wallet to create the offline signer that we will provide to the client:

const senderClient = await SigningCosmWasmClient.connectWithSigner(
    coreumRpcEndpoint,
    offlineSigner,
    { gasPrice: getGasPriceWithMultiplier(feemodelQueryClient) }
);

For convenience there are already tools to integrate wallets into our applications to sign the messages we send to our contracts. Cosmos-kit is a wallet connector that offers packages for many wallets (both mobile and Web extensions) that we can use to build applications to interact with our Smart Contracts deployed on the Coreum blockchain.

Next steps

  • Read Coreum modules specification, to be familiar with the custom Coreum functionality you can use for your application.
  • Read WASM docs to understand all supported WASM features.
  • Check other tutorials to find something you might be interested in additionally.