- Docs
- Developers
- Callback module
Introduction
The callback module allows smart contracts to schedule transactions at designated block heights, enhancing automation and decreasing the dependency on intermediary solutions. This feature also operates in a permissionless manner which promotes greater decentralization of dapps on Archway.
Smart contracts are typically triggered by users or other contracts. Some dapps however depend on third parties for initiating transactions, with needs ranging from price balancing arbitrage bots, vesting distribution of funds, auto-compounding of rewards etc. The callback module addresses these dependencies by providing a minimal, trust-minimized scheduler service, allowing for autonomous operations within the Archway ecosystem.
How does it work?
A callback can happen at the end of every block after all the transactions within that block have been executed. The Archway protocol itself will execute these actions on your contract’s behalf based on the block height selected.
When a transaction is being executed traditionally, the user would need to pay a transaction fee but since executions from the callback module are executed by the Protocol, no transaction fees are required but this could lead to spam and the reason a registration fee is required for registering callbacks. The registration fee is calculated as:
registrationFee = txFeeForMaxGas + blockReservationFee + futureReservationFee
- txFeeForMaxGas: This is based on the maximum gas that can be consumed by a callback. If the gas consumed exceeds this value, the callback will fail automatically.
- futureReservationFee: The further in the future you want your callback the more you will have to pay.
- blockReservationFee: Fee to reserve the block. The more callbacks that are registered in a block, the more the dev has to pay.
There is an endpoint that gets the reservation fee so you don't need to calculate it yourself:
- API:
https://api.constantine.archway.io/archway/callback/v1/estimate_callback_fees?block_height=<blockheight>
- CLI:
archwayd q callback estimate-callback-fees <blockheight>
This guide will look at a simple example based on the Increment contract from the My First Dapp guide.
Callback registration
This can be done via the archwayd CLI or via your smart contract using the correct stargate message.
via CLI
If you are using the archwayd CLI, the command would be as follows:
archwayd tx callback request-callback <contract-address> <job-id> <callback-height> <fee-amount>
- job-id: The is added to allow the contract to have context on the callback.
- callback-height: This is the block height at which the callback is scheduled to be executed.
- contract-address: The contract that should be called to execute the callback.
- fee-amount: This is the registration fee highlighted above.
via smart contract
Within your smart contract you will need to set up an action that executes the correct stargate message for registering a callback. You can find the proto message signature here. The following is an example.
Add the following to your Cargo.toml
file under dependencies
to add the prost
functionality:
[dependencies]
prost = "0.12.6"
prost-types = "0.12.6"
You could then add the following to your state.rs
file:
#[derive(Clone, PartialEq, ::prost::Message)]pub struct ProstCoin { #[prost(string, tag = "1")] pub denom: ::prost::alloc::string::String, #[prost(string, tag = "2")] pub amount: ::prost::alloc::string::String,}#[allow(clippy::derive_partial_eq_without_eq)]#[derive(Clone, PartialEq, ::prost::Message)]pub struct MsgRequestCallback { #[prost(string, tag = "1")] pub sender: ::prost::alloc::string::String, #[prost(string, tag = "2")] pub contract_address: ::prost::alloc::string::String, #[prost(uint64, tag = "3")] pub job_id: u64, #[prost(int64, tag = "4")] pub callback_height: i64, #[prost(message, required, tag = "5")] pub fees: ProstCoin,}impl From<cosmwasm_std::Coin> for ProstCoin { fn from(coin: cosmwasm_std::Coin) -> Self { ProstCoin { denom: coin.denom, amount: coin.amount.to_string(), } }}impl From<ProstCoin> for cosmwasm_std::Coin { fn from(prost_coin: ProstCoin) -> Self { cosmwasm_std::Coin { denom: prost_coin.denom, amount: prost_coin.amount.parse().unwrap_or_default(), } }}
You would then add this message to the msg.rs
file:
#[cw_serde]pub enum ExecuteMsg { Register { job_id: u64, callback_height: i64 },}
This final piece would be added to your contract.rs
file:
#[cfg_attr(not(feature = "library"), entry_point)]pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg,) -> Result<Response, ContractError> { match msg { ExecuteMsg::Register { job_id, callback_height } => execute::register(env, job_id, callback_height, info.funds[0].clone()), }}pub mod execute { use super::*; pub fn register( env: Env, job_id: u64, callback_height: i64, funds: Coin, ) -> Result<Response, ContractError> { let contract_address = env.contract.address.to_string(); let regsiter_msg = MsgRequestCallback { sender: contract_address.clone(), contract_address: contract_address.clone(), job_id: job_id.clone(), callback_height: callback_height.clone(), fees: ProstCoin::from(funds), }; let register_stargate_msg = CosmosMsg::Stargate { type_url: "/archway.callback.v1.MsgRequestCallback".to_string(), value: Binary::from(prost::Message::encode_to_vec(®siter_msg)), }; Ok(Response::new() .add_attribute("action", "register") .add_message(register_stargate_msg)) }}
Here's an example of the command to execute the registration of the callback:
archway contracts execute callbacktest --amount 140980000000000000aconst --args '{"register": {"job_id": 1, "callback_height": 6799600}}'
Accepting callbacks
Messages
The following message could be added to the "msg.rs" file:
#[cw_serde]pub enum SudoMsg { Callback { job_id: u64 },}
This code defines a Callback message with a job ID that the contract can use. Note that only the Archway protocol can send requests to SudoMsg endpoints, meaning that only the protocol can trigger this message.
Contract
The following would be added to the "contract.rs" file:
#[cfg_attr(not(feature = "library"), entry_point)]pub fn sudo( deps: DepsMut, _env: Env, msg: SudoMsg,) -> Result<Response, ContractError> { match msg { SudoMsg::Callback { job_id } => sudo::handle_callback(deps, job_id), }}pub mod sudo { use super::*; use std::u64; pub fn handle_callback(deps: DepsMut, job_id: u64) -> Result<Response, ContractError> { STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { if job_id == 0 { state.count -= 1; }; if job_id == 1 { state.count += 1; }; if job_id == 2 { return Err(ContractError::SomeError {}); } Ok(state) })?; Ok(Response::new().add_attribute("action", "handle_callback")) }}
A new "sudo" entry point is created that captures the Callback message create before. The function to handle the callback uses the job ID to decide what action to take which shows how the job ID can actually be used to enable conditional code execution.
Checking Callback Execution
Callback execution is handled by the protocol and occurs in the endblocker of the callback module, so it does not create an official transaction that appears in a block explorer. To ensure that a callback was executed and to determine if the execution was successful or encountered an error, you can utilize events triggered by the callback module or subscribe to the cwerrors module.
Listening for Callback Events
Two types of events are emitted during the callback process:
- Successful Callback Execution:
- When a callback is successfully executed, an event is triggered.
- You can find the event definition in the endblocker logic.
- Failed Callback Execution:
- If the callback execution fails, a different event is triggered.
- The event definition for a failed callback can be found here.
Subscribing to Callback Events
To listen for these events, you can subscribe to the appropriate events emitted by the Archway protocol. This can be done using tools such as WebSockets, RPC, or any event subscription mechanism.
Here are the key resources and event types you need to monitor:
- Callback Success Event:
archway.callback.v1.EventTypeCallbackSuccess
- Callback Failure Event:
archway.callback.v1.EventTypeCallbackFailure
For more details on the event structure, refer to the callback events protobuf definition.
Additionally, you can subscribe to events from the cw-errors
module to get detailed information on errors that occur during callback execution.
Callback test contract
You can find more details in the Callback test contract here.