CosmWasmClient Part 2: Writing
In the first part of this duology, we saw how to use the class CosmWasmClient from CosmWasm JS for reading data from a CosmWasm blockchain. This article is all about writing, i.e. crafting, signing and sending transactions.
A simple token send transaction
This part works for all Cosmos SDK chain with the bank module enabled and aims to make you familiar with the tooling. The first thing we need is a Pen, a simple, insecure and definitely-not-for-production!!1 tool for key storage and signing. This Pen can then be used by SigningCosmWasmClient, the bigger brother of CosmWasmClient with signing support.
We use our REPL again (npx @cosmwasm/cli
) to get started:
>> const mnemonic = Bip39.encode(Random.getBytes(16)).toString()>> mnemonic
'flush token addict voice luxury hotel crazy vast spike nature flash air'>> const pen = await Secp256k1Pen.fromMnemonic(mnemonic)>> const address = pubkeyToAddress(encodeSecp256k1Pubkey(pen.pubkey), "cosmos")>> address
'cosmos18g5r9zv4k6wxramn8txsy7zu0a7u86clmcdlk2'
This gives us a secret (mnemonic
) and an address for Demonet. We now create a client using those for signing and sending:
>> const client = new SigningCosmWasmClient("https://lcd.demo-071.cosmwasm.com/", address, signBytes => pen.sign(signBytes))
The first argument is an API URL to a Cosmos SDK light client daemon (also called REST server). The second argument is our sender address. And the third one is a callback that receives bytes to be signed and returns a signature. We implement this callback using our simlpe Pen. But the callback is sufficiently generic to support any kind of advanced key management solution including as user interaction, e.g. when typing a password is required.
Our newly generated address is not yet known to the blockchain and if we try to call await client.getNonce()
, we get an Error: Account does not exist on chain. Send some tokens there before trying to query nonces. Let’s follow that advice:
>> axios.post("https://faucet.demo-071.cosmwasm.com/credit", { ticker: "COSM", address: "cosmos18g5r9zv4k6wxramn8txsy7zu0a7u86clmcdlk2" })
{
status: 200,
statusText: 'OK',
// ...
data: 'ok'
}
Now we have COSM tokens to pay fees on Demonet:
>> await client.getAccount()
{
address: 'cosmos18g5r9zv4k6wxramn8txsy7zu0a7u86clmcdlk2',
balance: [ { denom: 'ucosm', amount: '10000000' } ],
pubkey: undefined,
accountNumber: 31,
sequence: 0
}>> await client.sendTokens("cosmos1z3ef6m7q933f9k63j4y7l86tsm59tedett64ry", [{ amount: "7890", denom: "ucosm" }], "for dinner")
{
logs: [ { msg_index: 0, log: '', events: [Array] } ],
rawLog: '[{"msg_index":0,"log":"","events":[{"type":"message","attributes":[{"key":"action","value":"send"},{"key":"sender","value":"cosmos18g5r9zv4k6wxramn8txsy7zu0a7u86clmcdlk2"},{"key":"module","value":"bank"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"cosmos1z3ef6m7q933f9k63j4y7l86tsm59tedett64ry"},{"key":"sender","value":"cosmos18g5r9zv4k6wxramn8txsy7zu0a7u86clmcdlk2"},{"key":"amount","value":"7890ucosm"}]}]}]',
transactionHash: '7FAF617D6E9E0E998E85C8DA6780E529E419DA74B01E4276E009A2DDB2729773'
}
We successfully created, signed and submitted a transaction. Let’s see if the tokens arrived:
>> await client.getAccount("cosmos1z3ef6m7q933f9k63j4y7l86tsm59tedett64ry")
{
address: 'cosmos1z3ef6m7q933f9k63j4y7l86tsm59tedett64ry',
balance: [ { denom: 'ucosm', amount: '7890' } ],
pubkey: undefined,
accountNumber: 32,
sequence: 0
}
Uploading, instantiating and executing contracts
We use the Mask contract from cosmwasm-examples, which contains an optimized Wasm compilation in contract.wasm
:
$ wget https://github.com/CosmWasm/cosmwasm-examples/raw/mask-0.1.1/mask/contract.wasm
$ sha256sum contract.wasm
5f1e1d3b667f42348a1d331e2507702b88e7252007bd2df370058b56fe469e55 contract.wasm
Now we can upload the wasm blob along with source and builder information required for CosmWasm Verify:
>> const wasm = fs.readFileSync("contract.wasm")>> toHex(new Sha256(wasm).digest())
'5f1e1d3b667f42348a1d331e2507702b88e7252007bd2df370058b56fe469e55'>> client.upload(wasm, { source: "https://crates.io/api/v1/crates/cw-mask/0.1.1/download", builder: "confio/cosmwasm-opt:0.7.3" })
{
originalSize: 99254,
originalChecksum: '5f1e1d3b667f42348a1d331e2507702b88e7252007bd2df370058b56fe469e55',
compressedSize: 35667,
compressedChecksum: '6ae5c0efce41b515503e36578dd75585d5392848b494ddad9f49e0ae33b9e432',
codeId: 3,
logs: [ { msg_index: 0, log: '', events: [Array] } ],
transactionHash: '22FF33F256967D453AB98593D34899A2C4386BEEC6FCA73DD3F4A047191EF3DC'
}
We see a few things in the result of the upload call:
- The Wasm file was compressed by the client
- The code was stored as code ID 3 (see code explorer)
- The transaction ID of the upload transaction, which can be looked up in a code explorer
To instantiate a contract, we send an InitMsg as part of an wasm/instantiate message:
>> const codeId = 3
>> const initMsg = {}
>> client.instantiate(codeId, initMsg, "My Mask")
{
contractAddress: 'cosmos1p0vnn3m468jceccy0z6vxv8lchfpn3p976vqa7',
logs: [ { msg_index: 0, log: '', events: [Array] } ],
transactionHash: 'C78A672D3F9EC2E5974FAEE99FE49CDF62875B4DDCD95CCDD8AEF95A4DA3CE0E'
}>> const contractAddress = "cosmos1p0vnn3m468jceccy0z6vxv8lchfpn3p976vqa7"
In this case, the InitMsg does not contain any properties. A more interesting example from the ERC20 contract would look like this.
The contract was instantiated at address cosmos1p0vn…p976vqa7. We use the faucet from above to give it some tokens. We can now execute it by sending a HandleMsg:
>> .editor
const handleMsg = {
reflectmsg: {
msgs: [
{
send: {
from_address: "cosmos1p0vnn3m468jceccy0z6vxv8lchfpn3p976vqa7",
to_address: "cosmos1u2tpck56avjc50fugffr9ch8mg09czdjwnyf93",
amount: [{ amount: "80000", denom: "ucosm" }],
},
}
]
}
}
// use Ctrl+d to close the editor>> client.execute(contractAddress, handleMsg)
{
logs: [ { msg_index: 0, log: '', events: [Array] } ],
transactionHash: 'BDEA4BE3B9A4E5B88708A204A7F03C7249E6D082B5F64327FCEC6D4467297FB4'
}
Well done! You sucessfully used all three CosmWasm messages: wasm/store-code, wasm/instantiate and wasm/execute.