Using CosmJS

This document gives instructions and examples on how to use CosmJS package to broadcast transactions and query the Coreum blockchain using TypeScript.

Source Code

The complete app source code is located hereopen in new window. You can use the README.md instruction to build and run the application.

Installing dependencies

Install the CosmJSopen in new window dependency.

npm install @cosmjs/proto-signing @cosmjs/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: https://docs.coreum.dev/tools-ecosystem/faucet and click on "Generate Funded Wallet" button in "Testnet" section. Assign mnemonic to the constant senderMnemonic in the code snippet below.

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 recipientAddress = "testcore1534s8rz2e36lwycr6gkm9vpfe5yf67wkuca7zs"
const senderMnemonic = "putYourMnemonicHere"; // put mnemonic here

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

Generate TS based on coreum protos

  • Clone coreum repo.
git clone [email protected]:CoreumFoundation/coreum.git
npm install -g protoc

Check version

protoc --version

Output example:

libprotoc 3.21.9
  • Install ts-proto.
npm install ts-proto --save-dev
  • Generate TS proto.
mkdir -p coreum-ts
protoc \
-I "coreum/proto" \
-I "coreum/third_party/proto" \
--plugin=./node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_out="./coreum-ts" \
--ts_proto_opt="esModuleInterop=true,forceLong=long,useOptionals=messages" \
$(find "./coreum/proto" -maxdepth 5 -name '*.proto')
  • Remove coreum repo.
rm -rf coreum

Prepare RPC/tendermint clients.

The clients will be reused by multiple samples later.

const tendermintClient = await Tendermint34Client.connect(coreumRpcEndpoint);
const queryClient = new QueryClient(tendermintClient);
const rpcClient = createProtobufRpcClient(queryClient);
const feemodelQueryClient = new FeemodelQueryClient(rpcClient)
const stakingExtension = setupStakingExtension(queryClient);

// the custom tx types should be registered in the types registry
const ftTypes: ReadonlyArray<[string, GeneratedType]> = [
    ["/coreum.asset.ft.v1.MsgIssue", MsgIssue],
];
let registryTypes: ReadonlyArray<[string, GeneratedType]> = [
    ...defaultRegistryTypes,
    ...ftTypes,
]
const registry = new Registry(registryTypes)

Prepare sender client

To sign transactions, you need to set up the new account-specific 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 SigningStargateClient.connectWithSigner(
    coreumRpcEndpoint,
    senderWallet,
    { registry }
);
const senderCoreBalance = await senderClient.getBalance(sender.address, coreumDenom);
console.log(`sender balance: ${senderCoreBalance.amount}`);

Send coins

Now we are ready to broadcast transaction. As an example we send 100000utestcore tokens from sender wallet to recipient:

 console.log("preparing recipient wallet");
const recipientWallet = await DirectSecp256k1HdWallet.generate(12, {
    prefix: coreumAccountPrefix,
    hdPaths: [stringToPath(coreumHDPath)],
});
const [recipient] = await recipientWallet.getAccounts();
console.log(`recipient address: ${recipient.address}`);

const msgBankSend: MsgSendEncodeObject = {
    typeUrl: "/cosmos.bank.v1beta1.MsgSend",
    value: MsgSend.fromPartial({
        fromAddress: sender.address,
        toAddress: recipient.address,
        amount: [{
            denom: coreumDenom,
            amount: "100000",
        }],
    }),
};
console.log(
    `sending ${msgBankSend.value.amount?.[0].amount}${msgBankSend.value.amount?.[0].denom} from ${msgBankSend.value.fromAddress} to ${msgBankSend.value.toAddress}`
);

let gasPrice = await getGasPriceWithMultiplier(feemodelQueryClient)
const bankSendGas = await senderClient.simulate(sender.address, [msgBankSend], "")
console.log(`estimated gas: ${bankSendGas}, gasPrice: ${gasPrice.toString()}`);
const bankSendFee: StdFee = calculateFee(bankSendGas, gasPrice);
const bankSendResult = await senderClient.signAndBroadcast(
    sender.address,
    [msgBankSend],
    bankSendFee
);
isDeliverTxSuccess(bankSendResult);
console.log(`successfully sent, tx hash: ${bankSendResult.transactionHash}`);

const recipientCoreBalance = await senderClient.getBalance(recipient.address, coreumDenom);
console.log(`recipient balance: ${recipientCoreBalance.amount}`);

After executing this code, you will see output like this:

successfully sent, tx hash: DC77A19C73D463CA5365F115C900CDD435DE6616B49A93573D64628D25699941

