Cw-Orchestrator

docs.rs Crates.io Codecov

cw-orchestrator is an advanced testing and deployment tool for CosmWasm smart-contracts. It’s designed to make it easy to test and deploy contracts in a variety of environments including cw-multi-test, local, testnet, and mainnet. It does this by providing the ability to write environment-generic code that interacts with CosmWasm contracts. In doing so it removes the need to maintain deployment code for multiple environments. In short, cw-orchestrator is the go-to tool for testing and deploying CosmWasm contracts.

Features

Below we briefly outline the key features that cw-orchestrator has to offer.

Testing

cw-orchestrator provides a testing framework that makes it easy to write tests for CosmWasm contracts. It does this by providing a testing environment that mimics the behavior of a CosmWasm blockchain. This allows you to write tests that interact with your contract in the same way that it would be interacted with on a real blockchain.

This framework allow developers to easily test contract-to-contract interactions without having to deal with the overhead of running a node locally. The testing framework also provides a number of utilities simplify the syntax for write tests. These utilities include the ability to easily set and query balances, set block height/time and more.

Additionally developers can share their infrastructure with other developers by creating a wrapper around their project’s deployment logic, allowing others to easily test how their contracts interact with the project.

The testing frameworks supported by cw-orchestrator includes:

Deployment + Scripting

cw-orchestrator also provides the ability to deploy to real networks. It does this by providing an easy to use interface to a blockchain node that can be used to submit transactions, query state and inspect transaction results. Any blockchain transaction can be broadcasted using cw-orchestrator.

Interface Generation

Interacting with a smart-contract is often verbose, leading to a lot of boilerplate code. cw-orchestrator solves this problem by providing a macro that generates an interface to a contract. This interface can be used to easily interact with a contract and improves the readability of your tests and deployments. Making it easier to write and maintain tests and deployments. Additionally, because this library is written in Rust, any breaking changes to your contract’s interface will cause a compile-time error, making it easy to keep your tests and deployments up to date.

Getting Started

These docs contain a quick-start and a longer tutorial-style walkthrough.

Quick-Start Guide

Get ready to change the way you interact with contracts. The following steps will allow you to write clean code such as:

    counter.upload()?;
    counter.instantiate(&InstantiateMsg { count: 0 }, None, &[])?;

    counter.increment()?;

    let count = counter.get_count()?;
    assert_eq!(count.count, 1);

In this quick-start guide, we will review the necessary steps in order to integrate cw-orch into a simple contract crate. We review integration of rust-workspaces (multiple contracts) at the end of this page.

NOTE: Additional content

If you’re moving quicker than everybody else, we suggest looking at a before-after review of this example integration. This will help you catch the additions you need to make to your contract to be able to interact with it using cw-orchestrator.

Video Workshop

If you prefer watching a video, you can follow the workshop below:

Summary

Single Contract Integration

Adding cw-orch to your Cargo.toml file

To use cw-orchestrator, you need to add cw-orch to your contract’s TOML file. Run the command below in your contract’s directory:

cargo add cw-orch

Alternatively, you can add it manually in your Cargo.toml file as shown below:

[dependencies]
cw-orch = {version = "0.21.2" } # Latest version at time of writing

NOTE: Even if you include cw-orch in your dependencies here, it won’t be included in your wasm contract. Learn more about this behavior in the section about Wasm Compilation

Creating an Interface

When using a single contract, we advise creating an interface.rs file inside your contract’s directory. You then need to add this module to your lib.rs file. This file should not be included inside you final wasm. In order to do that, you need to add #[cfg(not(target_arch = "wasm32"))] when importing the file.

#[cfg(not(target_arch = "wasm32"))]
mod interface;

Then, inside that interface.rs file, you can define the interface for your contract:

use cw_orch::{interface, prelude::*};

use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};

pub const CONTRACT_ID: &str = "counter_contract";

#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)]
pub struct CounterContract;

impl<Chain> Uploadable for CounterContract<Chain> {
    /// Return the path to the wasm file corresponding to the contract
    fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
        artifacts_dir_from_workspace!()
            .find_wasm_path("counter_contract")
            .unwrap()
    }
    /// Returns a CosmWasm contract wrapper
    fn wrapper() -> Box<dyn MockContract<Empty>> {
        Box::new(
            ContractWrapper::new_with_empty(
                crate::contract::execute,
                crate::contract::instantiate,
                crate::contract::query,
            )
            .with_migrate(crate::contract::migrate),
        )
    }
}

Learn more about the content of the interface creation specifics on the interface page

NOTE: It can be useful to re-export this struct to simplify usage (in lib.rs):

#[cfg(not(target_arch = "wasm32"))]
pub use crate::interface::CounterContract;

Interaction helpers

cw-orchestrator provides a additional macros that simplify contract calls and queries. The macro implements functions on the interface for each variant of the contract’s ExecuteMsg and QueryMsg.

Enabling this functionality is very straightforward. Find your ExecuteMsg and QueryMsg definitions (in msg.rs in our example) and add the ExecuteFns and QueryFns derive macros to them like below:

#[cw_serde]
#[derive(cw_orch::ExecuteFns)] // Function generation
/// Execute methods for counter
pub enum ExecuteMsg {
    /// Increment count by one
    Increment {},
    /// Reset count
    Reset {
        /// Count value after reset
        count: i32,
    },
}

#[cw_serde]
#[derive(cw_orch::QueryFns)] // Function generation
#[derive(QueryResponses)]
/// Query methods for counter
pub enum QueryMsg {
    /// GetCount returns the current count as a json-encoded number
    #[returns(GetCountResponse)]
    GetCount {},
}

// Custom response for the query
#[cw_serde]
/// Response from get_count query
pub struct GetCountResponse {
    /// Current count in the state
    pub count: i32,
}

Make sure to derive the #[derive(cosmwasm_schema::QueryResponses)] macro on your query messages !

Find out more about the interaction helpers on the interface page

NOTE: Again, it can be useful to re-export these generated traits to simplify usage (in lib.rs):

pub use crate::msg::{ExecuteMsgFns as CounterExecuteMsgFns, QueryMsgFns as CounterQueryMsgFns};

Using the integration

Now that all the setup is done, you can use your contract in tests, integration-tests or scripts.

Start by importing your crate, in your [dev-dependencies] for instance:

counter-contract = { path = "../counter-contract"}

You can now use:

use counter_contract::{
    msg::InstantiateMsg, CounterContract, CounterExecuteMsgFns, CounterQueryMsgFns,
};
use cw_orch::{anyhow, prelude::*};

// From https://github.com/CosmosContracts/juno/blob/32568dba828ff7783aea8cb5bb4b8b5832888255/docker/test-user.env#L2
const LOCAL_MNEMONIC: &str = "clip hire initial neck maid actor venue client foam budget lock catalog sweet steak waste crater broccoli pipe steak sister coyote moment obvious choose";
pub fn main() -> anyhow::Result<()> {
    std::env::set_var("LOCAL_MNEMONIC", LOCAL_MNEMONIC);
    dotenv::dotenv().ok(); // Used to load the `.env` file if any
    pretty_env_logger::init(); // Used to log contract and chain interactions

    let network = networks::LOCAL_JUNO;
    let chain = DaemonBuilder::new(network).build()?;

    let counter = CounterContract::new(chain);

    counter.upload()?;
    counter.instantiate(&InstantiateMsg { count: 0 }, None, &[])?;

    counter.increment()?;

    let count = counter.get_count()?;
    assert_eq!(count.count, 1);

    Ok(())
}

Integration in a workspace

In this paragraph, we will use the cw-plus repository as an example. You can review:

Handling dependencies

When using workspaces, you need to add cw-orch as a dependency on all crates that include ExecuteMsg and QueryMsg used in your contracts. You then add the #[derive(ExecuteFns)] and #[derive(QueryFns)] macros to those messages.

Refer above to Adding cw-orch to your Cargo.toml file for more details on how to do that.

For instance, for the cw20_base contract, you need to execute those 2 steps on the cw20-base contract (where the QueryMsg are defined) as well as on the cw20 package (where the ExecuteMsg are defined).

Creating an interface crate

When using workspace, we advise you to create a new crate inside your workspace for defining your contract’s interfaces. In order to do that, use:

cargo new interface --lib
cargo add cw-orch --package interface 

Add the interface package to your workspace Cargo.toml file

[workspace]
members = ["packages/*", "contracts/*", "interface"]

Inside this interface crate, we advise to integrate all your contracts 1 by 1 in separate files. Here is the structure of the cw-plus integration for reference:

interface (interface collection)
├── Cargo.toml
└── src
    ├── cw1_subkeys.rs
    ├── cw1_whitelist.rs
    ├── cw20_base.rs
    ├── cw20_ics20.rs
    └── ..

When importing your crates to get the messages types, you can use the following command in the interface folder.

cargo add cw20-base --path ../contracts/cw20-base/
cargo add cw20 --path ../packages/cw20

Integrating single contracts

Now that you workspace is setup, you can integrate with single contracts using the above section

More examples and scripts

You can find more example interactions on the counter-contract example directly in the cw-orchestrator repo:

FINAL ADVICE: Continue to explore those docs to learn more about cw-orch. Why not go directly to environment variables?

Tutorial

This tutorial will guide you through creating a cw-orch interface for your contracts. By the end of this tutorial you should be able to:

  • Write deployment scripts for your contract.
  • Write integration tests for your contract.
  • Write executables for interacting with your contract.

In order to ensure that the code snippets shown here are correct we’ll be using the counter contract provided in the cw-orch Github repository as the source for our code-snippets. You can find the contract here.

Prerequisites

  • Contract Entry Point Messages: In order to use cw-orchestrator you need access to the entry point message types (InstantiateMsg,ExecuteMsg,…) of the contracts you want to interact with. Having them locally will enable you to generate helper functions for interacting with the contracts.

  • A gRPC endpoint (optional): If you want to perform on-chain transaction you will need access to the gRPC endpoint of a node. These are most-often available on port 9090. We provide chain definitions and constants for some of the more widely used Cosmos Chains. Learn more about this on the Daemon page.

  • A desire to learn (mandatory): This tutorial will cover the basics of using cw-orchestrator but it won’t cover everything. If you want to learn more about the features of cw-orchestrator you can check out the API Documentation.

Setup

Before being able to interact easily with your contracts, you need to add a few dependencies and files to your contract. Check out our dedicated setup tutorial for preparing the work !

Sections

The following sections detail setting up a contract, tests for the contract, and scripts for interacting with the contract on a blockchain network.

Interfaces

Interfaces are virtual wrappers around CosmWasm contracts. They allow you to interact with your contracts in a type-safe way, and provide a convenient way to reason about contract interactions. Interfaces are the core reason why we built cw-orchestrator and we hope that you’ll find them as useful as we do.

Reminder: You can find the code for this example in the cw-orch counter-contract folder.

If you are a fast or visual learner, you can find a Before-After view of the cw-orch integration process in the sample contract.

Creating an Interface

Now that we have our filesystem and crate setup, we are able to create our contract interface using the cw-orch::interface macro. It allows you to create an interface for your contract without having to call it at the entry points, as well as the ability to specify the contract’s source more easily.

use cw_orch::{interface, prelude::*};

use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};

pub const CONTRACT_ID: &str = "counter_contract";

#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)]
pub struct CounterContract;

impl<Chain> Uploadable for CounterContract<Chain> {
    /// Return the path to the wasm file corresponding to the contract
    fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
        artifacts_dir_from_workspace!()
            .find_wasm_path("counter_contract")
            .unwrap()
    }
    /// Returns a CosmWasm contract wrapper
    fn wrapper() -> Box<dyn MockContract<Empty>> {
        Box::new(
            ContractWrapper::new_with_empty(
                crate::contract::execute,
                crate::contract::instantiate,
                crate::contract::query,
            )
            .with_migrate(crate::contract::migrate),
        )
    }
}

The use of the interface macro even allows you to have generic arguments in the message types. Any generics will be added to the interface under a PhantomData attribute.

It can be beneficial to re-export the structure in our lib.rs file.

In the counter contract we re-export in lib.rs;

#[cfg(not(target_arch = "wasm32"))]
pub use crate::interface::CounterContract;

NOTE: You can see that we have used the artifacts_dir_from_workspace macro inside the wasm trait function. This macro helps you locate the workspace artifacts folder. It actually looks for any directory named artifacts from the root of the current crate going up. For instance if the project is located in /path1/path2/counter, it will look for the artifacts folder inside the following directories in order and return as soon as it finds such a folder:

  • /path1/path2/counter
  • /path1/path2
  • /path1/

