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

Interacting with the daemon is really straightforward. How to creating a daemon instance is shown below:

    use cw_orch::prelude::*;
    use tokio::runtime::Runtime;
    // We start by creating a runtime, which is required for a sync daemon.
    let runtime = Runtime::new().unwrap();

    // We can now create a daemon. This daemon will be used to interact with the chain.
    let daemon = Daemon::builder()
        // set the network to use
        .chain(cw_orch::daemon::networks::LOCAL_JUNO) // chain parameter
        .handle(runtime.handle()) // handler 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.

  • The handle parameter is a tokio runtime handle.

    If you don't know what that means

    You don’t need to know all the intricacies of tokio and rust-async to use daemons. If you don’t have time to learn more, you can simply use the snippet above and not touch the runtime creation. However for more advanced daemon usage and also for your culture, don’t hesitate to learn about those wonderful processes and their management.

    If you know what that means

    This handler is used because all the front-facing daemon methods are synchronous. However everything that’s happening in the background is asynchronous. This handle is used exclusively to await asynchronous function:

        runtime.block_on(...)
    

    Because creating runtimes is a costly process, we leave the handler creation and management to the user.

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.

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.get_chain().sender()),
        None,
    );
    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": {
    "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": {
    "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 (required) is the tokio runtime handled used to await async functions. 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
  • sender (optional) is the sender that will be uses with the resulting Daemon Object. It is not compatible with the mnemonic method. Documentation Link
  • authz_granter (optional) allows you to use the authz module. If this field is specified, the sender will send transactions wrapped inside an authz message sent by the specified granter. More info on the authz module. Documentation Link
  • fee_granter (optional) allows you to use the fee-grant module. If this field is specified, the sender will try to pay for transactions using the specified granter. More info on the fee grant module. Documentation Link
  • hd_index (optional) allows to set the index of the HD path for the account associated with the Daemon object. More info on the derivation path and index. Documentation Link

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

Keep in mind that those 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.

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.wallet();
      rt.block_on(wallet.bank_send("<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 = "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.get_chain();

        // 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, None).unwrap();

        Ok(())
    }
}