Please copy transaction hash and paste it in the search box of our block exploreropen in new window to confirm the transaction execution and check its properties.

GasPrice

In the example above we have already used the correct computation of the gas price.

export async function getGasPriceWithMultiplier(feemodelQueryClient: FeemodelQueryClient) {
    const gasPriceMultiplier = 1.1
    const minGasPriceRes = await feemodelQueryClient.MinGasPrice({})
    const minGasPrice = decodeCosmosSdkDecFromProto(minGasPriceRes.minGasPrice?.amount || "")
    let gasPrice = minGasPrice.toFloatApproximation() * gasPriceMultiplier
    return GasPrice.fromString(`${gasPrice}${minGasPriceRes.minGasPrice?.denom || ""}`);
}

To understand the function read about the coreum gas price.

Staking Delegation

Once the send tx is executed we can use the recipient to Delegate 100utestcore to a first bonded validator.

const recipientClient = await SigningStargateClient.connectWithSigner(
    coreumRpcEndpoint,
    recipientWallet,
    { registry }
);

// query all bonded validators to find first bonded to delegate to
const bondedValidators = await stakingExtension.staking.validators("BOND_STATUS_BONDED");
const validatorOperatorAddress = bondedValidators.validators[0].operatorAddress;
const msgStakingDelegate: MsgDelegateEncodeObject = {
    typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
    value: MsgDelegate.fromPartial({
        delegatorAddress: recipient.address,
        validatorAddress: validatorOperatorAddress,
        amount: {
            denom: coreumDenom,
            amount: "100",
        },
    }),
};
console.log(
    `delegating ${msgStakingDelegate.value.amount?.amount}${msgStakingDelegate.value.amount?.denom} from ${recipient.address} to ${validatorOperatorAddress}`
);
// the gas price can be changed at that time, we need to re-fetch it
gasPrice = await getGasPriceWithMultiplier(feemodelQueryClient)
const stakingDelegateGas = await recipientClient.simulate(recipient.address, [msgStakingDelegate], "")
console.log(`estimated gas: ${stakingDelegateGas}, gasPrice: ${gasPrice.toString()}`);
const stakingDelegateFee: StdFee = calculateFee(stakingDelegateGas, gasPrice);
const stakingDelegateResult = await recipientClient.signAndBroadcast(
    recipient.address,
    [msgStakingDelegate],
    stakingDelegateFee
);
isDeliverTxSuccess(stakingDelegateResult);
console.log(`successfully delegated, tx hash: ${stakingDelegateResult.transactionHash}`);

After executing this code, you will see output like this:

successfully delegated, tx hash: 8B44E82051A2D45C08C7A8A6EB2C444C048AD8BFBE77E98DC4ED6A57666CE88B

Please copy transaction hash and paste it in the search box of our block exploreropen in new window to confirm the transaction execution and check its properties.

Coreum custom message (FT)

In the getGasPriceWithMultiplier we have already used the coreum query to get feemodel params and MinGasPrice. In this section, we interact with the custom-generated transaction. The sample shows how to create and broadcast FT issuance transaction.

const msgIssueFT: MsgIssueEncodeObject = {
    typeUrl: "/coreum.asset.ft.v1.MsgIssue",
    value: MsgIssue.fromPartial({
        issuer: sender.address,
        subunit: `mysubunit`,
        symbol: `mysymbol`,
        precision: 18,
        initialAmount: "1000000",
        features: [Feature.minting, Feature.burning],
        sendCommissionRate: `${Decimal.fromUserInput("0.5", 18).atomics}` // 50%
    }),
};
const ftDenom = `${msgIssueFT.value.subunit}-${sender.address}`
console.log(
    `issuing ${ftDenom} FT`
);

gasPrice = await getGasPriceWithMultiplier(feemodelQueryClient)
const issueFTGas = await senderClient.simulate(sender.address, [msgIssueFT], "")
console.log(`estimated gas: ${issueFTGas}, gasPrice: ${gasPrice.toString()}`);
const issueFTFee: StdFee = calculateFee(issueFTGas, gasPrice);
// pay attention that additionally to the gas the `issue_fee` will be burned.
const issueFTResult = await senderClient.signAndBroadcast(
    sender.address,
    [msgIssueFT],
    issueFTFee
);
isDeliverTxSuccess(issueFTResult);
console.log(`successfully issued, tx hash: ${issueFTResult.transactionHash}`);

