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 \
Make uniond
executable
Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Install development tools
sudo apt install jq tree git ripgrep build-essential
Install wasm packages: wasm-pack
and wasm-opt
cargo install wasm-pack wasm-opt
Set up a new Rust project
cargo new --lib example-ucs01-cosmwasm
cd 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 }
Configure Rust-nightly
rustup override set nightly-2024-10-11
rustup component add rust-src --toolchain nightly-2024-10-11-aarch64-unknown-linux-gnu
Our end directory structure will look like this:
The UCS01 Relay contract address:
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 ,
pub struct InstantiateMsg {}
contract_address : String ,
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 >,
#[cfg_attr(not(feature = "library" ), entry_point)]
) -> StdResult < Response > {
Ok ( Response :: new () . add_attribute ( "action" , "instantiate" ))
#[cfg_attr(not(feature = "library" ), entry_point)]
) -> 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
$HOME /uniond keys add $WALLET_NAME \
--home /home/ $USER /.union \
Save the mnemonic and address in a safe place
Set the WALLET_ADDRESS
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 | jq '.balances' [0]
$HOME /uniond tx wasm store ./build/contract.wasm \
--chain-id union-testnet-8 \
--home /home/ $USER /.union \
The above will return a transaction hash at the end as txhash
. Record it:
DEPLOY_TX_HASH = txhash-value-of-previous-command
… and use it to query the transaction to get the code_id
:
$HOME /uniond query tx $DEPLOY_TX_HASH --node https://rpc.testnet-8.union.build:443 | rg -C 1 "code_id"
Record the code-id:
CODE_ID = code_id-value-of-previous-command
… and instantiate the contract:
tx wasm instantiate $CODE_ID '{}' \
--chain-id union-testnet-8 \
--home /home/ $USER /.union \
The above will return a transaction hash at the end as txhash
. record it:
INSTANTIATE_TX_HASH = txhash-value-of-previous-command
… and use it to query the transaction to get the _contract_address
(you’ll see it twice):
$HOME /uniond query tx $INSTANTIATE_TX_HASH --node https://rpc.testnet-8.union.build:443 | rg -C 1 "_contract_address"
Record the contract address
CONTRACT_ADDRESS = _contract_address-value-of-previous-command
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" : "d8da6bf26964af9d7eed9e03e53415d37aa96045" ,
"contract_address" : "union1m87a5scxnnk83wfwapxlufzm58qe2v65985exff70z95a2yr86yq7hl08h" ,
Execute the contract:
tx wasm execute $CONTRACT_ADDRESS "$( jq -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 your $WALLET_ADDRESS
: here