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 thecw_orch::daemon::networks
module. You can also add additional chains yourself by simply defining a variable of typeChainInfo
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 atokio::Runtime
. Be careful because this builder is not usable in anasync
function. In such function, you can useDaemonAsync
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 theenv_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_id
variable (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 thedaemon
object will interact with. Documentation Linkdeployment_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 Linkhandle
(optional) is thetokio
runtime handled used to await async functions.cw-orch
provides a default runtime if not specified. Documentation Linkmnemonic
(optional) is the mnemonic that will be used to create the sender associated with the resultingDaemon
Object. It is not compatible with thesender
method. Documentation Linkstate
(optional) is used when you want to specify an existingDaemonState
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:
CosmosSender::set_authz_granter
allows you to use the authz module. If this method is used, the sender will send transactions wrapped inside an authz message sent by the specifiedgranter
. More info on the authz module. Documentation LinkCosmosSender::set_fee_granter
allows you to use the fee-grant module. If this method is used, the sender will try to pay for transactions using the specifiedgranter
. More info on the fee grant module. Documentation Link
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(())
}
}