This site requires Javascript to be enabled.

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, and 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 endpoint that executes the correct stargate message for registering a callback. The following is an example:

// This could be added to the state.rs file/// MsgRequestCallback is used to register a callback.#[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(uint64, tag = "2")]    pub job_id: u64,    #[prost(uint64, tag = "3")]    pub callback_height: u64,}// This could be added as one of the execute entrypoint functions in your contract.rs filepub fn register(deps: Deps, env: Env, job_id: u64, callback_height: u64, 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: funds.clone()  };  let register_stargate_msg = CosmosMsg::Stargate {    type_url: "/archway.callback.v1.MsgRequestCallback".to_string(),    value: Binary::from(prost::Message::encode_to_vec(&regsiter_msg)),  };  Ok(Response::new()    .add_attribute("action", "register")    .add_message(register_stargate_msg))}

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.

Callback test contract

You can find more details in the Callback test contract here.

Drop Camp is here!

Join the queue and be one of the first to get in!.

Go Camping ↗