const senderFTBalance = await senderClient.getBalance(sender.address, ftDenom);
console.log(`sender ft balance: ${senderFTBalance.amount}${ftDenom}`);

After executing this code, you will see output like this:

successfully issued, tx hash: A1FD3D02BA51FE65EB593442FF9A979A99EC40CA70A2E55FFEF1FAA8A36BAC3F

Please copy transaction hash and paste it in the search box of our block exploreropen in new window to confirm the transaction execution and check its properties.

Docs and additional examples

Additional examples and docs can be found in the CosmJSopen in new window repository.

Complete code

Here is the complete code listing with all the features implemented above:

import { StdFee } from "@cosmjs/amino";
import { stringToPath } from "@cosmjs/crypto";
import { DirectSecp256k1HdWallet, EncodeObject, GeneratedType, Registry } from "@cosmjs/proto-signing";
import { Decimal } from "@cosmjs/math";
import {
    calculateFee,
    createProtobufRpcClient, decodeCosmosSdkDecFromProto,
    GasPrice,
    SigningStargateClient,
} from "@cosmjs/stargate";
import { MsgDelegateEncodeObject, setupStakingExtension } from "@cosmjs/stargate/build/modules";
import { MsgSendEncodeObject } from "@cosmjs/stargate/build/modules";
import { QueryClient } from "@cosmjs/stargate/build/queryclient/queryclient";
import { isDeliverTxSuccess } from "@cosmjs/stargate/build/stargateclient";
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { defaultRegistryTypes } from "@cosmjs/stargate"
import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx";
import { QueryClientImpl as FeemodelQueryClient } from "../coreum-ts/coreum/feemodel/v1/query";
import { MsgIssue } from "../coreum-ts/coreum/asset/ft/v1/tx";
import { Feature } from "../coreum-ts/coreum/asset/ft/v1/token";

export interface MsgIssueEncodeObject extends EncodeObject {
    readonly typeUrl: "/coreum.asset.ft.v1.MsgIssue";
    readonly value: Partial<MsgIssue>;
}

