This site requires Javascript to be enabled.

Enabling cross-chain CW20 token transfers with CW20-ICS20

This guide will cover how to enable cross-chain transfers for CW20 tokens using a CW20-ICS20 contract which is an IBC-enabled contract that allows for sending CW20 tokens from one chain over the standard ICS20 protocol to the bank module of another chain. This functionality enables our custom CW20 tokens to be used like native tokens on other chains.

The contract implementation for CW20-ICS20 can be found in the cw-plus GitHub repository.

Prerequisites

Before proceeding with this guide, please ensure you have the following:

  1. Archway Daemon (archwayd): We will use the core Archway Daemon for deploying, instantiating, and interacting with the CW20-ICS20 contract. This is because the contract's structure is not compatible with the Archway Developer CLI requirements and would require modifications to function properly. To keep things simple, we'll use archwayd instead. You can find the guide for setting up archwayd here.
  2. Docker: You will need Docker to compile and build the contract. Ensure you have it installed and running on your system. For installation instructions, refer to the Docker installation guide.

Set up an account

To interact with the Archway network, you'll need to create an account. For detailed instructions on how to set up your account using the Archway Daemon, please refer to the official Archway documentation: Archway Daemon Account Setup Guide.

This guide will walk you through the process of creating a new key and obtaining your mnemonic phrase and account address. Remember to keep your mnemonic phrase secure. Never share it with anyone, as it provides full access to your account and tokens.

After setting up your account, you'll need to fund it. For testnet purposes, you can use the Archway testnet faucet. To learn how to use the faucet to fund your account, follow this guide: Archway Testnet Faucet Guide.

Project setup

To get started with the CW20-ICS20 contract, follow these steps:

  1. The CW20-ICS20 contract is part of the cw-plus repository. You can find more information about it here.
  2. Clone the entire cw-plus repository using the following command:
git clone https://github.com/CosmWasm/cw-plus.git
  1. After cloning, navigate to the cw-plus folder:
cd cw-plus

Build your contract

Now that the project is set up, it's time to build and optimize your contract for on-chain deployment. We'll use the Rust Optimizer for this task.

It's important to note that the cw20-ics20 contract has dependencies on other contracts within the cw-plus repository. This is evident in the Cargo.toml file, where you'll see multiple dependencies marked with { workspace = true }. Due to these workspace dependencies, we need to build all the related contracts in order to successfully compile the cw20-ics20 contract.

Ensure you have Docker installed and running on your system. Once Docker is ready, execute the following command from the root of your project's folder:

docker run --rm -v "$(pwd)":/code \  --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \  cosmwasm/optimizer:0.16.0

This command will:

  1. Run the Rust Optimizer in a Docker container
  2. Mount your current directory to the container
  3. Optimize your contract for deployment

After running this command, you should find the optimized cw20_ics20.wasm file in the artifacts directory.

Note: The optimization process may take a few minutes, depending on your system's performance.

Store your contract on-chain

To store the contract on-chain, execute the following command from the root of the cw-plus folder:

archwayd tx wasm store artifacts/cw20_ics20.wasm --from <my-wallet> --node https://rpc.constantine.archway.io:443 --chain-id constantine-3 --gas auto --gas-prices $(archwayd q rewards estimate-fees 1 --node 'https://rpc.constantine.archway.io:443' --output json | jq -r '.gas_unit_price | (.amount + .denom)') --gas-adjustment 1.4

Make sure to replace <my-wallet> with your actual wallet name or address.

This command:

  1. Uses archwayd to submit a transaction to store the WASM file on-chain
  2. Specifies the Archway testnet (Constantine) as the target network
  3. Automatically estimates the gas needed and sets the gas price based on current network conditions
  4. Applies a gas adjustment factor of 1.4 to ensure sufficient gas is provided

After executing this command, you should receive a transaction hash. You can use this hash to check the transaction status and retrieve the contract's code ID once it's successfully stored on-chain.

Here's an example of the output:

code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: '[]'
timestamp: ""
tx: null
txhash: F0126A3A0D6FB876C7C56FACB071B1B5C0FAF9F16EBF8D24EA6783A1B6B22599

Save the code id

You can use this transaction hash in your block explorer to find the code_id which will be required for instantiating your contract. Here is an example: https://www.mintscan.io/archway-testnet/tx/F0126A3A0D6FB876C7C56FACB071B1B5C0FAF9F16EBF8D24EA6783A1B6B22599.

If you have the jq tool installed on your terminal, you can execute the following command to get the code_id:

archwayd query tx F0126A3A0D6FB876C7C56FACB071B1B5C0FAF9F16EBF8D24EA6783A1B6B22599 --node 'https://rpc.constantine.archway.io:443' --output json | jq '.logs[].events[] | select(.type == "store_code") | .attributes[] | select(.key == "code_id") | .value'

Instantiate contract

The instantiation process allows us to whitelist at least one CW20 token to be transferred via IBC. Here's the structure of the message required for instantiating the contract:

pub struct InitMsg {    /// Default timeout for ics20 packets, specified in seconds    pub default_timeout: u64,    /// who can allow more contracts    pub gov_contract: String,    /// initial allowlist - all cw20 tokens we will send must be previously allowed by governance    pub allowlist: Vec<AllowMsg>,    /// If set, contracts off the allowlist will run with this gas limit.    /// If unset, will refuse to accept any contract off the allow list.    pub default_gas_limit: Option<u64>,}

Let's break down the fields of InitMsg:

  • default_timeout: Specifies the default timeout for ICS20 packets in seconds, defining how long a packet remains valid.
  • gov_contract: Address of the admin managing permissions for this CW20-ICS20 contract.
  • allowlist: A list of allowed tokens, detailing which CW20 tokens can interact with this contract, along with optional custom gas limits.
  • default_gas_limit: Optional parameter setting the default gas limit for unauthorized contracts attempting to interact with this contract.

Here's an example of the instantiate command:

archwayd tx wasm instantiate <code-id> \'{"default_timeout": 300, "gov_contract": "archway1govcontractaddress", "allowlist": [{"contract": "archway1cw20tokenaddress1", "gas_limit": 140000000000}, {"contract": "archway1cw20tokenaddress2","gas_limit": 200000}], "default_gas_limit": 250000}' \--from <my-wallet> \--node https://rpc.constantine.archway.io:443 \--chain-id constantine-3 \--label testdev \--admin <contract-admin-address> \--gas auto \--gas-prices $(archwayd q rewards estimate-fees 1 --node 'https://rpc.constantine.archway.io:443' --output json | jq -r '.gas_unit_price | (.amount + .denom)') \--gas-adjustment 1.3

Replace <code-id>, <my-wallet>, and <contract-admin-address> with your actual values.

You will get a response like the following:

code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: '[]'
timestamp: ""
tx: null
txhash: B8A7D1C2AAD886C83292168906D32EC5EBE4C7FBF63B419CE782D73A18C201DF