Smart Contracts

This document explains how to perform tasks related to working with smart contracts with xplajs.

Upload Code

You will first need a compiled WASM smart contract’s binary to upload. The following example demonstrates how to upload a smart contract to the XPLA blockchain using the MsgStoreCode message type.

The process involves creating a DirectSigner, reading the compiled WASM binary file, constructing a store code message, and then signing and broadcasting the transaction. After the transaction is processed, you can extract the code ID from the transaction response, which is needed for contract instantiation.

Note: The counter.wasm file in this example is compiled from the CosmWasm cw-template, which provides a quickstart template for building smart contracts in Rust.

import { DEFAULT_COSMOS_EVM_SIGNER_CONFIG, EthSecp256k1HDWallet } from "@xpla/xpla"
import { HDPath } from "@interchainjs/types"
import { createCosmosQueryClient, DirectSigner } from "@interchainjs/cosmos";
import fs from "fs"
import { storeCode } from "@xpla/xplajs";
import { createRPCQueryClient } from "@xpla/xplajs/xpla/rpc.query";

const queryClient = await createCosmosQueryClient("https://cube-rpc.xpla.io");
    const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
    const wallet = await EthSecp256k1HDWallet.fromMnemonic(mnemonic, {
        derivations: [{
            prefix: "xpla",
            hdPath: HDPath.eth().toString()
        }]
    })

    const baseSignConfig = {
        queryClient: queryClient,
        chainId: "cube_47-5",
        addressPrefix: "xpla",
    }
    const signerConfig = {
        ...DEFAULT_COSMOS_EVM_SIGNER_CONFIG,
        ...baseSignConfig
    }
    
    const signer = new DirectSigner(wallet, signerConfig);
    const address = (await signer.getAddresses())[0]
    
    const tx = await storeCode(
        signer,
        address, 
        {
            sender: address,
            wasmByteCode: Buffer.from(fs.readFileSync("./counter.wasm"))
        },
        {
            gas: "2000000",
            amount: [{denom: "axpla", amount: "560000000000000000"}],
        },
        ""
    )
    
    console.log("Waiting for transaction to be included in block...")
    await tx.wait()

    const client = await createRPCQueryClient({rpcEndpoint: "https://cube-rpc.xpla.io"})
    const res = await client.cosmos.tx.v1beta1.getTx({hash: tx.transactionHash || ""})
    const codeId = res.txResponse?.events.find(e => e.type === "store_code")?.attributes.find(a => a.key === "code_id")?.value
    console.log(codeId)

Create a Contract

For XPLA Chain smart contracts, there is a distinction between uploading contract code and creating a contract. This allows multiple contracts to share the same code if there are only minor variations in their logic which can be configured at contract creation. This configuration is passed in an InitMsg, and provides the initial state for the contract.

To create or instantiate a smart contract, you must first know the code ID of an uploaded code. You will reference it in a MsgInstantiateContract alongside the InitMsg to create the contract. Upon successful creation, your contract will be located at an address that you specify.

const codeId = 1761n
const tx = await instantiateContract(
    signer,
    address, 
    {
        sender: address,
        codeId,
        label: "counter",
        msg: new TextEncoder().encode(`{"count": 0}`),
        admin: address,
        funds: [],
    },
    {
        gas: "2000000",
        amount: [{denom: "axpla", amount: "560000000000000000"}],
    },
    ""
)

console.log("Waiting for transaction to be included in block...")
await tx.wait()

const client = await createRPCQueryClient({rpcEndpoint: "https://cube-rpc.xpla.io"})
const res = await client.cosmos.tx.v1beta1.getTx({hash: tx.transactionHash || ""})
const contractAddress = res.txResponse?.events.find(e => e.type === "instantiate")?.attributes.find(a => a.key === "_contract_address")?.value
console.log(contractAddress)

Execute a Contract

Smart contracts respond to JSON messages called HandleMsg which can exist as different types. The smart contract writer should provide any end-users of the smart contract with the expected format of all the varieties of HandleMsg the contract is supposed to understand, in the form of a JSON schema. The schema thus provides an analog to Ethereum contracts’ ABI.

const contractAddress = "xpla1cspdyqa2722k5e5wfhhuf9tehy4yh5ngy83yd0gg48x7emjvjjasmg76fz"
const tx = await executeContract(
    signer,
    address, 
    {
        sender: address,
        contract: contractAddress,
        msg: new TextEncoder().encode(`{"increment": {}}`),
        funds: [],
    },
    {
        gas: "2000000",
        amount: [{denom: "axpla", amount: "560000000000000000"}],
    },
    ""
)

console.log("Waiting for transaction to be included in block...")
await tx.wait()
console.log(tx)

Query Data from a Contract

A contract can define a query handler, which understands requests for data specified in a JSON message called a QueryMsg. Unlike the message handler, the query handler cannot modify the contract’s or blockchain’s state – it is a readonly operation. Therefore, a querying data from a contract does not use a message and transaction, but works directly through the RPC Client API.

const contractAddress = "xpla1cspdyqa2722k5e5wfhhuf9tehy4yh5ngy83yd0gg48x7emjvjjasmg76fz"
const res = await client.cosmwasm.wasm.v1.smartContractState({
    address: contractAddress, 
    queryData: new TextEncoder().encode(`{"get_count": {}}`)
})

const { count } = JSON.parse(new TextDecoder().decode(res.data));
console.log(count)