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(())
    }
}