const main = (async function() {
    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-pluto.testnet-1.coreum.dev:26657"; // rpc endpoint (different for different chains/environments)
    const senderMnemonic =
        "emerge cake river crush explain long else rebuild author duty bulb mind pelican sun alcohol add sample purity two crop wish oven engage tone";

    // ******************** Initialize clients ********************

    const tendermintClient = await Tendermint34Client.connect(coreumRpcEndpoint);
    const queryClient = new QueryClient(tendermintClient);
    const rpcClient = createProtobufRpcClient(queryClient);
    const feemodelQueryClient = new FeemodelQueryClient(rpcClient)
    const stakingExtension = setupStakingExtension(queryClient);

    // the custom tx types should be registered in the types registry
    const ftTypes: ReadonlyArray<[string, GeneratedType]> = [
        ["/coreum.asset.ft.v1.MsgIssue", MsgIssue],
    ];
    let registryTypes: ReadonlyArray<[string, GeneratedType]> = [
        ...defaultRegistryTypes,
        ...ftTypes,
    ]
    const registry = new Registry(registryTypes)

    // ******************** Bank MsgSend example ********************

    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 SigningStargateClient.connectWithSigner(
        coreumRpcEndpoint,
        senderWallet,
        { registry }
    );
    const senderCoreBalance = await senderClient.getBalance(sender.address, coreumDenom);
    console.log(`sender balance: ${senderCoreBalance.amount}`);

    console.log("preparing recipient wallet");
    const recipientWallet = await DirectSecp256k1HdWallet.generate(12, {
        prefix: coreumAccountPrefix,
        hdPaths: [stringToPath(coreumHDPath)],
    });
    const [recipient] = await recipientWallet.getAccounts();
    console.log(`recipient address: ${recipient.address}`);

    const msgBankSend: MsgSendEncodeObject = {
        typeUrl: "/cosmos.bank.v1beta1.MsgSend",
        value: MsgSend.fromPartial({
            fromAddress: sender.address,
            toAddress: recipient.address,
            amount: [{
                denom: coreumDenom,
                amount: "100000",
            }],
        }),
    };
    console.log(
        `sending ${msgBankSend.value.amount?.[0].amount}${msgBankSend.value.amount?.[0].denom} from ${msgBankSend.value.fromAddress} to ${msgBankSend.value.toAddress}`
    );

    let gasPrice = await getGasPriceWithMultiplier(feemodelQueryClient)
    const bankSendGas = await senderClient.simulate(sender.address, [msgBankSend], "")
    console.log(`estimated gas: ${bankSendGas}, gasPrice: ${gasPrice.toString()}`);
    const bankSendFee: StdFee = calculateFee(bankSendGas, gasPrice);
    const bankSendResult = await senderClient.signAndBroadcast(
        sender.address,
        [msgBankSend],
        bankSendFee
    );
    isDeliverTxSuccess(bankSendResult);
    console.log(`successfully sent, tx hash: ${bankSendResult.transactionHash}`);

    const recipientCoreBalance = await senderClient.getBalance(recipient.address, coreumDenom);
    console.log(`recipient balance: ${recipientCoreBalance.amount}`);

    // ******************** Staking MsgDelegate example ********************

    const recipientClient = await SigningStargateClient.connectWithSigner(
        coreumRpcEndpoint,
        recipientWallet,
        { registry }
    );

    // query all bonded validators to find first bonded to delegate to
    const bondedValidators = await stakingExtension.staking.validators("BOND_STATUS_BONDED");
    const validatorOperatorAddress = bondedValidators.validators[0].operatorAddress;
    const msgStakingDelegate: MsgDelegateEncodeObject = {
        typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
        value: MsgDelegate.fromPartial({
            delegatorAddress: recipient.address,
            validatorAddress: validatorOperatorAddress,
            amount: {
                denom: coreumDenom,
                amount: "100",
            },
        }),
    };
    console.log(
        `delegating ${msgStakingDelegate.value.amount?.amount}${msgStakingDelegate.value.amount?.denom} from ${recipient.address} to ${validatorOperatorAddress}`
    );
    // the gas price can be changed at that time, we need to re-fetch it
    gasPrice = await getGasPriceWithMultiplier(feemodelQueryClient)
    const stakingDelegateGas = await recipientClient.simulate(recipient.address, [msgStakingDelegate], "")
    console.log(`estimated gas: ${stakingDelegateGas}, gasPrice: ${gasPrice.toString()}`);
    const stakingDelegateFee: StdFee = calculateFee(stakingDelegateGas, gasPrice);
    const stakingDelegateResult = await recipientClient.signAndBroadcast(
        recipient.address,
        [msgStakingDelegate],
        stakingDelegateFee
    );
    isDeliverTxSuccess(stakingDelegateResult);
    console.log(`successfully delegated, tx hash: ${stakingDelegateResult.transactionHash}`);

    // ******************** Coreum custom message example, FT ********************

    const msgIssueFT: MsgIssueEncodeObject = {
        typeUrl: "/coreum.asset.ft.v1.MsgIssue",
        value: MsgIssue.fromPartial({
            issuer: sender.address,
            subunit: `mysubunit`,
            symbol: `mysymbol`,
            precision: 18,
            initialAmount: "1000000",
            features: [Feature.minting, Feature.burning],
            sendCommissionRate: `${Decimal.fromUserInput("0.5", 18).atomics}` // 50%
        }),
    };
    const ftDenom = `${msgIssueFT.value.subunit}-${sender.address}`
    console.log(
        `issuing ${ftDenom} FT`
    );

    gasPrice = await getGasPriceWithMultiplier(feemodelQueryClient)
    const issueFTGas = await senderClient.simulate(sender.address, [msgIssueFT], "")
    console.log(`estimated gas: ${issueFTGas}, gasPrice: ${gasPrice.toString()}`);
    const issueFTFee: StdFee = calculateFee(issueFTGas, gasPrice);
    // pay attention that additionally to the gas the `issue_fee` will be burned.
    const issueFTResult = await senderClient.signAndBroadcast(
        sender.address,
        [msgIssueFT],
        issueFTFee
    );
    isDeliverTxSuccess(issueFTResult);
    console.log(`successfully issued, tx hash: ${issueFTResult.transactionHash}`);

    const senderFTBalance = await senderClient.getBalance(sender.address, ftDenom);
    console.log(`sender ft balance: ${senderFTBalance.amount}${ftDenom}`);
})();

export async function getGasPriceWithMultiplier(feemodelQueryClient: FeemodelQueryClient) {
    const gasPriceMultiplier = 1.1
    const minGasPriceRes = await feemodelQueryClient.MinGasPrice({})
    const minGasPrice = decodeCosmosSdkDecFromProto(minGasPriceRes.minGasPrice?.amount || "")
    let gasPrice = minGasPrice.toFloatApproximation() * gasPriceMultiplier
    return GasPrice.fromString(`${gasPrice}${minGasPriceRes.minGasPrice?.denom || ""}`);
}

export default main;
Last Updated: