Table of contents
- Prerequisites
- What is a CosmWasm contract?
- What is Axelar Cosmos general message passing (GMP)
- Project setup and installation
- Build and deploy a CosmWasm contract on Osmosis
- Build and deploy an EVM smart contract on Avalanche
- Send a message from a CosmWasm contract to Avalanche
- Import dependencies
- Set up the environment, API settings, token, and contract details
- Add EVM contract details
- Decode the Private Key and Create a Wallet
- Connect to the Client
- Retrieve and Log Balances
- Estimate Gas Fee
- Check for Sufficient Balances
- Query Message from the Osmosis Contract
- Prepare and Send the Message
- Test sending a message from Coswasm contract to Avalanche
- Read a message from an EVM smart contract on Avalanche
- Send a message from EVM to a CosmWasm contract on Osmosis
- Read a message from a CosmWasm contract on Osmosis
- Conclusion
- Reference
Cross-chain messaging allows seamless communication and interaction between different blockchain ecosystems, from Ethereum Virtual Machine (EVM)-based chains to Cosmos-based chains.
Axelar has long been the best way to connect EVM and Cosmos chains via bridged assets. With General Message Passing, that connection goes beyond bridging, supporting a new generation of cross-chain applications that combine the best of Cosmos and EVM.
In this tutorial, you will learn how to:
Build and deploy an EVM smart contract on Avalanche
Build and deploy a CosmWasm contract on Osmosis
Send a message from a CosmWasm contract to Avalanche
Read a message from an EVM smart contract on Avalanche
Send a message from EVM to a CosmWasm contract on Osmosis
Read a message from a CosmWasm contract on Osmosis
Prerequisites
Practical understanding of how to build a CosmWasm contract
Basic understanding of how to build EVM smart contract
Install
osmosisd
CLI- Download and install the
osmosisd
CLI by following the instructions here. If the installation wizard does not work, build from the source using the following commands:
- Download and install the
git clone https://github.com/osmosis-labs/osmosis.git
cd osmosis
make build
Create a Wallet
- If you don't have a wallet yet, create one using the following command:
osmosisd keys add wallet
Obtain Test Tokens
- Get some test tokens from the Osmosis Testnet Faucet.
Ensure you have Docker installed to build the contract.
A wallet with an Avax token for testing. If you don’t have these tokens, you can get Avax from the Avalanche faucet.
What is a CosmWasm contract?
CosmWasm is a smart contracting platform designed for the Cosmos ecosystem. In simple terms, it utilizes WebAssembly (Wasm) in the Cosmos (Cosm) way.
CosmWasm contracts provide advanced smart contract capabilities within the Cosmos ecosystem, leveraging the performance and security advantages of WebAssembly and Rust. This allows developers to create complex, interoperable, and secure decentralized applications across various Cosmos-based blockchains.
What is Axelar Cosmos general message passing (GMP)
General Message Passing (GMP) empowers developers with the ability to build interchain-native applications that make cross-chain function calls and synchronize state in a way that is completely abstracted for the user.
In simpler terms, Axelar GMP enables developers to build applications that integrate functions (such as smart contracts) hosted on various connected chains, similar to using Lego bricks.
Axelar has expanded General Message Passing (GMP) to support Cosmos blockchains. With Axelar, you can now send and receive messages on EVM chains and Cosmos chains. Messages sent to Cosmos chains can be received by CosmWasm smart contracts (on blockchains with CosmWasm support), or those messages can be received natively at the consensus layer as part of Go code.
Cosmos GMP works by sending and receiving through IBC’s memo field. Cosmos chains that support GMP should integrate the appropriate middleware and verify the message source.
Project setup and installation
To start the project setup and installation quickly, clone this project on GitHub using the following command:
git clone https://github.com/axelarnetwork/send-message-from-cosmos-to-evm-example.git
Make sure you're on the start
branch using the following command:
git checkout start
Next, change the directory into the cloned folder and install the project locally using npm with the following command:
cd send-message-from-cosmos-to-evm-example && npm i
Create a .env
file
Run the following command to create an .env
file:
touch .env
Add your axl private key to .env
Export your osmo prefix address private key and add it to the .env
file you just created:
PRIVATE_KEY= // Add your account private key here
Build and deploy a CosmWasm contract on Osmosis
The cloned project has the following folder structure that already contains the WASM contract; the next step you will be taking is to build and deploy the contract.
├── wasm
│ ├── src
│ │ ├── contract.rs
│ │ ├── error.rs
│ │ ├── ibc.rs
| │ ├── lib.rs
│ │ ├── msg.rs
│ │ ├── state.rs
| │ ├── unit_tests.rs
│ ├── cargo.lock
│ ├── corgo.toml
├── .env
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore
In the contract.rs
, the following was implemented:
Initialization: The
instantiate
function sets up the contract by saving the initial configuration and a placeholder message into the contract's storage.Message Sending: The
execute
function handles variousExecuteMsg
variants to send messages to EVM or Cosmos chains using Axelar GMP, constructing and dispatching messages with the necessary payload and metadata.Message Reception: The contract includes functions (
receive_message_evm
andreceive_message_cosmos
) to receive and store messages from EVM and Cosmos chains, decoding the payload and saving it in the contract's state.IBC Integration: It leverages Inter-Blockchain Communication (IBC) to send cross-chain messages, specifically designed to work with Axelar's GMP for transferring messages and funds across chains.
Query Stored Messages: The
query
function enables retrieval of stored messages via theGetStoredMessage
query, returning the sender and message details stored in the contract.
Build the CosmWasm contract
Navigate into the wasm
folder and build the contract with the following command:
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.12.13
Make sure you have Docker daemon running before running the command above.
You should see something similar to what is displayed below on your terminal if the build is successful:
Info: RUSTC_WRAPPER=sccache
Info: sccache stats before build
Compile requests 0
Compile requests executed 0
Cache hits 0
Cache misses 0
Cache timeouts 0
Cache read errors 0
Forced recaches 0
Cache write errors 0
Compilation failures 0
Cache errors 0
Non-cacheable compilations 0
Non-cacheable calls 0
Non-compilation calls 0
Unsupported compiler calls 0
Average cache write 0.000 s
Average cache read miss 0.000 s
Average cache read hit 0.000 s
Failed distributed compilations 0
Cache location Local disk: "/root/.cache/sccache"
Cache size 0 bytes
Max cache size 10 GiB
Building contract in /code ...
Finished release [optimized] target(s) in 0.44s
Creating intermediate hash for send_receive.wasm ...
ebea0ee2e90b34fe4d55bd011051213938b95d596d6b56853c6e3e9bda8630ad ./target/wasm32-unknown-unknown/release/send_receive.wasm
Optimizing send_receive.wasm ...
Creating hashes ...
c255d85938b647f59fcb77fe9d52ceefef5e510cd71364c522ce9ce25d3de8cc send_receive.wasm
Info: sccache stats after build
Compile requests 0
Compile requests executed 0
Cache hits 0
Cache misses 0
Cache timeouts 0
Cache read errors 0
Forced recaches 0
Cache write errors 0
Compilation failures 0
Cache errors 0
Non-cacheable compilations 0
Non-cacheable calls 0
Non-compilation calls 0
Unsupported compiler calls 0
Average cache write 0.000 s
Average cache read miss 0.000 s
Average cache read hit 0.000 s
Failed distributed compilations 0
Cache location Local disk: "/root/.cache/sccache"
Cache size 0 bytes
Max cache size 10 GiB
done
Upload the CosmWasm contract
Next, you need to upload the contract using the following command:
osmosisd tx wasm store ./artifacts/send_receive.wasm --from wallet --gas-prices 0.4uosmo --gas auto --gas-adjustment 1.5 -y -b sync --output json --node https://rpc.osmotest5.osmosis.zone:443 --chain-id osmo-test-5
You should see something similar to what is displayed below on your terminal if the upload is successful:
gas estimate: 3150117
{
"height":"0",
"txhash":"339C8D4E8BB10E0DD337F79ACC26BB825C341E6DAFE803C5E8F8C2B42E9D5A33",
"codespace":"","code":0,"data":"",
"raw_log":"","logs":[],
"info":"","gas_wanted":"0","gas_used":"0","tx":null,
"timestamp":"","events":[]
}
Instantiate the CosmWasm contract
To instantiate the CosmWasm contract, you will need a CodeId
. To retrieve that, copy the transaction hash from the terminal response and paste it on Mintscan; in the transaction details, you should see a CodeId
similar to this.
Next, instantiate the contract with the following command:
osmosisd tx wasm instantiate <codeId> '{"channel":"channel-4118"}' --from wallet --label "send_receive" --gas-prices 0.1uosmo --gas auto --gas-adjustment 1.3 --no-admin -y -b sync --output json --node https://rpc.osmotest5.osmosis.zone:443 --chain-id osmo-test-5
Replace <codeId>
with the actual code ID of your transaction. You should see something similar to what is displayed below on your terminal if the instantiation is successful:
gas estimate: 208330
{
"height":"0",
"txhash":"6F2FD21EF41267562826444DB29091CA163941BC31AB5FCC085A42B3F67E2317",
"codespace":"","code":0,"data":"","raw_log":"","logs":[],
"info":"","gas_wanted":"0","gas_used":"0",
"tx":null,"timestamp":"","events":[]
}
You have successfully built, uploaded, and instantiated the CosmWasm contract. In the next section, you will build and deploy the EVM smart contract on Avalanche in this example and then interact with the EVM contract from the CosmWasm contract.
Save the CosmWasm contract address
You will need the CosmWasm contract address later in this tutorial. You can find it in the contract details on Mintscan by pasting the transaction hash and saving it somewhere. In this example, the contract address is osmo1vqgrchlfuymkjrzmrjznpam3xtzfemthzue43yt8l4ug046rtvwqarcl8r
.
Build and deploy an EVM smart contract on Avalanche
In this session, you will build and deploy an EVM smart contract to Avalanche, a contract you will interact with from the CosmWasm you deployed.
To quickly build and deploy the EVM smart contract, you can use remix.ethereum.org, a powerful toolset for developing, deploying, debugging, and testing Ethereum and EVM-compatible smart contracts.
Build contract
Create a new file titled SendReceive.sol
inside the contracts
folder and copy the code from this gist into it.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";
import {IAxelarGasService} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol";
import {StringToAddress, AddressToString} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/AddressString.sol";
contract SendReceive is AxelarExecutable {
using StringToAddress for string;
using AddressToString for address;
IAxelarGasService public immutable gasService;
string public chainName; // name of the chain this contract is deployed to
struct Message {
string sender;
string message;
}
Message public storedMessage; // message received from _execute
constructor(
address gateway_,
address gasReceiver_,
string memory chainName_
) AxelarExecutable(gateway_) {
gasService = IAxelarGasService(gasReceiver_);
chainName = chainName_;
}
function send(
string calldata destinationChain,
string calldata destinationAddress,
string calldata message
) external payable {
require(msg.value > 0, 'Gas payment is required');
// 1. Generate GMP payload
bytes memory executeMsgPayload = abi.encode(msg.sender.toString(), message);
bytes memory payload = _encodePayloadToCosmWasm(executeMsgPayload);
// 2. Pay for gas
gasService.payNativeGasForContractCall{value: msg.value}(
address(this),
destinationChain,
destinationAddress,
payload,
msg.sender
);
// 3. Make GMP call
gateway.callContract(destinationChain, destinationAddress, payload);
}
function _encodePayloadToCosmWasm(bytes memory executeMsgPayload) internal view returns (bytes memory) {
// Schema
// bytes4 version number (0x00000001)
// bytes ABI-encoded payload, indicating function name and arguments:
// string CosmWasm contract method name
// dynamic array of string CosmWasm contract argument name array
// dynamic array of string argument abi type array
// bytes abi encoded argument values
// contract call arguments for ExecuteMsg::receive_message_evm{ source_chain, source_address, payload }
bytes memory argValues = abi.encode(
chainName,
address(this).toString(),
executeMsgPayload
);
string[] memory argumentNameArray = new string[](3);
argumentNameArray[0] = "source_chain";
argumentNameArray[1] = "source_address";
argumentNameArray[2] = "payload";
string[] memory abiTypeArray = new string[](3);
abiTypeArray[0] = "string";
abiTypeArray[1] = "string";
abiTypeArray[2] = "bytes";
bytes memory gmpPayload;
gmpPayload = abi.encode(
"receive_message_evm",
argumentNameArray,
abiTypeArray,
argValues
);
return abi.encodePacked(
bytes4(0x00000001),
gmpPayload
);
}
function _execute(
string calldata /*sourceChain*/,
string calldata /*sourceAddress*/,
bytes calldata payload
) internal override {
(string memory sender, string memory message) = abi.decode(payload, (string, string));
storedMessage = Message(sender, message);
}
}
In the contract above,
Contract Initialization: The
SendReceive
contract inherits fromAxelarExecutable
and initializes the Axelar gas service and chain name in its constructorSending Messages: The
send
function allows users to send messages to a specified destination chain and address, requiring a gas payment. It generates a General Message Passing (GMP) payload, pays for the gas, and makes the call through the Axelar gatewayPayload Encoding: The
_encodePayloadToCosmWasm
internal function encodes the message payload into a specific format required for CosmWasm contracts, including schema versioning and argument encodingMessage Reception: The
_execute
function decodes the received message payload and stores the message in the contract's state, handling incoming messages from other chains
Compile contract
You can proceed to compile the smart contract. Click the compile icon, and then the compile buttons are shown below.
Deploy contract
Deploying the smart contract to Avalanche testnets requires specifying both the Axelar Gateway Service and the Gas Service contract in the argument. Here is the list of Axelar Gas Service and Gateway contracts for all the chains currently supported by Axelar.
This contract you want to deploy requires the Gas Service, Gateway, and Chain Name, which will be passed on to the constructor before deployment, as shown below.
Click the deploy icon on Remix, as shown below.
Select the deployment target, Injected Provider - MetaMask.
Add the Axelar Gateway
: 0xC249632c2D40b9001FE907806902f63038B737Ab
and the Gas Service
: 0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6
contract address deployed on Avalanche and chain name: Avalanche
.
Next, click the transact
button.
Copy contract address
Copy the contract address and save it somewhere; you will need it in the following section to send a message from the Coswasm contract to Avalanche.
Address 0x113BD07720a5Ae9104C5d54fBDfA83F792afc8e0 Details - Avalanche Testnet.
Send a message from a CosmWasm contract to Avalanche
You are all set to send a message from the CosmWasm contract you deployed to Avalanche. Create a new file with the name index.js
in the root directory.
Import dependencies
To import the required dependencies add the following code snippet.
require("dotenv").config();
const { GasPrice } = require("@cosmjs/stargate");
const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm-stargate");
const { AxelarQueryAPI } = require("@axelar-network/axelarjs-sdk");
const { DirectSecp256k1Wallet } = require("@cosmjs/proto-signing");
const { fromHex } = require("@cosmjs/encoding");
The script uses
dotenv
to load environment variables and imports essential modules from@cosmjs/stargate
,@cosmjs/cosmwasm-stargate
,@axelar-network/axelarjs-sdk
, and@cosmjs/proto-signing
to interact with Cosmos and Axelar networksThe
fromHex
function from@cosmjs/encoding
is imported for decoding hex strings, which is crucial for handling private keys and other hexadecimal data formats
Set up the environment, API settings, token, and contract details
You need to define a few variables to be used in the script for the environment, API settings, token, and contract details. To do that, add the following code snippet.
//...
// Environment and API settings
const privateKey = process.env.PRIVATE_KEY;
const rpcEndpoint = "https://rpc.osmotest5.osmosis.zone";
const chainId = "osmosis-7";
const testnetEnvironment = "testnet";
// Token and contract details
const aUSDC =
"ibc/1587E7B54FC9EFDA2350DC690EC2F9B9ECEB6FC31CF11884F9C0C5207ABE3921"; // aUSDC IBC address
const osmoDenom = "uosmo";
const gasPriceString = `0.4${osmoDenom}`;
const wasmContractAddress =
"osmo1vqgrchlfuymkjrzmrjznpam3xtzfemthzue43yt8l4ug046rtvwqarcl8r";
Add EVM contract details
//...
// Message details
const destinationChain = "Avalanche";
const destinationAddress = "0x113BD07720a5Ae9104C5d54fBDfA83F792afc8e0"; // Replace with the Avalanche address you deployed
const messageToSend = "Hello world from Osmosis!";
Replace 0x113BD07720a5Ae9104C5d54fBDfA83F792afc8e0
with the contract address you copied from Remix earlier when you deployed on Avalanche.
Decode the Private Key and Create a Wallet
Now, decode your private key and create a wallet:
//...
(async () => {
try {
// Decode the private key from hex
const decodedPrivateKey = fromHex(privateKey);
// Create a wallet from the private key
const wallet = await DirectSecp256k1Wallet.fromKey(
decodedPrivateKey,
"osmo",
);
const [{ address }] = await wallet.getAccounts();
} catch (error) {
console.error("An error occurred:", error);
}
})();
Connect to the Client
Set up the client to interact with the blockchain:
//...
// Connect to the client
const gasPrice = GasPrice.fromString(gasPriceString);
const client = await SigningCosmWasmClient.connectWithSigner(
rpcEndpoint,
wallet,
{ gasPrice },
);
Retrieve and Log Balances
Retrieve the balances of aUSDC and OSMO tokens and log them:
//...
// Retrieve balances
const balanceUsdc = await client.getBalance(address, aUSDC);
const balanceOsmo = await client.getBalance(address, osmoDenom);
// Log wallet information
console.log("----- Wallet Info -----");
console.log(`Wallet address: ${address}`);
console.log(`aUSDC balance: ${balanceUsdc.amount / 1e6} aUSDC`);
console.log(`Osmo balance: ${balanceOsmo.amount / 1e6} OSMO\\n`);
Estimate Gas Fee
Estimate the gas fee for sending the message:
//...
// Estimate gas fee
const api = new AxelarQueryAPI({ environment: testnetEnvironment });
const gasAmount = await api.estimateGasFee(
chainId,
destinationChain,
100000,
"auto",
"aUSDC",
);
console.log(`Estimated gas fee: ${parseInt(gasAmount) / 1e6} aUSDC`);
Check for Sufficient Balances
Ensure you have enough balances to cover the gas fees:
//...
// Check for sufficient balances
if (balanceUsdc.amount < gasAmount) {
console.error("Insufficient aUSDC balance to pay for gas fee");
return process.exit(0);
}
if (balanceOsmo.amount < 1e6) {
console.error("Insufficient OSMO balance to pay for gas fee");
return process.exit(0);
}
Query Message from the Osmosis Contract
Retrieve a stored message from the Osmosis contract:
//...
// Query message from the osmosis contract
const response = await client.queryContractSmart(wasmContractAddress, {
get_stored_message: {},
});
console.log("Message from Osmosis contract:", response.message);
Prepare and Send the Message
Prepare the payload and send the message to the Osmosis contract:
//...
// Prepare payload to send message to osmosis contract
const payload = {
send_message_evm: {
destination_chain: destinationChain,
destination_address: destinationAddress,
message: messageToSend,
},
};
const fee = {
amount: gasAmount,
denom: aUSDC,
};
console.log("Sending message to Osmosis contract...");
// Execute transaction
const result = await client.execute(
address,
wasmContractAddress,
payload,
"auto",
undefined,
[fee],
);
console.log("Sent:", result.transactionHash);
Test sending a message from Coswasm contract to Avalanche
You have successfully implemented sending messages from the CosmWasm contract you deployed earlier. It's time to test the implementation. Use the following command to run the script and ensure you are in the project's root directory.
node index.js
You should see something similar to what is displayed below on your terminal if the test is successful:
----- Wallet Info -----
Wallet address: osmo16m62wcd7dyednttjgayc08n22z509a70vcekuu
aUSDC balance: 48.553691 aUSDC
Osmo balance: 109.698386 OSMO
Estimated gas fee: 0.344874 aUSDC
Message from Osmosis contract: none
Sending message to Osmosis contract...
Sent: F19AB19C94794CF5BD2E480E1CEFD893D1E74E7CC709ADC8260736BCC253AD27
In case you run into an error like the following:
Error: Client network socket disconnected before secure TLS connection was established
at connResetException (node:internal/errors:705:14)
at TLSSocket.onConnectEnd (node:_tls_wrap:1594:19)
at TLSSocket.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1358:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
code: 'ECONNRESET',
path: null,
host: 'rpc.osmotest5.osmosis.zone',
port: 443,
localAddress: undefined
}
Wait a bit and retry.
If you encounter the error "Insufficient aUSDC balance to pay for gas fee,"
please proceed to the Axelar Discord server. In the faucet channel, kindly paste your axl wallet address in this format: !faucet axelar16m62wcd7dyednttjgayc08n22z509a70qduwp0
, and then conduct an IBC transfer to your osmo wallet address.
Check transaction on Axelarscan testnet.
If you do not have any errors, you can copy the transaction hash logged on the console, head over to the Axelarscan testnet, and paste it; you should be able to see how the message is sent from Osmosis to Avalanche via Axelar. You can find an example of the transaction here.
Read a message from an EVM smart contract on Avalanche
You have successfully sent a message from your CosmWasm contract to Avalanche; let's confirm that the message was actually sent and arrived on Avalanche.
Head back to https://remix.ethereum.org/, find the contract you deployed earlier, and click on the storedMessage
button, as shown below. You should see "Hello world from Osmosis!" logged, which indicates that your message was successfully sent from the CosmWasm contract and received on the Avalanche contract.
Send a message from EVM to a CosmWasm contract on Osmosis
You have been able to send the message successfully in the previous step; now, proceed to send a message from Avalanche to the same CosmWasm contract on Osmosis. To do that, on Remix, click on the arrow icon beside the “Send” button and enter the following details.
destinationChain: osmosis-7
destinationAddress: osmo1vqgrchlfuymkjrzmrjznpam3xtzfemthzue43yt8l4ug046rtvwqarcl8r
message: Hello from Avalanche
Make sure to replace osmo1vqgrchlfuymkjrzmrjznpam3xtzfemthzue43yt8l4ug046rtvwqarcl8r
with your contract address on Osmosis.
Next, pass a gas value in Gwei, in this example we passed 10000000 Gwei.
After confirming the transaction on the MetaMask popup, you can proceed to verify the transaction onchain on the Axelarscan testnet by copying the transaction hash and pasting it. Here is an example.
Read a message from a CosmWasm contract on Osmosis
In the previous step, you have successe=fully sent a message from Avalanche to Osmosis, in this step you will read the message from Osmosi to confirm it was truly received on Osmosis. To do that, create a new file, read.js
, in the root folder of your project and add the following code snippet.
require("dotenv").config();
const { GasPrice } = require("@cosmjs/stargate");
const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm-stargate");
const { DirectSecp256k1Wallet } = require("@cosmjs/proto-signing");
const { fromHex } = require("@cosmjs/encoding");
// Environment and API settings
const privateKey = process.env.PRIVATE_KEY;
const rpcEndpoint = "https://rpc.osmotest5.osmosis.zone";
// Token and contract details
const aUSDC =
"ibc/1587E7B54FC9EFDA2350DC690EC2F9B9ECEB6FC31CF11884F9C0C5207ABE3921";
const osmoDenom = "uosmo";
const gasPriceString = `0.4${osmoDenom}`;
const wasmContractAddress =
"osmo1vqgrchlfuymkjrzmrjznpam3xtzfemthzue43yt8l4ug046rtvwqarcl8r";
(async () => {
try {
// Decode the private key from hex
const decodedPrivateKey = fromHex(privateKey);
// Create a wallet from the private key
const wallet = await DirectSecp256k1Wallet.fromKey(
decodedPrivateKey,
"osmo",
);
const [{ address }] = await wallet.getAccounts();
// Connect to the client
const gasPrice = GasPrice.fromString(gasPriceString);
const client = await SigningCosmWasmClient.connectWithSigner(
rpcEndpoint,
wallet,
{ gasPrice },
);
// Retrieve balances
const balanceUsdc = await client.getBalance(address, aUSDC);
const balanceOsmo = await client.getBalance(address, osmoDenom);
// Log wallet information
console.log("----- Wallet Info -----");
console.log(`Wallet address: ${address}`);
console.log(`aUSDC balance: ${balanceUsdc.amount / 1e6} aUSDC`);
console.log(`Osmo balance: ${balanceOsmo.amount / 1e6} OSMO\n`);
// Query message from the osmosis contract
const response = await client.queryContractSmart(wasmContractAddress, {
get_stored_message: {},
});
console.log("Message from Osmosis contract:", response.message);
} catch (error) {
console.error("An error occurred:", error);
}
})();
You should see something similar to what is displayed below on your terminal if the test is successful:
----- Wallet Info -----
Wallet address: osmo16m62wcd7dyednttjgayc08n22z509a70vcekuu
aUSDC balance: 48.208817 aUSDC
Osmo balance: 109.58592 OSMO
Message from Osmosis contract: Hello from Avalanche
Woohoo! If you made it this far, congratulations.
Conclusion
In this tutorial, you have learned how to build and deploy an EVM smart contract on Avalanche, build and deploy a CosmWasm contract on Osmosis, send a message from a CosmWasm contract to Avalanche, read a message from an EVM smart contract on Avalanche, send a message from EVM to a CosmWasm contract on Osmosis, and read a message from a CosmWasm contract on Osmosis.
By mastering these steps, you can effectively create interoperable decentralized applications that leverage the strengths of both EVM-based and Cosmos-based blockchain networks.