This example demonstrates how to create a CosmWasm contract that calls Union’s UCS01 Relay to execute a cross-chain asset transfer.
Summary of the steps:
Install required dependencies: Rust and uniond
.
Clone code example and install some dependencies,
Build the contract,
Create a Union account through the CLI then fund it from the faucet,
Deploy the contract to the Union Network,
Query the deployed transaction to obtain code_id
,
Instantiate the contract with the code_id
,
Query the instantiation transaction to obtain _contract_address
,
Execute the contract to transfer assets 🎉
Source code for this entire example can be found here
Install the uniond
binary which you will use to publish the contract and execute the transfer
# determine your system architecture
RELEASE = "uniond-release-$( uname -m )-linux"
# get the version we want to install
https://github.com/unionlabs/union/releases/download/ $VERSION / $RELEASE \
Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Install CLI packages: ripgrep
and jaq
cargo install ripgrep jaq
Create a new directory and initialize a git repository
mkdir example-ucs01-cosmwasm && cd example-ucs01-cosmwasm
Set up a new Rust project
cargo new --lib example_ucs01_cosmwasm
Add the required dependencies to the Cargo.toml
file
name = "example-ucs01-cosmwasm" # name of the project
crate-type = [ "cdylib" , 'rlib' ]
cosmwasm-schema = { version = "2.1.4" }
cosmwasm-std = { version = "2.1.4" , default-features = false , features = [ "std" , "staking" , "stargate" ] }
serde = { version = "1.0.210" , default-features = false , features = [ "derive" ] }
thiserror = { version = "1.0.64" , default-features = false }
Install uniond
# determine your system architecture
RELEASE = "uniond-release-$( uname -m )-linux"
# get version we want to install
https://github.com/unionlabs/union/releases/download/ $VERSION / $RELEASE \
Our end directory structure will look like this:
Directory src
Cargo.toml Cargo.lock .gitignore
export UCS01_RELAY = "union1m87a5scxnnk83wfwapxlufzm58qe2v65985exff70z95a2yr86yq7hl08h"
Let’s write a dead simple contract that transfers an asset from a CosmWasm contract to an EVM contract.
use cosmwasm_schema :: cw_serde;
entry_point, to_json_binary, Coin , DepsMut , Env , MessageInfo , Response , StdResult , Uint128 ,
use std :: collections :: BTreeMap ;
pub struct InstantiateMsg {}
contract_address : String ,
pub type Fees = BTreeMap < String , Uint128 >;
pub enum Ucs01ExecuteMsg {
/// This is the message we accept via Receive
/// The local channel to send the packets on
/// The remote address to send to.
/// How long the packet lives in seconds. If not specified, use default_timeout
pub timeout : Option < u64 >,
) -> StdResult < Response > {
Ok ( Response :: new () . add_attribute ( "action" , "instantiate" ))
) -> StdResult < Response > {
contract_address : String ,
) -> StdResult < Response > {
let msg = WasmMsg :: Execute {
contract_addr : contract_address . to_string (),
msg : to_json_binary ( & Ucs01ExecuteMsg :: Transfer ( TransferMsg {
receiver : receiver . clone (),
funds : vec! [ Coin { denom , amount }],
. add_attribute ( "action" , "transfer" )
. add_attribute ( "recipient" , receiver )
. add_attribute ( "amount" , amount . to_string ()))
You will be using the same RPC url across examples, so good to export it to an environment variable:
export RPC_URL = " https://rpc.testnet-8.union.build:443"
The build is two steps; first we compile the Rust code to WASM, and then we optimize the WASM to be smaller.
RUSTFLAGS = '-C target-cpu=mvp -C opt-level=z' cargo build \
--target wasm32-unknown-unknown \
-Z build-std=std,panic_abort \
-Z build-std-features=panic_immediate_abort
wasm-opt target/wasm32-unknown-unknown/release/example_ucs01_cosmwasm.wasm \
we need a wallet with some gas money in it to deploy the contract
Pick a name for the wallet and create it
export WALLET_NAME = "throwaway"
Let’s create a new wallet and fund it
uniond keys add $WALLET_NAME \
--home /home/ $USER /.union \
Save the mnemonic in a safe place.
To fund the wallet, we will use the faucet. You can find the faucet here .
curl https://rest.testnet-8.union.build/cosmos/bank/v1beta1/balances/ $WALLET_ADDRESS | jaq '.balances' [0]
tx wasm store ./target/wasm32-unknown-unknown/release/example_ucs01_cosmwasm.wasm \
--chain-id union-testnet-8 \
--home /home/ $USER /.union \
The above will return a transaction hash at the end, use it to query the transaction to get the code_id
:
uniond query tx $DEPLOY_TX_HASH --node https://rpc.testnet-8.union.build:443 | rg "_id"
Instantiate the contract:
tx wasm instantiate 269 '{}' \
--chain-id union-testnet-8 \
--home /home/ $USER /.union \
The above will return a transaction hash at the end, use it t query the transaction to get the _contract_address
:
query tx $INSTANTIATE_TX_HASH --node https://rpc.testnet-8.union.build:443 | rg "_contract_address"
Now you can execute the contract to transfer assets:
Let’s construct the JSON payload for the contract execution.
receiver
field we are using Vitalik’s address,
amount
is the amount of tokens to transfer,
denom
is the token’s denomination. Use muno
for this example,
contract_address
is the UCS01 Relay contract address (defined at the beginning),
channel
is the channel to use for the transfer.
"receiver" : "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" ,
"contract_address" : "union1m87a5scxnnk83wfwapxlufzm58qe2v65985exff70z95a2yr86yq7hl08h" ,
Execute the contract:
tx wasm execute $CONTRACT_ADDRESS "$( jaq -c '.' payload.json )" \
--chain-id union-testnet-8 \
--home /home/ $USER /.union \
--node $RPC_URL --amount 2muno
To see the result of your cross chain transfer, go to this query and replace the sender with yours: here