This works for single contracts as well as workspace setups. If you have a specific setup, you can still specify the path yourself. If you do so, we advise indicating the wasm location from the current crate directory, using something like:

 let crate_path = env!("CARGO_MANIFEST_DIR");
 let wasm_path = format!("{}/../../artifacts/counter_contract.wasm", crate_path);
 WasmPath::new(wasm_path).unwrap()
 ```

Constructor

The interface macro implements a new function on the interface:

    // Construct the counter interface
    let contract = CounterContract::new(chain.clone());

The constructor takes one argument:

  • chain: The CosmWasm supported environment to use when calling the contract. Also includes the default sender information that will be used to call the contract. You can find more information later in the Integrations section for how to create this chain variable

NOTE: If you prefer working with different contract addresses for the same contract interface, you can remove the id argument in the interface macro:

#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)]
pub struct CounterContract;

The generated constructor will now take 2 arguments, the contract_id and the chain. This contract_id will allow you to specify which contract you want to interact with.

let contract = CounterContract::new("specific_counter_contract", chain.clone());

Interacting with your contracts

Now, you are able to interact directly with your contracts with ensured type safety.

The environments that are currently supported are:

  1. cw-multi-test by using Mock as the chain variable.
  2. Actual Cosmos SDK nodes for interacting with lives chains (mainnet, testnet, local). Use Daemon as the chain variable.
  3. osmosis-test-tube or testing against actual chain binaries. This allows for fast testing with actual on-chain modules. This is particularly useful when testing against chain-specific modules. Use OsmosisTestTube as the chain variable.

Generic functions

Generic functions can be executed over any environment. Setup functions are a good example of this.

/// Instantiate the contract in any CosmWasm environment
fn setup<Chain: CwEnv>(chain: Chain) -> anyhow::Result<CounterContract<Chain>> {
    // Construct the counter interface
    let contract = CounterContract::new(chain.clone());
    let admin = Addr::unchecked(ADMIN);

    // Upload the contract
    let upload_resp = contract.upload()?;

    // Get the code-id from the response.
    let code_id = upload_resp.uploaded_code_id()?;
    // or get it from the interface.
    assert_eq!(code_id, contract.code_id()?);

    // Instantiate the contract
    let msg = InstantiateMsg { count: 1i32 };
    let init_resp = contract.instantiate(&msg, Some(&admin), &[])?;

    // Get the address from the response
    let contract_addr = init_resp.instantiated_contract_address()?;
    // or get it from the interface.
    assert_eq!(contract_addr, contract.address()?);

    // Return the interface
    Ok(contract)
}

Entry point function generation

Tired of having to use endless schemas? Tired of having to redeclare your field names every time you want to declare an struct?
{
    "swap": {
        "offer_asset": {
            "native":{
                "denom":"ujuno"
            }
        },
        "ask_asset": {
            "native":{
                "denom":"uluna"
            }
        },
        "amount": "3465"
    }
}
dex::core::swap::ExecuteMsg::Swap{
    offer_asset: CwAsset::Native("ujuno"),
    ask_asset: CwAsset::Native("uluna"),
    amount: 3465u128.into()
}

Learn more in the next section about entry-point functions how to do just that!

Learn more

Got questions? Join the Abstract Discord and ask in the #cw-orchestrator channel. Learn more about Abstract at abstract.money.

References

Entry Point Function Generation

Contract execution and querying is so common that we felt the need to improve the method of calling them. To do this we created two macros: ExecuteFns and QueryFns. As their name implies they can be used to automatically generate functions for executing and querying your contract through the interface.

Execution

To get started, find the ExecuteMsg definition for your contract. In our case it’s located in counter/src/msg.rs. Then add the following line to your ExecuteMsg enum:

#[cw_serde]
#[derive(cw_orch::ExecuteFns)] // Function generation
/// Execute methods for counter
pub enum ExecuteMsg {
    /// Increment count by one
    Increment {},
    /// Reset count
    Reset {
        /// Count value after reset
        count: i32,
    },
}

The functions are implemented as a trait named ExecuteMsgFns which is implemented on any interface that uses this ExecuteMsg as an entrypoint message.

Using the trait then becomes as simple as:

    // in integration_tests.rs
    // Reset
    use counter_contract::CounterExecuteMsgFns;
    contract.reset(0)?;

Query

Generating query functions is a similar process but has the added advantage of using the cosmwasm-schema return tags to detect the query’s return type. This allows for type-safe query functions!

#[cw_serde]
#[derive(cw_orch::QueryFns)] // Function generation
#[derive(QueryResponses)]
/// Query methods for counter
pub enum QueryMsg {
    /// GetCount returns the current count as a json-encoded number
    #[returns(GetCountResponse)]
    GetCount {},
}

// Custom response for the query
#[cw_serde]
/// Response from get_count query
pub struct GetCountResponse {
    /// Current count in the state
    pub count: i32,
}

Keep in mind that you NEED to derive the cosmwasm_schema::QueryResponses trait on your QueryMsgs for the QueryFns macro to compile.

Using it is just as simple as the execution functions:

    // in integration_tests.rs
    // Get the count.
    use counter_contract::CounterQueryMsgFns;
    let count1 = contract.get_count()?;

    // or query it manually
    let count2: GetCountResponse = contract.query(&QueryMsg::GetCount {})?;
    assert_eq!(count1.count, count2.count);

Just like the interface it can be beneficial to re-export the trait in your lib.rs or interface.rs file.

In the counter contract we re-export in lib.rs;

pub use crate::msg::{
    AsyncQueryMsgFns as AsyncCounterQueryMsgFns, ExecuteMsgFns as CounterExecuteMsgFns,
    QueryMsgFns as CounterQueryMsgFns,
};

Async functions

In the case of queries, async functions get generated by the derive macro as well. These have the same arguments and return the same type as their synchronous counterparts, but are asynchronous and are suffixed with _async:

use counter_contract::AsyncCounterQueryMsgFns;
use counter_contract::CounterContract;
use cw_orch::{anyhow, prelude::*, tokio};

// From https://github.com/CosmosContracts/juno/blob/32568dba828ff7783aea8cb5bb4b8b5832888255/docker/test-user.env#L2
const LOCAL_MNEMONIC: &str = "clip hire initial neck maid actor venue client foam budget lock catalog sweet steak waste crater broccoli pipe steak sister coyote moment obvious choose";

#[tokio::main]
pub async fn main() -> anyhow::Result<()> {
    std::env::set_var("LOCAL_MNEMONIC", LOCAL_MNEMONIC);
    dotenv::dotenv().ok(); // Used to load the `.env` file if any
    pretty_env_logger::init(); // Used to log contract and chain interactions

    let network = networks::LOCAL_JUNO;
    let chain = DaemonAsyncBuilder::new(network).build().await?;

    let counter = CounterContract::new(chain);

    let count = counter.get_count_async().await?;
    assert_eq!(count.count, 1);

    Ok(())
}

Additional Remarks on QueryFns and ExecuteFns

The QueryFns and ExecuteFns derive macros generate traits that are implemented on any Contract structure (defined by the interface macro) that have the matching execute and query types. Because of the nature of rust traits, you need to import the traits in your application to use the simplifying syntax. Those traits are named ExecuteMsgFns and QueryMsgFns.

Any variant of the ExecuteMsg and QueryMsg that has a #[derive(ExecuteFns)] or #[derive(QueryFns)] will have a function implemented on the interface (e.g. CounterContract) through a trait. Here are the main things you need to know about the behavior of those macros:

  • The function created will have the snake_case name of the variant and will take the same arguments as the variant.
  • The arguments are ordered in alphabetical order to prevent attribute ordering from changing the function signature.
  • If coins need to be sent along with the message you can add #[cw_orch(payable)] to the variant and the function will take a Vec<Coin> as the last argument.
  • The cw_orch::QueryFns macro needs your QueryMsg struct to have the cosmwasm_schema::QueryResponses macro implemented (this is good practice even outside of use with cw-orch).

Additional configuration

payable Attribute

Let’s see an example for executing a message (from a money market for instance).

    money_market.deposit_stable()?;

There’s a problem with the above function. The money market only knows how much you deposit into it by looking at the funds you send along with the transaction. Cw-orchestrator doesn’t ask for funds by default. However, to allow attaching funds to a transaction, you can add the #[cw_orch(payable)] attribute on your enum variant like so:

    #[derive(ExecuteFns)]
    enum ExecuteMsg{
        UpdateConfig{
            config_field: String
        },
        #[cw_orch(payable)]
        DepositStable{}
        ...
    }

Be defining this attribute, you can now use:

    use cosmwasm_std::coins;
    money_market.deposit_stable(&coins(456, "ujunox"))?;

fn_name Attribute

#[derive(cw_orch::ExecuteFns)] 
pub enum ExecuteMsg{
    Execute{
        msg: CosmoMsg
    }
}

The following command will error because the execute function is reserved for contract execution. This will not even compile actually.

// Doesn't compile
money_market.execute(message_to_execute_via_a_proxy)?;

This can happen in numerous cases actually, when using reserved keywords of cw-orch (or even rust). If this happens, you can use the fn_name attribute to rename a generated function.

#[derive(cw_orch::ExecuteFns)] 
pub enum ExecuteMsg{
    #[cw_orch(fn_name("proxy_execute"))]
    Execute{
        msg: CosmoMsg
    }
}
// This works smoothly !
money_market.proxy_execute(message_to_execute_via_a_proxy)?;

This is also true for query functions.

Nested Messages

For nested messages (execute and query), you need to do 2 things:

  • Derive ExecuteFns or QueryFns on the underlying structures
  • Implement From<Underlying> for your contract message type

In general, every structure that implements the Into trait for the contract message will make the function available on the contract. To make that clearer, here’s an example:

use cw_orch::interface;
use cw_orch::prelude::*;

// An execute message that is generic.
#[cosmwasm_schema::cw_serde]
pub enum GenericExecuteMsg<T> {
    Generic(T),
    Nested(NestedMessageType),
}

// This is the message that will be used on our contract
type ExecuteMsg = GenericExecuteMsg<Foo>;
#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum Foo {
    Bar { a: String },
}

impl From<Foo> for ExecuteMsg {
    fn from(msg: Foo) -> Self {
        ExecuteMsg::Generic(msg)
    }
}

#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum NestedMessageType {
    Test { b: u64 },
}

impl From<NestedMessageType> for ExecuteMsg {
    fn from(msg: NestedMessageType) -> Self {
        ExecuteMsg::Nested(msg)
    }
}

#[interface(Empty, ExecuteMsg, Empty, Empty)]
struct Example<Chain>;

impl<Chain: CwEnv> Example<Chain> {
    pub fn test_macro(&self) {
        // function `bar` is available now!
        self.bar("hello".to_string()).unwrap();

        // function `test` is available now!
        self.test(65u64).unwrap();
    }
}

Into

String and Uint* (e.g. Uint128) types will have relaxed trait bounds on the argument that the generated function receives. This allows for easier handling of the cw-orch generated functions. For other types, you can add the #[cw_orch(into)] attribute on a field to also accept structures that implement Into the field type. For instance:

pub enum ExecuteMsg<T>{
    #[cw_orch(payable)]
    SecondMessage {
        /// test doc-comment
        #[cw_orch(into)]
        t: T,
    },
}

// Will produce:
fn second_message<T>(&self, t: impl Into<T>, funds: &[Coin]){}

disable_fields_sorting Attribute

By default the ExecuteFns and QueryFns derived traits will sort the fields of each enum member. For instance,

#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum ExecuteMsgOrdered {
    Test { b: u64, a: String },
}

will generate

pub fn bar(a: String, b: u64) -> ...{
   ...
} 

You see in this example that the fields of the bar function are sorted lexicographically. We decided to put this behavior as default to prevent potential errors when rearranging the order of enum fields. If you don’t want this behavior, you can disable it by using the disable_fields_sorting attribute. This is the resulting behavior:

#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
#[cw_orch(disable_fields_sorting)]
pub enum ExecuteMsg {
    Test { b: u64, a: String },
}

 
 pub fn bar(b: u64, a: String) -> ...{
    ...
 } 

NOTE: This behavior CAN be dangerous if your struct members have the same type. In that case, if you want to rearrange the order of the members inside the struct definition, you will have to be careful that you respect the orders in which you want to pass them.

Wasm Compilation

cw-orch was designed to help you test, deploy, script and maintain your application. None of its features include in-contract operations. cw-orch interfaces, macros and helpers can’t be used inside a wasm smart contract.

In case you use one of the cw-orch features inside your smart-contract, the compilation will send an error anyway, so that we are SURE you are not using un-intended features inside your contract.

Importing cw-orch

Importing cw-orch as a dependency in your smart contract is not a problem. We have built this library such that every feature that cw-orch imports, uses or exports is not included when compiled for Wasm. This means that you are safe to use any cw-orch feature when developing your application, creating interfaces, exporting features, because none of it will land in your contract.

In order to make sure you don’t encounter wasm compilation errors, you should follow the guidelines outlined in the next section.

Guidelines

Imports

Import cw-orch without a worry, this won’t include unnecessary dependencies and bloat your smart-contract. Be careful of the features you import cw-orch with because they might increase your compile time (especially daemon and osmosis-test-tube).

Interface

The interface macro itself compiles to Wasm to empty traits. So this macro can be used anywhere in your contract. This IS smart-contract safe:

#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)]
pub struct CounterContract;

However, the Uploadable traits implementation IS NOT safe for smart contracts and needs to import namely cw-multi-test elements that we don’t remove from WASM compilation. The following code needs to be flagged to not be compiled inside Wasm contracts:

#[cfg(not(target_arch = "wasm32"))]
impl<Chain> Uploadable for CounterContract<Chain> {
    /// Return the path to the wasm file corresponding to the contract
    fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
        artifacts_dir_from_workspace!()
            .find_wasm_path("counter_contract")
            .unwrap()
    }
    /// Returns a CosmWasm contract wrapper
    fn wrapper() -> Box<dyn MockContract<Empty>> {
        Box::new(
            ContractWrapper::new_with_empty(
                crate::contract::execute,
                crate::contract::instantiate,
                crate::contract::query,
            )
            .with_migrate(crate::contract::migrate),
        )
    }
}

Entry Points

The entry points are easy to work with as they compile to empty traits inside Wasm. So you can define them, import and export them in your contract without having to care about compilation targets. Furthermore, those traits are optimized out when getting your contract ready to upload on a chain. The syntax use in the 2 following examples is WASM safe:

#[cw_serde]
#[derive(cw_orch::ExecuteFns)] // Function generation
/// Execute methods for counter
pub enum ExecuteMsg {
    /// Increment count by one
    Increment {},
    /// Reset count
    Reset {
        /// Count value after reset
        count: i32,
    },
}
pub use crate::msg::{
    AsyncQueryMsgFns as AsyncCounterQueryMsgFns, ExecuteMsgFns as CounterExecuteMsgFns,
    QueryMsgFns as CounterQueryMsgFns,
};

Integration Tests

Integration tests are very easy to write with cw-orch and are 100% compatible with actual on-chain deployment and scripting. We provide an overview of how they can be executed here. Find out more about how to setup your integration tests on the Cw Multi Test page

Start by creating a tests folder in your contract’s dir.

mkdir counter/tests

Then create a file called integration_tests.rs in the tests folder.

touch counter/tests/integration_tests.rs

Now we can write our tests. Here’s an example of a test that deploys the contract, increments the counter and then resets it.

use counter_contract::{
    msg::{GetCountResponse, InstantiateMsg, QueryMsg},
    ContractError, CounterContract,
};
// Use prelude to get all the necessary imports
use cw_orch::prelude::*;

use cosmwasm_std::Addr;

// consts for testing
const USER: &str = "user";
const ADMIN: &str = "admin";

#[test]
fn count() -> anyhow::Result<()> {
    // Create a user
    let user = Addr::unchecked(USER);
    // Create the mock. This will be our chain object throughout
    let mock = Mock::new(ADMIN);

    // Set up the contract (Definition below) ↓↓
    let contract = setup(mock.clone())?;

    // Increment the count of the contract
    contract
        // Set the caller to user
        .call_as(&user)
        // Call the increment function (auto-generated function provided by CounterExecuteMsgFns)
        .increment()?;

    // Get the count.
    use counter_contract::CounterQueryMsgFns;
    let count1 = contract.get_count()?;

    // or query it manually
    let count2: GetCountResponse = contract.query(&QueryMsg::GetCount {})?;
    assert_eq!(count1.count, count2.count);

    // Or get it manually from the chain
    let count3: GetCountResponse = mock.query(&QueryMsg::GetCount {}, &contract.address()?)?;
    assert_eq!(count1.count, count3.count);

    // Check the count
    assert_eq!(count1.count, 2);
    // Reset
    use counter_contract::CounterExecuteMsgFns;
    contract.reset(0)?;

    let count = contract.get_count()?;
    assert_eq!(count.count, 0);

    // Check negative case
    let exec_res: Result<cw_orch::mock::cw_multi_test::AppResponse, CwOrchError> =
        contract.call_as(&user).reset(0);

    let expected_err = ContractError::Unauthorized {};
    assert_eq!(
        exec_res.unwrap_err().downcast::<ContractError>()?,
        expected_err
    );

    Ok(())
}

/// Instantiate the contract in any CosmWasm environment
fn setup<Chain: CwEnv>(chain: Chain) -> anyhow::Result<CounterContract<Chain>> {
    // Construct the counter interface
    let contract = CounterContract::new(chain.clone());
    let admin = Addr::unchecked(ADMIN);

    // Upload the contract
    let upload_resp = contract.upload()?;

    // Get the code-id from the response.
    let code_id = upload_resp.uploaded_code_id()?;
    // or get it from the interface.
    assert_eq!(code_id, contract.code_id()?);

    // Instantiate the contract
    let msg = InstantiateMsg { count: 1i32 };
    let init_resp = contract.instantiate(&msg, Some(&admin), &[])?;

    // Get the address from the response
    let contract_addr = init_resp.instantiated_contract_address()?;
    // or get it from the interface.
    assert_eq!(contract_addr, contract.address()?);

    // Return the interface
    Ok(contract)
}

Environment Variables

cw-orch leverages some environment variables to interact with contracts on actual blockchains. The environment variables are described here. You can find additional information about their usage, default values and types in the cw-orch repo.

IMPORTANT: Before proceeding, ensure that you add .env to your .gitignore. We are not responsible for any loss of funds due to leaked mnemonics.

Mnemonics

You can provide mnemonics directly to the Daemon using the following environment variables. Those variables will be used if the mnemonic setter is not used. See the Daemon page for more detailed information on mnemonic usage.

  • MAIN_MNEMONIC will be used when working with a Mainnet (PHOENIX_1, OSMOSIS_1…)
  • TEST_MNEMONIC will be used when working with a Testnet (PISCO_1, UNI_6…)
  • LOCAL_MNEMONIC will be used when working locally (LOCAL_JUNO…)

Only 24-word mnemonics are supported at this time. If you’re experienced with keychain and private key management we’d really appreciate your help in adding support for other formats. Please reach out to us on Discord if you’re interested in helping out.

Saving and Loading State

STATE_FILE

Optional, accepted values: Path to a valid file Default value: ~./cw-orchestrator/state.json

This environment variable indicates the location of the state file that cw-orch will use to save on-chain state (addresses and code ids). Here is the behavior of this env variable:

  • folder/file.json will resolve to ~/.cw-orchestrator/folder/file.json
  • ./folder/file.json will resolve $pwd/folder/file.json
  • ../folder/file.json will resolve $pwd/../folder/file.json
  • /usr/var/file.json will resolve to /usr/var/file.json

ARTIFACTS_DIR

Optional, accepted values: Path to a valid directory

Path where the wasms will be fetched. This is used by ArtifactsDir::env() inside the code. This is used in case you are using different tools than what is produced by rust-optimizer. Prefer using the following macro if you are using wasms produced by rust-optimizer:

artifacts_dir_from_workspace!()
    .find_wasm_path("contract_name")
    .unwrap()

Transaction options

CW_ORCH_GAS_BUFFER

Optional, accepted values: float

This allows changing the gas buffer applied after tx simulation. Use this in case a transaction is blocked for insufficient gas reasons.

CW_ORCH_MIN_GAS

Optional, accepted values: integer

Minimum gas amount for every transaction. Useful when transaction still won’t pass even when setting a high gas_buffer or for mixed transaction scripts.

CW_ORCH_MAX_TX_QUERY_RETRIES

Optional, accepted values: integer Defaults to 50.

Changes the number of tx queries (~1 query per block) before it fails if it doesn’t find any result. Useful if the chain is slow or if the transaction has low gas price.

CW_ORCH_MIN_BLOCK_TIME

Optional, accepted values:

  • (integer)ms (e.g. 57ms), to indicate the min block time in milliseconds
  • (integer)s (e.g. 57s), to indicate the min block time in seconds
  • (integer) (e.g. 57), to indicate the min block time in seconds

Defaults to 1s.

Minimum block time. This is used internally by cw-orch when broadcasting transactions. Useful when the block time are varying a lot.

CW_ORCH_MAX_BLOCK_TIME

Optional, accepted values:

  • (integer)ms (e.g. 57ms), to indicate the max block time in milliseconds
  • (integer)s (e.g. 57s), to indicate the max block time in seconds
  • (integer) (e.g. 57), to indicate the max block time in seconds

Maximum block time. This is used internally by cw-orch when broadcasting transactions. Useful when the block time are varying a lot.

CW_ORCH_WALLET_BALANCE_ASSERTION

Optional, accepted values: true, false Defaults to true

By default, cw-orch verifies that the account has sufficient balance to pay for gas fees. If it detects that the balance is too low, it prompts the user to fill up their wallet with gas tokens and allows for retrying at the press of a button.

If set to false, it won’t check the user has enough balance before broadcasting transactions.

CW_ORCH_MANUAL_INTERACTION

Optional, accepted values: true, false Defaults to true

Some actions require user-intervention. If set to false, this disables those interventions. Useful when working in scripts that can’t handle user-intervention.

Supported actions:

  • Balance checks. When set to false, if the gas token balance is too low to submit a transaction, it will error.
  • Deployment checks. When set to false, if no deployment file is detected when deploying a structure using the Deploy::multi_deploy function, it will deploy to all provided chains without asking for approval.

Logging

RUST_LOG

Optional, accepted values: debug, error, info, warn, trace

RUST_LOG defines the Log level for the application. This is actually a Rust flag that we use inside cw-orch. If working with this environment variable, don’t forget to also initialize a logger at the beginning of your application to be able to see the output. You can work with pretty_env_logger for instance.

CW_ORCH_SERIALIZE_JSON

Optional, accepted values: false, true Defaults to false

If equals to true, in the output logs, cw-orch will serialize the contract messages (instantiate, execute, query,… ) as JSON. This replaces the standard Rust Debug formatting and allows for easy copying and sharing of the executed messages.

CW_ORCH_LOGS_ACTIVATION_MESSAGE

Optional, accepted values: false, true

Defaults to true

By default if the logs are not enabled, cw-orch will print a warning message to invite users to activate the logging capabilities of cw-orch. if equals to false, the warning message is disabled.

Writing and Executing Scripts

Now that we have the interface written for our contract, we can start writing scripts to deploy and interact with it on a real blockchain. We’ll do this by adding a examples folder in the counter contract and add our deploy script there. We only provide an overview of how scripting can be done here. Find our more about how to make your scripting dreams come true on the Daemon page.

Setup

Before we get going we need to add the examples folder and tell cargo that it contains scripts. We can do this by creating a folder named examples in counter and creating a file in it called deploy.rs

mkdir counter/examples
touch counter/examples/deploy.rs

Then we want to add the required dependencies to the dev-dependencies section of our Cargo.toml file. We’ll need dotenv to load our environment variables and pretty_env_logger to log to stdout.

[dev-dependencies]
# Deps for deployment
dotenv = { version = "0.15.0" } # Enables loading of .env files
pretty_env_logger = { version = "0.5.0" } # Enables logging to stdout and prettifies it

Now we’re ready to start writing our script.

Main Function

With the setup done, we can start writing our script. Our initial plan is to deploy the counter contract to the chain. We’ll start by writing a main function that will call our deploy function.

We start by creating the chain object for specifying we want to interact with a local juno instance:

    dotenv::dotenv().ok(); // Used to load the `.env` file if any
    pretty_env_logger::init(); // Used to log contract and chain interactions

    let network = networks::LOCAL_JUNO;
    let chain = DaemonBuilder::new(network).build()?;

Then we can interact with our contract using that chain

    let counter = CounterContract::new(chain);

    counter.upload()?;
    counter.instantiate(&InstantiateMsg { count: 0 }, None, &[])?;

    counter.increment()?;

    let count = counter.get_count()?;
    assert_eq!(count.count, 1);

Asynchronous Daemon

All the functionalities described in this guide/tutorial only allow for synchronous interactions. If for whatever reason you need to interact with an actual chain in an asynchronous way, you can use the DaemonAsync structure. However, this structure won’t allow for much interoperability as it’s not compatible with the cw-orch Contract structure. Blockchain transactions have to be sequential because of the sequence of an account and that’s why we provide limited support to asynchronous transaction broadcasting capabilities.

Setup

In this tutorial, you will find information on how to setup your working directory for cw-orchestrator integration. Because of the nature of smart contracts working directories, we have 2 separate tutorials depending on your setup:

Single Contract Crate Setup

You can find the code for this example in the cw-orch counter-contract folder.

If you are a fast or visual learner, you can find a Before-After view of the cw-orch integration process in the sample contract.

Dependency

Before we can create an interface we need to add cw-orch to the contract’s Cargo.toml file. In counter run:

$ cargo add cw-orch
> Adding cw-orch v0.16.0 to dependencies.

or add it manually to the counter/Cargo.toml file:

[dependencies]
cw-orch = {version = "0.21.2" } # Latest version at time of writing

Even if you include cw-orch in your dependencies here, it won’t be included in your wasm contract. Learn more about this behavior in the section about Wasm Compilation.

Crate Structure

Now that we have our dependency set up, we can create the files that will contain our interface. We will create that interface in an interface.rs file inside the crate (counter/src/interface.rs). Then, we need to include that file inside our project. However, that file will contain multiple cw-orch elements that are not exposed when compiling your WASM. In order to prevent errors when compiling for Wasm, we will import our interface.rs file and target-flag it like so, in counter/src/lib.rs:

#[cfg(not(target_arch = "wasm32"))]
mod interface;

In the next section, we will learn how to define the interface to be able to interact with an actual contract inside this interface.rs file.

Final structure

Following the steps above, the directory structure should eventually look like this:

.
├── Cargo.toml
├── artifacts
│   └── counter_contract.wasm (binary file)
└── counter
    ├── Cargo.toml
    ├── bin
    │   └── deploy.rs
    └── src
        ├── contract.rs (execute, instantiate, query, ...)
        ├── msg.rs (ExecuteMsg, QueryMsg, ...)
        ├── interface.rs (`cw-orch` contract structure definition)
        └── ..

NOTE:

  • The artifacts folder is generated by the rust-optimizer, and contains all your compiled contracts

  • Place your scripts inside the bin directory. Those can be run using the following command:

    cargo run --bin deploy
    

Now that the setup is complete, you can go back to the Contracts page to create the interface for your contract and start scripting/testing your contracts withe ease.

Workspace Tutorial

In this tutorial, you will learn how to setup cw-orchestrator inside a workspace project. We present here the best practices for setting up an application composed of multiple contracts with the cw-orch crate. If you want a short version on how to integrate your contracts, we advise running to our Quick Start guide.

NOTE: A rust workspace is a simple way to have multiple contracts inside the same repository and are especially suited for applications in which multiple contracts communicate with each other.

This tutorial has multiple components:

  • Project Setup
    • This tutorial helps you setup your project to have a rational workspace structure and all the dependencies needed for interacting with your contracts.
  • Project Wrapper
    • This tutorial allows you to go further and simplify the way you interact with all the interfaces you created included in your project. It presents best practices and usual project structure to keep your project organized.
  • Collaborating
    • This tutorial shows you how you can distribute your contract code, you deployment variables (code_ids, addresses…) for other projects to integrate with. This is the ultimate way to collaborate with other projects.

Workspace cw-orch setup process

In all this tutorial, we will use the cw-plus package as an example. This repository is maintained by CosmWasm and contains standard contracts. It’s also a good workspace structure example.

Crate setup

If you are using multiple contracts in a single project, we advise you to centralize all the interfaces to your contracts in a single location. To do so, go inside your packages folder and create a new interface library crate:

cargo new interface --lib
cd interface

Then, going into this crate, we need to add cw-orch as a dependency:

cargo add cw-orch --package interface 

Individual contract interface

Now inside this crate, you can add the interfaces to all your contracts individually. A sane way to do so is to create a new file (also called module) for each contract. For instance, the interface folder in the cw-plus repository has the following structure:

.
├── Cargo.toml
└── src
    ├── cw1_subkeys.rs
    ├── cw1_whitelist.rs
    ├── cw20_base.rs
    ├── cw20_ics20.rs
    ├── cw3_fixed_multisig.rs
    ├── cw3_flex_multisig.rs
    ├── cw4_group.rs
    ├── cw4_stake.rs
    └── lib.rs

Inside each file, you can define your contract interface. You can find the tutorial for that in the dedicated Interfaces page.

Scripts

We advise using a scripts crate at the root of your workspace. This will help you centralize your scripts and have them easily accessible for eventual other users accessing your crate remotely. This crate should be a binary crate:

cargo new --bin scripts

Final project structure

Following the steps above, the directory structure should eventually look like this:

.
├── Cargo.toml
├── artifacts
│   ├── other_contract.wasm
│   └── my_contract.wasm
├── contracts
│   ├── my-contract
│   │   ├── Cargo.toml
│   │   └── src
│   │       ├── contract.rs (execute, instantiate, query, ...)
│   │       └── ..
│   └── other-contract
│       └── ..
├── packages
│   ├── my-project (messages)
│   │   ├── Cargo.toml
│   │   └── src
│   │       ├── lib.rs
│   │       ├── my-contract.rs
│   │       ├── other-contract.rs
│   │       └── ..
│   └── my-project-interface (interface collection, renamed here)
│       ├── Cargo.toml
│       └── src
│           ├── lib.rs
│           ├── my-contract.rs
│           ├── other-contract.rs
│           └── ..
└── scripts (executables)
    ├── .env
    ├── Cargo.toml
    └── src
        ├── deploy.rs
        └── test_project.rs

NOTE:

  • The artifacts folder is generated by the workspace-optimizer, and contains all your compiled contracts
  • We advise you to place your scripts inside the scripts crate to centralize them and facilitate retrieving them:

Now that the setup is complete, you can go back to the Contracts page to create the interfaces for your contracts one by one and start scripting/testing your contracts with ease.

You can also continue this tutorial and discover how to:

Interface Collection

Follow the same process as for a single contract to generate the interfaces for your contract.

Now that you have your contract interfaces you can export a comprehensive wrapper of your application that can easily be used by others.

The idea is simple. If you can provide an easy way for others to deploy your contracts/application to their environments, then you’re making it extremely easy for them to use and build on your application.

As an example we will create a deployment for the Abstract smart-contract framework.

The deployment

The deployment can be represented by a struct containing all the contracts that are uploaded and instantiated when the protocol is deployed.

// Our Abstract deployment
pub struct Abstract<Chain> {
    pub ans_host: AnsHost<Chain>,
    pub version_control: VersionControl<Chain>,
}

Implementing Deploy

Now we can implement the cw_orch::Deploy trait for the Abstract struct.

use cw_orch::prelude::*;
use cw_orch::error::CwOrchError;
use cw_multi_test::ContractWrapper;
use abstract_interface::{AnsHost, VersionControl};
pub struct Abstract<Chain: CwEnv> {
  pub ans_host: AnsHost<Chain>,
  pub version_control: VersionControl<Chain>,
}

impl<Chain: CwEnv> cw_orch::Deploy<Chain> for Abstract<Chain> {
    // We don't have a custom error type
    type Error = CwOrchError;
    type DeployData = semver::Version;

    fn store_on(chain: Chain) -> Result<Self, Self::Error> {
        // "abstract" is a reserved keyword in rust!
        let mut abstrct = Abstract::new(chain);

        // Upload the contracts to the chain
        abstrct.ans_host.upload()?;
        abstrct.version_control.upload()?;

        Ok(abstrct)
    }

    fn deploy_on(chain: Chain, version: semver::Version) -> Result<Self, CwOrchError> {        
        // ########### Upload ##############
        let abstrct = Self::store_on(chain)?;

        // ########### Instantiate ##############
        abstrct.ans_host.instantiate(
            &abstract_core::ans_host::InstantiateMsg {},
            Some(sender),
            None,
        )?;

        abstrct.version_control.instantiate(
            &abstract_core::version_control::InstantiateMsg {},
            Some(sender),
            None,
        )?;

        // ... 

        Ok(abstrct)
    }

    fn load_from(chain: Chain) -> Result<Self, Self::Error> {
        let abstrct = Self::new(chain);
        Ok(abstrct)
    }
}

Now Abstract is an application that can be deployed to a mock and real environment with one line of code.

fn setup_test(mock: Mock) -> Result<(), CwOrchError> {
    let version = "1.0.0".parse().unwrap();
    // Deploy abstract
    let abstract_ = Abstract::deploy_on(mock.clone(), version)?;
}

And then when setting up your own deployment you can load these applications to access their contracts (for accessing configuration, addresses, …)

impl<Chain: CwEnv> cw_orch::Deploy<Chain> for MyApplication<Chain> {
    /// ...
    fn deploy_on(chain: Chain, _data: Empty) -> Result<Self, CwOrchError> {

        let abstract_: Abstract = Abstract::load_from(chain)?;

        /// ... do stuff with Abstract
    }
}

Collaboration

The structure you defined in the Deploy Wrapper section is very useful for testing and scripting with your platform. However, in most cases you also want other developers and smart-contracts builders to be able to test their application against your application logic. This can be done by publishing your deployment structures to crates.io

We’ll continue with the Abstract platform as an example. This platform can be used by developers to create smart contacts enhancing the wallet experience of their users. They create applications (apps) on top of the framework and need the framework to be present on their test chain to be able to test or script against it.

On actual blockchains (Mainnets and Testnets), users won’t redeploy the full Abstract platform. Instead, we added the Abstract addresses and code_ids alongside the abstract interface so that users can access those contracts without having to specify all those variables by hand (or find them in some documentation scattered across the web).

This type of information can also be useful for users working with Dexes (which have pool, factory, router addresses).

We’ll present in this guide how projects can provide this kind of information to their users directly through Rust dependencies.

State File

In order for your users to get access to the state of your application (addresses and cods_ids), you need to implement the deployed_state_file method on the Deploy trait:

fn deployed_state_file_path(&self) -> Option<String> {
    let crate_path = env!("CARGO_MANIFEST_DIR");

    Some(
        PathBuf::from(crate_path)
            // State file of your deployment
            .join("state.json")
            .display()
            .to_string(),
    )
}

In this function, you indicate where the deployment file is located. To be able to have those addresses accessible to users importing the crate, we advise you need to specify the path using the crate_path variable that is automatically set by Cargo.

NOTE: At Abstract for instance, we have a scripts crate which is different from the interface crate. In order to keep up with the deployment file location, we have symbolic link from interface/state.json to scripts/state.json. This symbolic link is turned automatically into an actual file when it’s published the crate to crates.io.

Check out the Abstract setup here.

Then, in the load_from function, we advise using the following implementation:

    fn load_from(chain: Chain) -> Result<Self, Self::Error> {
        let mut abstr = Self::new(chain);
        // We set all the contract's default state (addresses, code_ids)
        abstr.set_contracts_state();
        Ok(abstr)
    }

where Self::new simply creates the contract structures. You don’t need to reimplement the set_contracts_state function. It will use the state indicated in Self::deployed_state_file_path as long as contracts are not re-uploaded / instantiated.

You can customize the Deploy::deployed_state_file_path and Deploy::load_from methods, be we recommend doing something similar to what we show above to avoid mistakes and errors.

For visual learners, the workspace looks something like this:

.
├── artifacts
├── contracts
│   ├── contract1
│   │   └── src
│   │       ├── contract.rs
│   │       └── ...
│   └── contract2
│       └── src
│           ├── contract.rs
│           └── ...
├── packages
|   └── interface 
|       ├── src
│       │   ├── deploy.rs   // <-- Definition of the deploy struct and implementation of the Deploy trait. 
|       │   |               // <--   Leverages contract1 and contract2 structures
│       │   └── ...
│       └── state.json      // <-- Usually a symlink to the state.json file you use for deployment (by default in ~/.cw-orchestrator)
├── scripts
|   └── src
|       └── bin             // <-- Your deployment script can be located here
└── .env                    // <-- Place your .env file at the root of your workspace

Integrations

cw-orchestrator aims at allowing developers to interact with different execution environments with the exact same code base and syntax.

In order to do so, it provides a set of traits and interfaces, e.g. for contracts that can be implemented on execution structures. As of writing this documentation, the following execution environments are supported:

Those environments allow developers to test, deploy, manage, migrate, maintain, without having to switch programming languages, care for types and version compatibilities as they are enforced by the compiler and even change the syntax of the code used.

The Unit Tests page shows you how you can unit-test your contracts against actual on-chain data!

Daemon

Daemon is a CosmWasm execution environment for interacting with CosmosSDK chains. The Daemon allows you to deploy/migrate/configure your contracts on main and testnets as well as locally running chain instances. Furthermore it provides a wide range of tools to interact with the chains. We describe those tools in depth in this page.

Quick Start

Before starting, here are a few examples utilizing the daemon structure:

Interacting with the daemon is really straightforward. Creating a daemon instance is shown below:

    use cw_orch::prelude::*;

    // We start by creating a daemon. This daemon will be used to interact with the chain.
    let daemon = Daemon::builder(cw_orch::daemon::networks::LOCAL_JUNO) // chain parameter
        .build()
        .unwrap();
  • The chain parameter allows you to specify which chain you want to interact with. The chains that are officially supported can be found in the cw_orch::daemon::networks module. You can also add additional chains yourself by simply defining a variable of type ChainInfo and using it in your script. Don’t hesitate to open a PR on the cw-orchestrator repo, if you would like us to include a chain by default. The variables needed for creating the variable can be found in the documentation of the chain you want to connect to or in the Cosmos Directory.

This simple script actually hides another parameter which is the LOCAL_MNEMONIC environment variable. This variable is used when interacting with local chains. See the part dedicated to Environment Vars for more details.

NOTE: When using daemon, you are interacting directly with a live chain. The program won’t ask you for your permission at each step of the script. We advise you to test ALL your deployments on test chain before deploying to mainnet.

Under the hood, the DaemonBuilder struct creates a tokio::Runtime. Be careful because this builder is not usable in an async function. In such function, you can use DaemonAsync

When using multiple Daemons with the same state file, you should re-use a single Daemon State to avoid conflicts and panics:

let daemon1 = Daemon::builder(OSMOSIS_1).build()?;
// If you don't use the `state` method here, this will fail with:
// State file <file-name> already locked, use another state file, clone daemon which holds the lock, or use `state` method of Builder
let daemon2 = Daemon::builder(JUNO_1)
  .state(daemon1.state())
  .build()?;

Interacting with contracts

You can then use the resulting Daemon variable to interact with your contracts:

    let counter = CounterContract::new(daemon.clone());

    let upload_res = counter.upload();
    assert!(upload_res.is_ok());

    let init_res = counter.instantiate(
        &InstantiateMsg { count: 0 },
        Some(&counter.environment().sender_addr()),
        &[],
    );
    assert!(init_res.is_ok());

All contract operations will return an object of type cw_orch::prelude::CosmTxResponse. This represents a successful transaction. Using the txhash of the tx, you can also inspect the operations on a chain explorer.

ADVICE: Add RUST_LOG=INFO to your environment and use the env_logger::init() initializer to get detailed information about your script execution. Cw-orchestrator provides enhanced logging tools for following the deployment and potentially pick up where you left off. This environment needs wasm artifacts to deploy the contracts to the chains. Don’t forget to compile all your wasms before deploying your contracts !

State management

In order to manage your contract deployments cw-orchestrator saves the contract addresses and code ids for each network you’re interacting with in a JSON formatted state file. This state file represents all your past. You can customize the path to this state file using the STATE_FILE env variable.

When calling the upload function on a contract, if the tx is successful, the daemon will get the uploaded code_id and save it to file, like so:

{
  "juno-1": {
    "code_ids": {
      "counter_contract": 1356,
    },     
  }
}

In this example: counter_contract corresponds to the contract_idvariable (the one that you can set in the contract interface constructor).

When calling the instantiate function, if the tx is successful, the daemon will get the contract address and save it to file, like so:

{
  "juno-1": {
    "code_ids": {
      "counter_contract": 1356,
    },
    "default": {
      "counter_contract": "juno1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqwrw37d"
    }
  }
}

In this example, the default keyword corresponds to the deployment namespace. This can be set when building the daemon object (using the DaemonBuilder::deployment_id method) in order to separate multiple deployments. For instance for a DEX (decentralized exchange), you can have a single code-id but multiple pool addresses for all your liquidity pools. You would have a juno-usdc and a usdt-usdc deployment, sharing the same code-ids but different contract instances.

Configuration

When creating a Daemon, use the DaemonBuilder object to set options for the structure. Here are the available options and fields you can use in the builder object:

  • chain (required) specifies the chain the daemon object will interact with. Documentation Link
  • deployment_id (optional) is used when loading and saving blockchain state (addresses and code-ids). It is useful when you have multiple instances of the same contract on a single chain. It will allow you to keep those multiple instances in the same state file without overriding state.Documentation Link
  • handle (optional) is the tokio runtime handled used to await async functions. cw-orch provides a default runtime if not specified. Documentation Link
  • mnemonic (optional) is the mnemonic that will be used to create the sender associated with the resulting Daemon Object. It is not compatible with the sender method. Documentation Link
  • state (optional) is used when you want to specify an existing DaemonState object to the new Daemon. This is particularly useful when interacting with multiple chains at the same time.

NOTE: if mnemonic is not specified, env variables will be used to construct the sender object.

Keep in mind that most of these options can’t be changed once the Daemon object is built, using the build function. It is possible to create a new DaemonBuilder structure from a Daemon object by using the rebuild method and specifying the options that you need to change.

Properties of the default sender

If you wish to use the default CosmosSender provided by default, you can use this simple pattern:

  let daemon = Daemon::builder(JUNO_1).build()?;

You can use the following functions on the CosmosSender (obtained via Daemon::sender_mut()) object to customize some of its properties:

Customizing the Sender

If you wish to use the Daemon object with a different sender (for instance to batch transactions, or to submit the transaction to a multisig), you can use DaemonBuilder::build_sender instead of DaemonBuilder::build. This allows you to customize the sender before constructing the Daemon object. You can find an example of such usage in our official Github repository

Additional tools

The Daemon environment provides a bunch of tools for you to interact in a much easier way with the blockchain. Here is a non-exhaustive list:

  • Send usual transactions:

    #![allow(unused)]
    fn main() {
      let wallet = daemon.sender();
    
      let rt = daemon.rt_handle.clone();
      rt.block_on(wallet.bank_send(
          &Addr::unchecked("<address-of-my-sister>"),
          coins(345, "ujunox"),
      ))?;
    }
  • Send any transaction type registered with cosmrs:

    #![allow(unused)]
    fn main() {
      let tx_msg = cosmrs::staking::MsgBeginRedelegate {
          // Delegator's address.
          delegator_address: AccountId::from_str("<my-address>").unwrap(),
    
          // Source validator's address.
          validator_src_address: AccountId::from_str("<my-least-favorite-validator>").unwrap(),
    
          // Destination validator's address.
          validator_dst_address: AccountId::from_str("<my-favorite-validator>").unwrap(),
    
          // Amount to UnDelegate
          amount: Coin {
              amount: 100_000_000_000_000u128,
              denom: Denom::from_str("ujuno").unwrap(),
          },
      };
      rt.block_on(wallet.commit_tx(vec![tx_msg.clone()], None))?;
    }
  • Send any type of transactions (Using an Any type):

    #![allow(unused)]
    fn main() {
      rt.block_on(wallet.commit_tx_any(
          vec![cosmrs::Any {
              type_url: "/cosmos.staking.v1beta1.MsgBeginRedelegate".to_string(),
              value: tx_msg.to_any().unwrap().value,
          }],
          None,
      ))?;
    }
  • Simulate a transaction without sending it

    #![allow(unused)]
    fn main() {
      let (gas_needed, fee_needed) =
          rt.block_on(wallet.simulate(vec![tx_msg.to_any().unwrap()], None))?;
    
      log::info!(
          "Submitting this transaction will necessitate: 
              - {gas_needed} gas
              - {fee_needed} for the tx fee"
      );
    }

Queries

The daemon object can also be used to execute queries to the chains we are interacting with. This opens up a lot more applications to cw-orchestrator as this tools can also be used to manage off-chain applications.

Querying the chain for data using a daemon looks like:

    let bank_query_client: Bank = daemon.querier();

    let sender = Addr::unchecked("valid_sender_addr");
    let balance_result = bank_query_client.balance(&sender, None)?;
    println!("Balance of {} : {:?}", sender, balance_result);

For more information and queries, visit the daemon querier implementations directly

Example of code leveraging Daemon capabilities

Here is an example of a script that deploys the counter contract only after a specific block_height.

impl CounterContract<Daemon> {
    /// Deploys the counter contract at a specific block height
    pub fn await_launch(&self) -> Result<()> {
        let daemon = self.environment();

        // Get the node query client, there are a lot of other clients available.
        let node: Node = daemon.querier();
        let mut latest_block = node.latest_block().unwrap();

        while latest_block.height < 100 {
            // wait for the next block
            daemon.next_block().unwrap();
            latest_block = node.latest_block().unwrap();
        }

        let contract = CounterContract::new(daemon.clone());

        // Upload the contract
        contract.upload().unwrap();

        // Instantiate the contract
        let msg = InstantiateMsg { count: 1i32 };
        contract.instantiate(&msg, None, &[]).unwrap();

        Ok(())
    }
}

CW Multi Test

Cw Multi Test is a rust-based test framework that allows developers to test for multi-contract interactions without having to dispatch messages, storage and other variables themselves. With cw-orchestrator, most of the cw-multi-test logic is abstracted away, but you can still learn more about the framework here.

⚠️ Custom-typed contracts: cw-orch’s integration with cw-multi-test is not compatible with custom-typed contracts.

Custom-typed contracts are contracts where the endpoints return Response<M> where Mis not cosmwasm_std::Empty OR where the Deps and DepsMut endpoint arguments have a non Empty generic parameter specified.

You can still test and deploy your contracts on actual nodes (testnets, mainnets). To achieve that, don’t specify the wrapper function in the Uploadable trait implementation on your interface.

Quick Start

Before starting, here are a few examples utilizing the mock structures:

The cw-multi-test integration comes at no extra cost for the developer. Creating a test environment in cw-orchestrator that leverages cw-multi-test goes along the lines of:

    use cw_orch::prelude::*;
    use cosmwasm_std::Addr;
    let sender = Addr::unchecked("juno16g2rahf5846rxzp3fwlswy08fz8ccuwk03k57y");

    let mock = Mock::new(&sender);

NOTE: When using cw-multi-test, the addresses ARE NOT validated like on a live chain. Therefore, you can use any string format for designating addresses. For instance,Addr::unchecked("my-first-sender") is a valid cw-multi-test address.

NOTE: When using cw-multi-test, NO gas fees are charged to the sender address.

Interacting with contracts

You can then use the resulting Mock variable to interact with your contracts:

    let contract_counter = CounterContract::new(mock);

    let upload_res = contract_counter.upload();
    upload_res.unwrap();

When executing contracts in a cw-multi-test environment, the messages and sub-messages sent along the Response of an endpoint, will be executed as well. This environment mocks the actual on-chain execution exactly.

  • This environment uses the actual functions of your contract without having to compile them into WASM. When you are calling upload with this environment, no wasm files are included in the test environment. This allows for better debugging of your contract code.

  • You will need to have implemented the wrapper function for interacting the the Mock environment. This function will allow you to “connect” your contract endpoints to your Contract struct. See the dedicated page for more details.

  • NOTE: Keep in mind that cw-multi-test is based solely in rust and that a lot of actual blockchain modules are not mocked in the environment. The main cosmos modules are there (Bank, Staking), but some very useful ones (tokenfactory, ibc) as well as Stargate messages are not supported by the environment.

Cloning

When cloning a cw_multi_test environment, you are not cloning the entire environment, but instead you are creating a new Mock typed variable with the same underlying cw_multi_test::App object reference. This is useful for objects that require to pass the chain as an object rather than by reference. The underlying cw_multi_test::App object is however not clonable.

Bech32

When testing your smart contracts, you may need to use valid bech32 addresses instead of arbitrary strings. Notably Bech32 addresses are a requirement whenever instantiate2 is used in your codebase. Therefore cw-orch allows this behavior using the MockBech32 struct. This structure has the same features as the above described Mock with the following differences:

  • The constructor now takes the bech32 prefix instead of the sender. Under the hood, the environment creates an address that will be used as the default sender for actions executed on that environment:

    let mock = MockBech32::new("<chain-prefix>");
    // For instance: 
    let mock = MockBech32::new("juno");
    // With chain id: 
    let mock = MockBech32::new_with_chain_id("juno", "juno-1");
    // Default sender address for this env
    let sender = mock.sender();
  • To facilitate the creation of additional valid bech32 addresses, we have added an addr_make function. This function uses its string argument to create a new address. That way you can create a new address without having to care about bech32 encoding/decoding yourself:

     let named_sender = mock.addr_make("sender");
     /// This creates a new address with some funds inside of it
     let rich_sender = mock.addr_make_with_balance("sender_with_funds", coins(100_000_000_000_000, "ujuno"));

Snapshot testing

cw-orch provides snapshot testing capabilities to assist you catching breaking changes to your contracts. The Mock::take_storage_snapshot function allows you to dump all the deployed contracts’ storage values into insta.rs that executes snapshot testing. An example application of this feature is to make sure that the storage of your contracts don’t change when migrating a contract. Using this tool, you should have a test that looks something like this:


#[test]
fn storage_stays_the_same(){
    let mock = Mock::new(Addr::unchecked("sender"));

    ... // Upload, instantiate, execute contracts

    // Make sure that the operations have a fixed result
    mock.take_storage_snapshot("mock_snapshot")?;
}

At any point of development, if the storage variables are modified, this test will fail and alert you that you are doing breaking changes to your storage variables. Learn more about the underlying tool in the official documentation.

Additional tools

The Mock test environment allows you to change application variables (such as the balance of an account) using wrappers around the underlying cw_multi_test::App object. Here are some examples of those wrappers in context:

    let new_sender = Addr::unchecked("entirely-new-sender");
    mock.set_balance(&new_sender, coins(100_000, "ujunox"))
        .unwrap();

    // Reuploads as the new sender
    contract_counter.call_as(&new_sender).upload().unwrap();

    // Here the contract_counter sender is again `sender`

    // Sets the new_sender as the definite sender
    contract_counter.set_sender(&new_sender);

    // From now on the contract_counter sender is `new_sender`

Additional customization

As we don’t provide wrappers around each and every functionality that cw-multi-test provides, you can also customize the underlying cw_multi_test::Appobject to your specific needs. In the following example, we create a new validator in the test environment:

    mock.app
        .borrow_mut()
        .init_modules(|router, api, storage| {
            router.staking.add_validator(
                api,
                storage,
                &BlockInfo {
                    height: 16736,
                    time: Timestamp::from_seconds(13345762376),
                    chain_id: "juno-1".to_string(),
                },
                cosmwasm_std::Validator::create(
                    "new-validator-address".to_string(),
                    Decimal::from_str("0.5").unwrap(), // Greedy validator
                    Decimal::from_str("1").unwrap(),   // Dangerous validator
                    Decimal::from_str("1").unwrap(),   // Very dangerous validator
                ),
            )
        })
        .unwrap();

Osmosis Test Tube

Osmosis Test Tube is a rust-based test framework that allows developers to test for multi-contract interactions without having to dispatch messages, storage and other variables themselves. This environment is even closer to the actual on-chain mechanisms than Cw Multi Test, because it runs tests directly on the actual chain binaries. With cw-orchestrator, most of the osmosis-test-tube logic is abstracted away, but you can still learn more about the framework here.

Prerequesites

In order to use osmosis-test-tube, the library needs to be able to compile and install the chain binaries. For that you will need to install a go compiler as well as the clang library. Run the following commands to install both of those libraries.

Ubuntu

  1. Install go
  2. Install the clang library
    sudo apt install clang

Arch Linux

  1. Install go
    sudo pacman -Sy go
  1. Install the clang library
    sudo pacman -Sy clang

Quick Start

Before starting, here is a few examples utilizing the osmosis-test-tube environment

To be able to use osmosis-test-tuee you need to add the cw-orch-osmosis-test-tube dependency inside your crate.

Creating a test environment in cw-orchestrator that leverages osmosis-test-tube goes along the lines of:

    use cw_orch::prelude::*;
    use cw_orch_osmosis_test_tube::OsmosisTestTube;
    use cosmwasm_std::coins;
    let mut chain = OsmosisTestTube::new(coins(1_000_000_000_000, "uosmo"));

This snippet will create a new address, provide it with initial balances and create the osmosis-test-tube environment. The addresses are not handled like in the cw-multi-test environment or in mock tests and can’t be decided upon manually. You will learn more later about handling addresses in the OsmosisTestTube environment.

NOTE: When using osmosis-test-tube, the addresses are validated like on a live chain.

NOTE: When using osmosis-test-tube, gas fees are charged to the sender address. The gas fees don’t represent the actual gas fees you will occur when interacting with the actual chain. That’s why in the test snippet above, the amount of uosmo instantiated with the account is very high.

Interacting with contracts

You can then use the resulting OsmosisTestTube variable to interact with your contracts:

    let contract_counter = CounterContract::new(chain.clone());

    let upload_res = contract_counter.upload();
    assert!(upload_res.is_ok());

    let init_res = contract_counter.instantiate(&InstantiateMsg { count: 0 }, None, None);
    assert!(init_res.is_ok());

When executing contracts in an osmosis_test_tube environment, the messages and sub-messages sent along the Response of an endpoint, will be executed as well. This environment mimics the actual on-chain execution by dispatching the messages inside the actual chain binaries.

This environment uses wasm files compiled from the project. Therefore, you need to compile the WASM artifacts from the project for your osmosis-test-tube integration tests. You will need to have the wasm function from the Uploadabletrait implemented . See the dedicated page for more details.

Cloning

When cloning an osmosis_test_tube environment, you are not cloning the entire environment, but instead you are creating a new OsmosisTestTube typed variable with the same underlying osmosis_test_tube::App object reference. This is useful for objects that require to pass the chain as an object rather than by reference. The underlying osmosis_test_tube::App object is however not clonable.

Additional tools

The OsmosisTestTube test environment allows you to change application variables (such as the balance of an account) using wrappers around the underlying osmosis_test_tube::App object. Here are some examples of those wrappers in context:

    let new_sender = chain.init_account(coins(100_000, "ujunox")).unwrap();

    // Reuploads as the new sender
    contract_counter.call_as(&new_sender).upload().unwrap();

    // Here the contract_counter sender is again `sender`

    // Sets the new_sender as the definite sender
    contract_counter.set_sender(&new_sender);

    // From now on the contract_counter sender is `new_sender`

NOTE: With OsmosisTestTube, you can’t create accounts with a specific address. Accounts are created by the app directly and you don’t have a lot of control over them. As in the example above, use OsmosisTestTube::init_account to create a new account.

Additional customization

As we don’t provide wrappers around each and every functionality that osmosis_test_tube provides, you can also customize the underlying osmosis_test_tube::App object to your specific needs. In the following example, we increase the block time in the test environment:

    chain.app.borrow_mut().increase_time(150);

Unit Tests

Cw-orchestrator provides an additional tool to help you with unit tests with context. The WasmMockQuerier object allows you to leverage cw-orch querying abilities to unit-test your contracts.

Capabilities

The WasmMockQuerier (and associated mock_dependencies helper function) allow developers to unit-test their contract against on-chain logic. This allows for a mix between unit and integration tests for application that need to rely on on-chain data. This structure works very similarly to cosmwasm_std::testing::mock_dependencies but instead of querying for local scripts or data, it queries the information from an actual running blockchain.

Today, the following modules are supported by this querier:

  • Wasm
  • Bank
  • Staking (support has not been finalized as of yet)

Example

Let’s imagine you want to build a lending aggregator. In this kind of application, you want to query the balance in staking tokens that your client has. In order to do that, you may want to use the following syntax inside your contract:

fn query_balance(deps: Deps, client: Addr, lending_contract: Addr) -> Result<BalanceResponse, ContractError>{
    let balance: BalanceResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart{
        contract_addr: lending_contract,
        query: Binary::from({
            StakingBalance{
                ...
            }
        })
    }))?;
}

In order to unit-test that logic, you may want to test against actual on-chain data. The following code_snippet will allow you to test this piece of code against actual on-chain code.

#[test]
fn balance_is_available() -> anyhow::Result<()>{
    let deps = cw_orch::prelude::live_mock::mock_dependencies(JUNO_1);

    // assert the query is successful
    let balance_response = query_balance(deps.as_ref(), Addr::unchecked("juno1..."), Addr::unchecked("juno1..")).unwrap();
}

Obviously, you need to specify existing on-chain addresses to be able to use the cw_orch mock_dependencies function. Those dependencies have a very similar behavior to cosmwasm_std::testing::mock_dependencies, expect the query response is fetched from on-chain information.

Clone Testing

Cw-orchestrator supports testing in a forked environment. With this feature, you can execute the application you are developing on a blockchain environment without having to spin up a node yourself and create a fork locally. This means that it will simulate having the same state as mainnet, but it will run locally inside your Rust code. All the code and application you will be running during the test will be rust code and the storage needed to execute is minimal as only the necessary data is downloaded from actual blockchain nodes.

Brief Overview

We leverage the beautiful cw-multi-test package, created and maintained by Cosmwasm and added a few functionalities that allow you to execute your code just as if you were interacting with an actual on-chain node, but locally, and without any on-chain funds necessary.

Setup

Before using clone testing, import the following crate cw-orch-clone-testing

Setting up the environment is really easy and only requires feeding a ChainData object to the struct constructor:

use cw_orch::networks::JUNO_1;
use cw_orch_clone_testing::CloneTesting;

let app = CloneTesting::new(JUNO_1)?;

With this, you are ready to upload, instantiate, migrate and interact with on-chain contracts…

You can find an advanced example in the cw-orch repository.

Execution Flow

This execution environment has a mixed behavior.

Execution

The blockchain modules logic is implemented in rust. Every module that is not standard has to be present and implemented in rust to be available. Because we are based on cw-multi-test, our application is compatible with cw-multi-test modules. For now:

  • The Wasm module is implemented locally via a mixed approach that allows execution of:

    • Rust Code via cw-multi-test
    • Wasm binaries via the cosmwasm-vm package.
  • The Bank module is implemented by cw-multi-test and available in this environment.

  • The Staking module is not fully implemented because distant storage is more difficult to query. It’s the next module we wish to implement.

Storage

This part is actually at the center of the innovation provided by this package.

  • When reading blockchain storage, the testing environment will execute in order:

    1. Check local storage for data availability. If some data was stored locally, use that value
    2. If not, check the registered blockchain node for data availability and error if it’s not available.
  • When writing value to storage, nothing changes.

Let’s take an example for clarity. Say I want to deposit some funds into Anchor Protocol. Here are the steps that a user would have to go through and how they are executed inside the environment.

  1. The user needs funds to interact with the protocol. A fork-helper allows to increase the balance of an address.

     // Sender address. Can also be an actual account with funds.
     // Could also be app.sender() for creating an address automatically.
     let sender = "terra1..."; 
     let sender_addr = Addr::unchecked(sender);
     app.set_sender(&sender_addr);
     // We add some funds specific for our application
     app.set_balance(&sender_addr, coins(10_000_000, "uusd"))?;
  2. The user calls the following ExecuteMsg on the actual mainnet Anchor Moneymarket Contract:

    let market_addr = Addr::unchecked("terra1..."); // Actual contract address of the Anchor deployment.
    let market = AnchorMarket::new("anchor:money-market", app.clone());
    market.set_address(&market_addr);
    market.deposit_stable(&coins(10_000, "uusd"))?;
  3. During the whole message execution, when storage is queried, if it doesn’t exist locally it will be queried from the chain. This is true for storage during contract execution but this is also true for querying the actual Wasm Code when executing/querying a contract. No local storage is used until something is written to it 1.

  4. Even in the case of multiple chained contract calls, storage is modified accordingly and usable by contracts.

  5. After message execution, queries and states are modified according to the contract execution. After depositing, it is now possible to query your stake or even to withdraw your funds:

    let a_currency = "terra1..."; // The contract address for the staking receipt
    
    /// This should give a non-zero value, even if no change occurred on the actual mainnet state
    let response: BalanceResponse = app
        .query(&Cw20QueryMsg::Balance {
                address: sender.to_string(),
            },
            &Addr::unchecked(a_currency),
        )?;
    
    /// We can get our funds back, no problem, the state changes get propagated as well locally
    market.redeem_all_stable()?;

Usage

You use this fork environment as you would use the Mock environment, with a few subtle changes:

  1. You can’t use human readable addresses, because this environment uses actual APIs and needs to be able to verify addresses. When creating the Mock environment, a sender gets created along and attach automatically to the CloneTesting instance. If you need additional addresses, you can use:

    let new_sender: Addr = fork.init_account();
  2. The environment allows for using contracts defined using its functions (just like the Mock can) AND compiled WASM contracts. By default, the Rust wrapper method is used to upload the contract in the environment. The following code will use this wrapper. This makes it really easy and fast to iterate on your contracts (for migrating, debugging…):

    use cw_orch::prelude::*;
    let chain = CloneTesting::new(JUNO_1)?;
    let contract = CounterContract::new(chain.clone());
    contract.upload()?;

    If you prefer using Wasm compiled smart contracts, use the following snippet:

    use cw_orch::prelude::*;
    let chain = CloneTesting::new(JUNO_1)?;
    let contract = CounterContract::new(chain.clone());
    use cw_orch_clone_testing::WasmUpload;
    contract.upload_wasm()?;
1

In the future, we might leverage a local storage cache to avoid querying distant RPCs too much (for more speed and less data consumption).

Interchain Capabilities

Because of its asynchronous and decentralized nature, the Inter-Blockchain Communication protocol makes developing and debugging applications more difficult than simple blockchain transactions. Cw-orch simplifies those tasks by providing developers tools, full testing environments and standard interfaces to interact with applications that leverage IBC capabilities.

Here are a few examples of what cw-orchestrator allows:

Interchain transaction waiting

To simplify the developer experience and provide simple syntax, cw-orchestrator allows developers to await the whole packet IBC lifecycle (Receive, Acknowledge or Timeout) from transactions that create IBC packets. This is mostly useful when interacting with existing contracts or modules in a script to simplify the execution pipeline.

// We order an ICA controller to burn some funds on juno from akash
// Upon submitting this transaction successfully, a packet will be sent from akash to juno to trigger the ICA action
let transaction_response = controller.send_msgs("channel-16", vec![
        CosmosMsg::Bank(cosmwasm_std::BankMsg::Burn {
            amount: vec![cosmwasm_std::coin(100u128, "ujuno")],
        })
    ])?;

// This function won't return before the packet is relayed successfully or timeouts. 
// In case of a timeout, it will error
interchain.await_and_check_packets(
    "akash",
    transaction_response
)?;

// You can safely continue with the rest of your application, the packet has been successfully relayed

This namely removes the need for pausing the program and resuming manually or with a timer. This also allows to automatically get extra information about the relayed packet and assert the IBC-cycle ended successfuly.

Interchain packet following

Using some simple tools, one can also follow the execution of IBC packets through their whole lifetime (Receive, Acknowledge or Timeout). This is a little more advanced, because you need to know and get more details about the packet manually.

This is mostly useful for packet analysis of certain channels, ports or connections.

use cw_orch_interchain::InterchainEnv;
fn main() -> anyhow::Result<()>{
    let interchain = cw_orch_interchain::MockInterchainEnv::new(vec![]);    
    #[allow(unused)]
    let packet_lifetime = interchain.await_single_packet(
        "juno",
        "transfer".parse()?,
        "channel-16".parse()?,
        "akash",
        3u64.into()
    )?;
    Ok(())
}

Interchain application testing

Cw-orch allows developers to test their IBC applications and smart-contracts using a common interface. As we know that setting an IBC testing environment is heavy on resources and can be time-consuming, we provide 3 testing environments that will help them streamline their development process:

Rust-only

The MockInterchainEnv object allows developers to test their application without leaving Rust and without compromising on test speed.

Built on top of cw-multi-test, this environment replicates the actual on-chain IBC module (channel creation as well as packet relaying). This allows you to test any IBC application that leverages Smart-Contract or Bank-module IBC packets. It is really powerful and doesn’t rely on ANY external tools to work. No node setup, no relayer setup, no cluster setup, everything runs inside your crate. Visit the dedicated Mock Interchain Env page for more details and code snippets.

Cosmos SDK Node Testing

The Starship object allows developers to test their application against the actual binaries of running chains. If you want to run your application with specific logic, custom messages or modules, this is the preferred way. This is the IBC version of the local chains that you can run locally. It can also spin up relayers and set up connections between your local chains automatically.

Visit the dedicated Starship page for more details and code snippets.

Cosmos SDK Node Scripting

The DaemonInterchain object allows developers to script, deploy and manage their application on running chains with attention to IBC functionalities. This enhances the developer experience with more tooling, more useful logging. This is the all-in-one toolbox cor the cosmwasm IBC developer.

Visit the dedicated Daemon Interchain page for more details and code snippets.

Interchain Quickstart

General Example

Creating the environment

In order to interact with your environment using IBC capabilities, you first need to create an interchain structure. In this guide, we will create a mock environment for local testing. ↓Click here, if you want to interact with actual nodes.

With mock chains, you can create a mock environment simply by specifying chains ids and sender addresses. For this guide, we will create 2 chains, juno-1 and osmosis-1, with respective address prefixes:

 let interchain = MockBech32InterchainEnv::new(vec![("juno-1", "juno"), ("osmosis-1", "osmosis")]);

Interacting with the environment

Now, we will work with interchain accounts (ICA). There is a simple implementation of the ICA protocol on Github, and we will use that application with a few simplifications for brevity.

In this protocol, we have 2 smart-contracts that are able to create a connection between them. The client will send IBC messages to the host that in turn will execute the messages on its chain. Let’s first create the contracts:

let juno = interchain.chain("juno-1")?;
let osmosis = interchain.chain("osmosis-1")?;

let client = Client::new("test:client", juno.clone());
let host = Host::new("test:host", osmosis.clone());

client.upload()?;
host.upload()?;
client.instantiate(&Empty{}, None, None)?;
host.instantiate(&Empty{}, None, None)?;

The Client and Host structures here are cw-orchestrator Contracts with registered ibc endpoints.

Client contract definition (Click to get the full code)
use cw_orch::prelude::ContractWrapper;
use cw_orch::contract::WasmPath;
#[interface(
    simple_ica_controller::msg::InstantiateMsg,
    simple_ica_controller::msg::ExecuteMsg,
    simple_ica_controller::msg::QueryMsg,
    Empty
)]
struct Client;

impl<Chain> Uploadable for Client<Chain> {
    // No wasm needed for this example
    // You would need to get the contract wasm to be able to interact with actual Cosmos SDK nodes
    fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
        unimplemented!("No wasm")
    }
    // Return a CosmWasm contract wrapper with IBC capabilities
    fn wrapper() -> Box<dyn MockContract<Empty>> {
        Box::new(
            ContractWrapper::new_with_empty(
                simple_ica_controller::contract::execute,
                simple_ica_controller::contract::instantiate,
                simple_ica_controller::contract::query,
            )
            .with_ibc(
                simple_ica_controller::ibc::ibc_channel_open,
                simple_ica_controller::ibc::ibc_channel_connect,
                simple_ica_controller::ibc::ibc_channel_close,
                simple_ica_controller::ibc::ibc_packet_receive,
                simple_ica_controller::ibc::ibc_packet_ack,
                simple_ica_controller::ibc::ibc_packet_timeout,
            ),
        )
    }
}
Host contract definition (Click to get the full code)
// This is used because the simple_ica_host contract doesn't have an execute endpoint defined 
pub fn host_execute(_: DepsMut, _: Env, _: MessageInfo, _: Empty) -> StdResult<Response> {
    Err(StdError::generic_err("Execute not implemented for host"))
}

#[interface(
    simple_ica_host::msg::InstantiateMsg,
    Empty,
    simple_ica_host::msg::QueryMsg,
    Empty
)]
struct Host;

impl<Chain> Uploadable for Host<Chain> {
    // No wasm needed for this example
    // You would need to get the contract wasm to be able to interact with actual Cosmos SDK nodes
    fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
        unimplemented!("No wasm")
    }
    // Return a CosmWasm contract wrapper with IBC capabilities
    fn wrapper() -> Box<dyn MockContract<Empty>> {
        Box::new(
            ContractWrapper::new_with_empty(
                host_execute,
                simple_ica_host::contract::instantiate,
                simple_ica_host::contract::query,
            )
            .with_reply(simple_ica_host::contract::reply)
            .with_ibc(
                simple_ica_host::contract::ibc_channel_open,
                simple_ica_host::contract::ibc_channel_connect,
                simple_ica_host::contract::ibc_channel_close,
                simple_ica_host::contract::ibc_packet_receive,
                simple_ica_host::contract::ibc_packet_ack,
                simple_ica_host::contract::ibc_packet_timeout,
            ),
        )
    }
}

Then, we can create an IBC channel between the two contracts:

let channel_receipt: ChannelCreationResult<_> = interchain.create_contract_channel(&client, &host, None, "simple-ica-v2").await?;

// After channel creation is complete, we get the channel id, which is necessary for ICA remote execution
let juno_channel = channel_receipt.interchain_channel.get_chain("juno-1")?.channel.unwrap();

This step will also await until all the packets sent during channel creation are relayed. In the case of the ICA contracts, a {"who_am_i":{}} packet is sent out right after channel creation and allows to identify the calling chain.

Finally, the two contracts can interact like so:

/// This broadcasts a transaction on the client
/// It sends an IBC packet to the host
let tx_response = client.send_msgs(
    juno_channel.to_string(), 
    vec![CosmosMsg::Bank(cosmwasm_std::BankMsg::Burn {
            amount: vec![cosmwasm_std::coin(100u128, "uosmo")],
    })],
    None
)?;

Now, we need to wait for the IBC execution to take place and the relayers to relay the packets. This will also verify that the IBC execution is successful. This is done through:

let packet_lifetime = interchain.await_and_check_packets("juno-1", tx_response).await?;

If it was relayed correctly, we can proceed with our application.

With this simple guide, you should be able to test and debug your IBC application in no time. Learn more about the implementation and details of the IBC-enabled local testing environment.

With actual Cosmos SDK Nodes

You can also create an interchain environment that interacts with actual running chains. Keep in mind in that case that this type of environment doesn’t allow channel creation. This step will have to be done manually with external tooling. If you’re looking to test your application in a full local test setup, please turn to ↓Starship

    use cw_orch::prelude::*;

    // This will create `Daemon` structures associated with chains `LOCAL_JUNO` and `LOCAL_OSMO`
    let mut interchain =
        DaemonInterchain::new(vec![LOCAL_JUNO, LOCAL_OSMO], &ChannelCreationValidator)?;

    let local_juno: Daemon = interchain.get_chain("testing")?;
    let _local_osmo: Daemon = interchain.get_chain("localosmosis")?;

    // You can also create your own daemon and add it manually
    let local_migaloo = DaemonBuilder::new(LOCAL_MIGALOO)
        .state(local_juno.state())
        .build()?;

    interchain.add_daemons(vec![local_migaloo]);

With this setup, you can now resume this quick-start guide from ↑Interacting with the environment.

You can also learn more about the interchain daemon implementation.

With Starship

You can also create you interchain environment using starship, which allows you to test your application against actual nodes and relayers. This time, an additional setup is necessary. Check out the official Starship Getting Started guide for more details.

Once starship is setup and all the ports forwarded, assuming that starship was run locally, you can execute the following:

    use cw_orch::prelude::*;
    
    let starship = Starship::new(None)?;
    let interchain = starship.interchain_env();

    let _local_juno: Daemon = interchain.get_chain("juno-1")?;
    let _local_osmo: Daemon = interchain.get_chain("osmosis-1")?;

This snippet will identify the local Starship setup and initialize all helpers and information needed for interaction using cw-orchestrator. With this setup, you can now resume this quick-start guide from ↑Interacting with the environment

You can also learn more about the interchain daemon implementation.

Execution Environment Integration

Daemon Interchain Environment

This environment allows to interact with actual Cosmos-SDK Nodes. Let’s see how that work in details:

Environment creation

For scripting

When scripting with cw-orch-interchain, developers don’t have to create chain Daemon objects on their own. You can simply pass chain data to the interchain constructor, and it will create the daemons for you. Like so:

use cw_orch::prelude::*;
use cw_orch_interchain::prelude::*;
use cw_orch::prelude::networks::{LOCAL_JUNO, LOCAL_OSMO};
fn main(){
    let mut interchain = DaemonInterchain::new(vec![
        (LOCAL_JUNO, None),
        (LOCAL_OSMO, None)
    ], &ChannelCreationValidator)?;
}

NOTE: Here the ChannelCreationValidator struct is a helper that will simply wait for channel creation when it’s called in the script. More information on that channel creation later.

You can then access individual Daemon objects like so:

use cw_orch::prelude::*;
use cw_orch_interchain::prelude::*;
fn main(){
    let local_juno = interchain.get_chain("testing")?;
    let local_osmo = interchain.get_chain("localosmosis")?;
 }

where the argument of the get_chain method is the chain id of the chain you are interacting with. Note that this environment can’t work with chains that have the same chain_id.

You can also add daemons manually to the interchain object:

let local_migaloo = DaemonBuilder::default(LOCAL_MIGALOO).build()?;
interchain.add_daemons(vec![local_migaloo]);
When working with multiple `Daemon` object that share the same state file, you need to make sure that the `Daemon` object use the same underlying `DaemonState` object, otherwise you might get conflicts at runtime. Here's how you do it:
let local_migaloo = Daemon::builder()
    .chain(LOCAL_MIGALOO)
    .build()?;
let local_juno = Daemon::builder()
    .chain(LOCAL_JUNO)
    .state(local_migaloo.state())
    .build()?;

For testing

In some cases (we highly recommend it), you might want to interact with local nodes and relayers to test IBC interactions. To do so, we allow users to leverage @cosmology-tech/Starship. Starship allows developers to spin up a fully simulated mini-cosmos ecosystem. It sets up Cosmos SDK Nodes as well as relayers between them allowing you to focus on your application and less on the testing environment.

For setup, please refer to the setup Starship section of this page or checkout the official Quick Start. When all that is done, the starship adapter that we provide will detect the deployment and create the right cw-orchestrator variables and structures for you to interact and test with.

use cw_orch_interchain::interchain::{Starship, ChannelCreator};

fn main(){
    let starship = Starship::new(None)?;
    let interchain = starship.interchain_env();

    let local_juno = interchain.chain("juno-1")?;
    let local_osmo = interchain.chain("osmosis-1")?;
}

NOTE: The argument of the Starship::new function is the optional URL of the starship deployment. It defaults to http://localhost:8081, but you can customize it if it doesn’t match your setup. All the starship data, daemons and relayer setup is loaded from that URL.

Setup Starship

You can find helpers to setup starship in the cw-orch repo. Here are the command to launch in order to have starship up and running:

  • Install Starship and the chain+relayer cluster (do this once, this make take a little time)

    make setup
    
  • Start Starship. This will create all chains and relayers based on the example configuration file.

    make install
    

Starship will most likely crash after at most 1 day of usage. Don’t forget to make stop and make install once everything is stopped from time to time to restart the whole chain cluster.

General Usage

All interchain environments are centered around the await_single_packet function. In the Daemon case (be it for testing or for scripting), this function is responsible for tracking the relayer interactions associated with the packet lifetime. The lifetime steps of this function are:

  1. On the source chain, identify the packet and the destination chain. If the destination chain id is not registered in the interchain environment, it will error. Please make sure all the chains you are trying to inspect are included in the environment.
  2. Then, it follows the timeline of a packet. A packet can either timeout or be transmitted successfully. The function concurrently does the following steps. If one step returns successfully, the other step will be aborted (as a packet can only have one outcome). a. Successful cycle:
    1. On the destination chain, it looks for the receive transaction of that packet. The function logs the transaction hash as well as the acknowledgement when the receive transaction is found.
    2. On the source chain, it looks for the acknowledgement transaction of that packet. The function logs when the acknowledgement is received and returns with the transactions involved in the packet broadcast, as well as information about the acknowledgement. b. Timeout cycle:
    3. On the source chain, it looks for the timeout transaction for that packet. The function logs the transaction hash of the transaction and returns the transaction response corresponding to that transaction.

If you have followed the usage closely, you see that this function doesn’t error when the acknowledgement is an error, has a wrong format or if the packet timeouts. However, the function might error if either of the timeout/successful cycle takes too long. You can customize the wait time in the cw-orchestrator environment variables.

The await_packets function is very similar except that instead of following a single packet, it follows all packets that are being sent within a transaction. This works in a very similar manner and will also not error as long as either a timeout or a successful cycle can be identified before cw-orchestrator query function timeouts. This function is recursive as it will also look for packets inside the receive/ack/timeout transactions and also follow their IBC cycle. You can think of this function as going down the rabbit-hole of IBC execution and only returning when all IBC interactions are complete.

Finally the await_and_check_packets function allows to follow all packet execution and assert that the acknowledgements received for each correspond to a successful execution. Because there is no standard for signaling a successful IBC transaction, you might need to customize your ack assertion logic if you are using un-common acknowledgment formats. Supported acks are the following:

Analysis Usage

The await_single_packet and await_packets function were coded for scripting usage in mind. They allow to await and repeatedly query Cosmos SDK Nodes until the cycle is complete. However, it is also possible to inspect past transactions using those tools. Using the DaemonInterchain::await_packets_for_txhash function, one can inspect the history of packets linked to a transaction from a transaction hash only. This enables all kinds of analysis usage, here are some:

  • Relayer activity
  • Analysis of past transactions for fund recovery
  • Whale account analysis

IBC Channel creation

cw-orchestrator doesn’t provide1 relayer capabilities. We only provide tools to analyze IBC activity based on packet relaying mechanism that only relayers can provide. However, when testing your implementation with Starship, you might want to automatically create channels on your test setup.

This is what the second argument of the DaemonInterchain::new function is used for. You provide an object which will be responsible for creating an IBC channel between two ports. We provide 2 such structures, you can obviously create your own if your needs differ:

  1. cw_orch_interchain::interchain::ChannelCreationValidator This is used when you want to have full control over the channel creation. When interchain.create_channel is called, the script will stop and prompt you to create a channel with external tools. Once the channel creation process is done on your side, you simply have to input the connection-id on which you created the channel to be able to resume execution. This solution is not ideal at all but allows you to script on actual nodes without having to separate your scripts into multiple parts or change the syntax you coded for your tests.

    To create the interchain environment with this ChannelCreator, use the Validator syntax above.

  2. cw_orch_interchain::interchain::Starship

    This is used when testing your application with Starship. When interchain.create_channel is called, the script will simply send a command to the starship cluster to create an IBC channel between the chains that you specified. Obviously, the relayer has to be specified in the starship configuration for this function to return successfully. With this function, you don’t have to worry about anything once your starship cluster is setup properly. The connection-id is returned automatically by the starship library and used throughout after that.

    To create the interchain environment with this ChannelCreator, use the Starship syntax above.

1

as of writing this documentation 06/25/2023. We have a branch open here, that may be merged into cw-orch. If you want to relay packets or create channel using cw-orch we recommend using ths branch.

Mock Interchain Environment

This environment allows you to test your IBC application inside your Rust environment without having to create new crates, new packages or even run other programs in the background. This is perfect for iterating through your designs and testing on the go without additional wait time or setup tutorials.

Environment creation

You can create your interchain environment using the following simple setup script:

use cw_orch::prelude::*;
use cw_orch_interchain::prelude::*;
fn main(){
    // Here `juno-1` is the chain-id and `juno` is the address prefix for this chain    
    #[allow(unused)]
    let interchain = MockBech32InterchainEnv::new(
        vec![("juno-1", "juno"), ("osmosis-1", "osmo")],
    );
}

Behind the scenes, MockBech32 objects are created with the specified chain ids. These mock environments can be used on their own to interact with testing environment directly. You can get those objects like so:

use cw_orch::prelude::*;
use cw_orch_interchain::prelude::*;
fn main() -> anyhow::Result<()>{
    let interchain = MockBech32InterchainEnv::new(
         vec![("juno-1", "juno"), ("osmosis-1", "osmosis")],
    );
    #[allow(unused)]
    let local_juno = interchain.get_chain("juno-1")?;
    #[allow(unused)]
    let local_osmo = interchain.get_chain("osmosis-1")?;
    Ok(())
}

where the argument of the chain method is the chain id of the chain you are interacting with. Note that this environment can’t work with chains that have the same chain_id.

You can also add mocks manually to the interchain object, after instantiation:

fn main(){
    use cw_orch::prelude::*;
    use cw_orch_interchain::prelude::*;
    let mut interchain = MockInterchainEnv::new(
       vec![("juno-1", "juno"), ("osmosis-1", "osmosis")],
    );
    let test_migaloo = MockBech32::new_with_chain_id("migaloo-1","migaloo");
    interchain.add_mocks(vec![test_migaloo]);
}

General Usage

All interchain environments are centered around the await_single_packet function. In the Mock case, this function is responsible for relaying the packets between the different chains. Using the exact same interface as with other environments, it takes care of all packet relaying procedures.

This function will relay a packets successfully from the receiving chain back the the source chain. Here is what the full cycle looks like:

  1. On the source chain, it queries the packet data associated with the packet channel and sequence.
  2. On the destination chain, it triggers a receive transaction for that packet.
  3. On the source chain, it finally triggers an acknowledgement transaction with the data the destination_chain returned.

The await_packets function is very similar except that instead of following a single packet, it follows all packets that are being sent within a transaction. This works in a very similar manner and will allows for testing timeouts on IBC packets. This function is recursive as it will also look for packets inside the receive/ack transactions and also follow their IBC cycles. You can think of this function as going down the rabbit-hole of IBC execution and only returning when all IBC interactions are complete.

Finally, the recommended function is await_and_check_packets. This will run await_packets and then make sure that the resulting IBC acknowledgments and executions are successful. You can find more live examples here. Here is an example usage:

use cw_orch_interchain::prelude::*;
fn main() -> anyhow::Result<()>{
    let mut interchain = MockBech32InterchainEnv::new(
       vec![("juno-1", "juno"), ("osmosis-1", "osmosis")],
    );
    let chain = interchain.get_chain("juno-1")?;
    let contract = Contract::new(chain);
    let response = contract.execution_with_ibc_consequences()?;

    interchain.await_and_check_packets("juno-1", response)?;

    let port_id = PortId::transfer();
    
    let ChannelCreationResult {
        interchain_channel,
        channel_creation_txs,
    } = interchain
        .create_channel(
            "juno-1", 
            "osmosis-1", 
            &port_id, 
            &port_id, 
            "ics20-1",
            Some(cosmwasm_std::IbcOrder::Unordered)
        )?;
    Ok(())
}

IBC Channel creation

cw-orchestrator also provides tooling for creating channels between mock environments. Here is how you do it:

use cw_orch_interchain::prelude::*;
fn main() -> anyhow::Result<()>{
    let port_id = PortId::transfer();
    let mut interchain = MockBech32InterchainEnv::new(
       vec![("juno-1", "juno"), ("osmosis-1", "osmosis")],
    );
    let ChannelCreationResult {
        interchain_channel,
        channel_creation_txs,
    } = interchain
        .create_channel(
            "juno-1", 
            "osmosis-1", 
            &port_id, 
            &port_id, 
            "ics20-1",
            Some(cosmwasm_std::IbcOrder::Unordered)
        )?;
    Ok(())
}
  • The resulting interchain_channel object allows you to identify the channel that was just created. It can be useful to retrieve the channel identifiers for instance (e.g. channel-567)
  • The resulting channel_creation_txs object allows you to identify the different steps of channel creation as well as the IBC packets sent during this creation. This is very useful to analyze the effects of the channel creation on external contracts and structures.

Supported Chains

cw-orchestrator currently explicitly supports the chains in this section:

Support a new Cosmos Chain

Almost all Cosmos Chains can be added to cw-orchestrator. Depending on the on-chain modules (CosmWasm is not available on all chains for instance), action may be forbidden for some chains. However, connection should work out of the box for most of the chains. In order to add a new chain to your project, you can use the following objects:


use cw_orch::environment::{ChainInfo, ChainKind, NetworkInfo};

pub const NEW_NETWORK_INFO: NetworkInfo = NetworkInfo {
    chain_name: "osmosis",
    pub_address_prefix: "osmo",
    coin_type: 118,
};

pub const NEW_CHAIN_INFO: ChainInfo = ChainInfo {
    chain_id: "osmosis-4",
    gas_denom: "uosmo",
    gas_price: 7575.8,
    grpc_urls: &["Some GRPC URLS"],
    lcd_url: None, // Not necessary for cw-orch
    fcd_url: None, // Not necessary for cw-orch
    network_info: NEW_NETWORK_INFO,
    kind: ChainKind::Mainnet,
};

This chain info can then be used inside your project just like any other chain defined inside cw-orch.

Alternatively, we suggest using the grpc_url and gas methods on the DaemonBuilder for quick and dirty fixes to the grpc url and the gas prices if needed.

If you would like to add explicit support for another chain, please feel free to open a PR!

Config Overwrite

If you’re running into issues with a dead gRPC URL, wrong gas price or similar issues then you can use a config file to overwrite those variables. To do so, make a new config file in ~/.cw-orchestrator/networks.toml.

touch ~/.cw-orchestrator/networks.toml

Then you can add the following optional content to the file, replacing the values with the ones you need. The chain-id is used to identify the chain you want to overwrite the values for.

[juno-1]
kind = "mainnet"
chain_id = "juno-1"
gas_denom = "ujuno"
gas_price = 0.075
grpc_urls = ["http://juno-grpc.polkachu.com:12690"]

[juno-1.network_info]
chain_name = "juno"
pub_address_prefix = "juno"
coin_type = 118

So in the example above the configuration is applied for a Daemon built with the JUNO_1 network.

Issues

Each of the gRPC endpoints has been battle-tested for deployments. If you find any issues, please open an issue!

Archway

Archway is a smart contract platform that directly rewards developers for their contributions. With built-in incentives, Archway equips developers with tools to craft and deploy scalable cross-chain dApps. As these dApps enhance the network’s value, developers enjoy further rewards.

Archway Website

pub const ARCHWAY_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "archway",
    pub_address_prefix: "archway",
    coin_type: 118u32,
};

/// Archway Docs: <https://docs.archway.io/resources/networks>
/// Parameters: <https://testnet.mintscan.io/archway-testnet/parameters>
pub const CONSTANTINE_3: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "constantine-3",
    gas_denom: "aconst",
    gas_price: 1000000000000.0,
    grpc_urls: &["https://grpc.constantine.archway.io:443"],
    network_info: ARCHWAY_NETWORK,
    lcd_url: Some("https://api.constantine.archway.io"),
    fcd_url: None,
};

/// Archway Docs: <https://docs.archway.io/resources/networks>
/// Parameters <https://www.mintscan.io/archway/parameters>
pub const ARCHWAY_1: ChainInfo = ChainInfo {
    kind: ChainKind::Mainnet,
    chain_id: "archway-1",
    gas_denom: "aarch",
    gas_price: 1000000000000.0,
    grpc_urls: &["https://grpc.mainnet.archway.io:443"],
    network_info: ARCHWAY_NETWORK,
    lcd_url: Some("https://api.mainnet.archway.io"),
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::ARCHWAY_1.

References

Injective

Injective is a unique blockchain tailored for finance, offering out-of-the-box modules like a fully decentralized orderbook. As an open smart contracts platform, it hosts a suite of decentralized apps designed for optimal user experience. Dive into Injective and unlock efficient capital allocation in decentralized financial markets.

Visit Injective’s Website

pub const INJECTIVE_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "injective",
    pub_address_prefix: "inj",
    coin_type: 60u32,
};

/// <https://docs.injective.network/develop/public-endpoints/#mainnet>
/// <https://www.mintscan.io/injective/parameters>
/// <https://status.injective.network/>
pub const INJECTIVE_1: ChainInfo = ChainInfo {
    kind: ChainKind::Mainnet,
    chain_id: "injective-1",
    gas_denom: "inj",
    gas_price: 500_000_000.0,
    grpc_urls: &["https://sentry.chain.grpc.injective.network:443"],
    network_info: INJECTIVE_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

/// <https://docs.injective.network/develop/public-endpoints/#testnet>
/// <https://testnet.status.injective.network/>
pub const INJECTIVE_888: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "injective-888",
    gas_denom: "inj",
    gas_price: 500_000_000.0,
    grpc_urls: &["https://k8s.testnet.chain.grpc.injective.network:443"],
    network_info: INJECTIVE_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

To interact with contracts on Injective, first enable the eth feature for cw-orchestrator. Injective supports EVM-based addresses, and this will enable their use within cw-orchestrator.

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::INJECTIVE_1.

References

Juno

Juno stands as the central hub for CosmWasm smart contracts, underpinned by the InterWasm DAO. As a global, open-source, and permission-less platform, Juno champions the forefront of CosmWasm development, enabling developers to seamlessly deploy inter-chain smart contracts crafted in Rust. The network’s inclusive design ensures that anyone can innovate and engage with inter-chain applications.

Visit Juno’s Website

pub const JUNO_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "juno",
    pub_address_prefix: "juno",
    coin_type: 118u32,
};

pub const UNI_6: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "uni-6",
    gas_denom: "ujunox",
    gas_price: 0.025,
    grpc_urls: &["http://juno-testnet-grpc.polkachu.com:12690"],
    network_info: JUNO_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const JUNO_1: ChainInfo = ChainInfo {
    kind: ChainKind::Mainnet,
    chain_id: "juno-1",
    gas_denom: "ujuno",
    gas_price: 0.0750,
    grpc_urls: &["http://juno-grpc.polkachu.com:12690"],
    network_info: JUNO_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const LOCAL_JUNO: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "testing",
    gas_denom: "ujunox",
    gas_price: 0.0,
    grpc_urls: &["http://localhost:9090"],
    network_info: JUNO_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::JUNO_1.

References

Kujira

A decentralized ecosystem for protocols, builders and web3 users seeking sustainable FinTech.

Visit Kujira’s Website

pub const KUJIRA_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "kujira",
    pub_address_prefix: "kujira",
    coin_type: 118u32,
};

pub const HARPOON_4: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "harpoon-4",
    gas_denom: "ukuji",
    gas_price: 0.025,
    grpc_urls: &["http://kujira-testnet-grpc.polkachu.com:11890"],
    network_info: KUJIRA_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::KAIYO_1.

References

Landslide

Landslide is the Avalanche-powered Interchain Hub connecting all blockchains to IBC, Web3’s most secure interoperability solution. Expanding user bases and unlocking cross-chain potential.

Visit Landslide’s Website

pub const LANDSLIDE_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "landslide",
    pub_address_prefix: "wasm",
    coin_type: 118u32,
};

pub const LOCAL_LANDSLIDE: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "landslide-test",
    gas_denom: "stake",
    gas_price: 1_f64,
    grpc_urls: &["http://127.0.0.1:9090"],
    network_info: LANDSLIDE_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::LOCAL_LANDSLIDE.

References

Neutron

The most secure CosmWasm platform in Cosmos, Neutron lets smart-contracts leverage bleeding-edge Interchain technology with minimal overhead.

Visit Neutron’s Website

pub const NEUTRON_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "neutron",
    pub_address_prefix: "neutron",
    coin_type: 118u32,
};

/// <https://github.com/cosmos/chain-registry/blob/master/testnets/neutrontestnet/chain.json>
pub const PION_1: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "pion-1",
    gas_denom: "untrn",
    gas_price: 0.075,
    grpc_urls: &["http://grpc-palvus.pion-1.ntrn.tech:80"],
    network_info: NEUTRON_NETWORK,
    lcd_url: Some("https://rest-palvus.pion-1.ntrn.tech"),
    fcd_url: None,
};

/// <https://github.com/cosmos/chain-registry/blob/master/neutron/chain.json>
pub const NEUTRON_1: ChainInfo = ChainInfo {
    kind: ChainKind::Mainnet,
    chain_id: "neutron-1",
    gas_denom: "untrn",
    gas_price: 0.075,
    grpc_urls: &["http://grpc-kralum.neutron-1.neutron.org:80"],
    network_info: NEUTRON_NETWORK,
    lcd_url: Some("https://rest-kralum.neutron-1.neutron.org"),
    fcd_url: None,
};

pub const LOCAL_NEUTRON: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "test-1",
    gas_denom: "untrn",
    gas_price: 0.0025,
    grpc_urls: &["http://localhost:8090"],
    network_info: NEUTRON_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::NEUTRON_1.

References

Nibiru

Nibiru is a breakthrough smart contract platform providing superior throughput, reduced latency, and improved security, all driven by Web Assembly (Wasm) smart contracts. Nibiru simplifies the existing DeFi trading solutions through vertical integration.

Nibiru Website

pub const NIBIRU_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "nibiru",
    pub_address_prefix: "nibi",
    coin_type: 118u32,
};

pub const NIBIRU_ITN_2: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "nibiru-itn-2",
    gas_denom: "unibi",
    gas_price: 0.025,
    grpc_urls: &["https://nibiru-testnet.grpc.kjnodes.com:443"],
    network_info: NIBIRU_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::NIBIRU_ITN_2.

References

Osmosis

Osmosis is a cutting-edge decentralized exchange built on the Cosmos network, designed for seamless asset swaps across various interconnected blockchains. As a trailblazer in AMM protocols, Osmosis empowers users with fast, secure, and efficient cross-chain liquidity solutions. The platform’s innovative approach to DeFi positions it as a cornerstone in the Cosmos ecosystem, enabling anyone to effortlessly tap into the vast potential of inter-chain finance.

Visit Osmosis’s Website

pub const OSMO_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "osmosis",
    pub_address_prefix: "osmo",
    coin_type: 118u32,
};

pub const OSMOSIS_1: ChainInfo = ChainInfo {
    kind: ChainKind::Mainnet,
    chain_id: "osmosis-1",
    gas_denom: "uosmo",
    gas_price: 0.025,
    grpc_urls: &["https://grpc.osmosis.zone:443"],
    network_info: OSMO_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const OSMO_5: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "osmo-test-5",
    gas_denom: "uosmo",
    gas_price: 0.025,
    grpc_urls: &["https://grpc.osmotest5.osmosis.zone:443"],
    network_info: OSMO_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const LOCAL_OSMO: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "localosmosis",
    gas_denom: "uosmo",
    gas_price: 0.0026,
    grpc_urls: &["http://65.108.235.46:9094"],
    network_info: OSMO_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::OSMO_5.

References

Sei

Sei is the fastest Layer 1 blockchain, designed to scale with the industry.

Visit Sei’s Website

pub const SEI_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "sei",
    pub_address_prefix: "sei",
    coin_type: 118u32,
};

pub const LOCAL_SEI: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "sei-chain",
    gas_denom: "usei",
    gas_price: 0.1,
    grpc_urls: &["http://localhost:9090"],
    network_info: SEI_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const SEI_DEVNET_3: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "sei-devnet-3",
    gas_denom: "usei",
    gas_price: 0.1,
    grpc_urls: &["http://sei_devnet-testnet-grpc.polkachu.com:11990"],
    network_info: SEI_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const ATLANTIC_2: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "atlantic-2",
    gas_denom: "usei",
    gas_price: 0.1,
    grpc_urls: &["http://sei-testnet-grpc.polkachu.com:11990"],
    network_info: SEI_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const PACIFIC_1: ChainInfo = ChainInfo {
    kind: ChainKind::Mainnet,
    chain_id: "pacific-1",
    gas_denom: "usei",
    gas_price: 0.1,
    grpc_urls: &["http://sei-grpc.polkachu.com:11990"],
    network_info: SEI_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::SEI_DEVNET_3.

References

Terra

Fueled by a passionate community and deep developer talent pool, the Terra blockchain is built to enable the next generation of Web3 products and services.

Visit Terra’s Website

pub const TERRA_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "terra2",
    pub_address_prefix: "terra",
    coin_type: 330u32,
};

/// Terra testnet network.
/// <https://docs.terra.money/develop/endpoints>
pub const PISCO_1: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "pisco-1",
    gas_denom: "uluna",
    gas_price: 0.015,
    grpc_urls: &["http://terra-testnet-grpc.polkachu.com:11790"],
    network_info: TERRA_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

/// Terra mainnet network.
///<https://docs.terra.money/develop/endpoints>
pub const PHOENIX_1: ChainInfo = ChainInfo {
    kind: ChainKind::Mainnet,
    chain_id: "phoenix-1",
    gas_denom: "uluna",
    gas_price: 0.015,
    grpc_urls: &["http://terra-grpc.polkachu.com:11790"],
    network_info: TERRA_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

/// Terra local network.
/// <https://docs.terra.money/develop/guides/initial/#next-steps-localterra-or-testnet>
pub const LOCAL_TERRA: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "localterra",
    gas_denom: "uluna",
    gas_price: 0.15,
    grpc_urls: &["http://localhost:9090"],
    network_info: TERRA_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::PHOENIX_1.

References

Rollkit

Rollkit is the open modular framework for sovereign rollups. Their mission is to empower developers to quickly innovate and create entire new classes of rollups with minimal tradeoffs.

Visit Rollkit’s Website

pub const ROLLKIT_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "rollkit",
    pub_address_prefix: "wasm",
    coin_type: 118u32,
};

pub const LOCAL_ROLLKIT: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "celeswasm",
    gas_denom: "uwasm",
    gas_price: 0.025,
    grpc_urls: &["http://localhost:9290"],
    network_info: ROLLKIT_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

pub const ROLLKIT_TESTNET: ChainInfo = ChainInfo {
    kind: ChainKind::Local,
    chain_id: "rosm",
    gas_denom: "urosm",
    gas_price: 0.025,
    grpc_urls: &["http://grpc.rosm.rollkit.dev:9290"],
    network_info: ROLLKIT_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::LOCAL_ROLLKIT.

Compatibility

This integration has been tested using the Rollkit Setup with CosmWasm. The following versions were used:

  • Mocha testnet node: celestia/v0.15.0.beta.1
  • Rollup node: wasmd/v0.50.0 (which opens up port 9290 locally)
  • In order to use cw-orchestrator with this setup, the LOCAL_MNEMONIC environment variable needs to be assigned. You can find the right value for this mnemonic in the beginning of the console logs generated by the bash init.sh command in the official RollKit docs.

Please contact us on Discord if you have any issues.

References

Union

Union is the first infrastructure layer that unifies thousands of networks, unlocking horizontal scalability for any protocol.

Visit Union’s Website

pub const UNION_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "union",
    pub_address_prefix: "union",
    coin_type: 118,
};

pub const UNION_TESTNET: ChainInfo = UNION_TESTNET_8;

pub const UNION_TESTNET_8: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "union-testnet-8",
    gas_denom: "muno",
    gas_price: 0.000025,
    grpc_urls: &["https://grpc.testnet-8.union.build:443"],
    network_info: UNION_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::UNION_TESTNET.

References

XION

XION empowers developers and brands to create frictionless Web3 experiences, with a Generalized Abstraction layer that removes technical barriers for all users.

Visit XION’s Website

pub const XION_NETWORK: NetworkInfo = NetworkInfo {
    chain_name: "xion",
    pub_address_prefix: "xion",
    coin_type: 118u32,
};

pub const XION_TESTNET_1: ChainInfo = ChainInfo {
    kind: ChainKind::Testnet,
    chain_id: "xion-testnet-1",
    gas_denom: "uxion",
    gas_price: 0.0,
    grpc_urls: &["http://xion-testnet-grpc.polkachu.com:22390"],
    network_info: XION_NETWORK,
    lcd_url: None,
    fcd_url: None,
};

Usage

See how to setup your main function in the main function section. Update the network passed into the Daemon builder to be networks::XION_TESTNET_1.

References

Continuous Integration and Deployment

One of the tools that can improve your developer productivity drastically is setting up pipelines for your contract deployments.

cw-orchestrator does not currently add additional support for actions, but an example using the directory structure of the cw-orchestrator Github repository can be found below

# .github/workflows/deploy.yml
---
name: Deploy Contracts  
on:  
  # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch  
  workflow_dispatch:  
  push:  
    branches: [ 'mainline' ]  
    paths:  
      - 'contracts/src/**/*.rs'  
      - '.github/workflows/deploy.yml'  
  
env:
  VERSION_CONTROL_ADDRESS: juno16enwrxhdtsdk8mkkcaj37fgp37wz0r3err4hxfz52lcdyayexnxs4468mu  
  STATE_FILE: "./state.json"  # Optional
  ARTIFACTS_DIR: "./target/wasm32-unknown-unknown/release" # Optional
  SCHEMA_DIR: "./schema"  
  
jobs:  
  deploy:  
    runs-on: ubuntu-latest  
    steps:  
      - uses: actions/checkout@v3  
      - uses: actions-rs/toolchain@v1  
        with:  
          toolchain: nightly  
          target: wasm32-unknown-unknown  
          override: true  
      - name: Run cargo wasm  
        uses: actions-rs/cargo@v1  
        with:  
          command: build  
          args: --package counter-app --release --target wasm32-unknown-unknown  
        env:  
          RUSTFLAGS: '-C link-arg=-s'  

      - name: Run deployment script  
        uses: actions-rs/cargo@v1  
        with:  
          command: run  
          args: --package scripts --bin deploy_app  
        env:  
          CHAIN: "juno"  
          DEPLOYMENT: "debugging"  
          NETWORK: "local"  
          RUST_LOG: info  
          ARTIFACTS_DIR: ${{ env.ARTIFACTS_DIR }}  
          STATE_FILE: ${{ env.STATE_FILE }}  
  
          VERSION_CONTROL_ADDRESS: ${{ env.VERSION_CONTROL_ADDRESS }}  
          TEST_MNEMONIC: ${{ secrets.TEST_MNEMONIC }}  

      - name: Upload deployment daemon state  
        uses: actions/upload-artifact@v2  
        with:  
          name: deployment.json  
          path: ${{ env.STATE_FILE }}  
      - name: Upload WASM  
        uses: actions/upload-artifact@v2  
        with:  
          # TODO: name env or from cargo  
          name: counter_app.wasm  
          path: ${{ env.ARTIFACTS_DIR }}/counter_app.wasm

Contributing to cw-orchestrator

Thank you for considering to contribute to the cw-orchestrator project! We appreciate your support and welcome contributions to help improve this multi-environment CosmWasm smart-contract scripting library. This document provides guidelines and instructions on how to contribute to the project effectively.

Table of Contents

Getting Started

To get started with contributing to the cw-orchestrator project, you should first familiarize yourself with the repository structure and the codebase. Please read the project’s README to understand the purpose, features, and usage of the cw-orchestrator library as well as its documentation.

How to Contribute

There are multiple ways to contribute to the cw-orchestrator project, including reporting bugs, suggesting enhancements, and submitting code contributions.

Reporting Bugs

If you encounter any bugs or issues while using the cw-orchestrator library, please report them by creating a new issue in the issue tracker. When reporting a bug, please provide the following information:

  • A clear and descriptive title
  • A detailed description of the issue, including steps to reproduce it
  • Any relevant logs, error messages, or screenshots
  • Information about your environment, such as the OS, software versions, and hardware specifications

Suggesting Enhancements

We welcome suggestions for new features or improvements to the existing functionality of the cw-orchestrator library. To suggest an enhancement, create a new issue in the issue tracker with the following information:

  • A clear and descriptive title
  • A detailed explanation of the proposed enhancement, including its benefits and potential use cases
  • If applicable, any examples or mockups of the proposed feature

Code Contributions

To contribute code to the cw-orchestrator project, please follow these steps:

  1. Fork the repository to your own GitHub account.
  2. Clone your fork to your local machine.
  3. Create a new branch for your changes using the git checkout -b feature/your-feature-name command.
  4. Make your changes and commit them with a clear and concise commit message.
  5. Push your branch to your fork on GitHub.
  6. Create a new pull request against the main branch of the cw-orchestrator repository.

Pull Requests

When submitting a pull request, please make sure that your code follows the Style Guide and that all tests pass. Please provide a detailed description of your changes, including the motivation for the changes and any potential impact on the project. This will help maintainers review your pull request more effectively.

Style Guide

The cw-orchestrator project follows the Rust coding style and conventions. Please ensure that your code adheres to these guidelines to maintain consistency and readability throughout the codebase.

  • Use proper indentation (4 spaces) and consistent formatting (cargo fmt).
  • Write descriptive variable and function names.
  • Use comments to explain complex or non-obvious code.
  • Follow the Rust API Guidelines for API design.
  • Add documentation for public functions, types, and modules.
  • Write doc tests for public functions and methods.

Community

To join the cw-orchestrator community, please join the Abstract Discord server and the #cw-orchestrator channel. You can also follow the project on Twitter and GitHub.

References

Easy, right? Try building your contracts with Abstract for the same experience with smart contracts. Get started here.