Fully On-Chain Scheduler via Native Messaging on Avalanche, Polkadot & Cosmos: The Cross-Subnet Ping-Pong Pattern
One of the biggest hurdles in decentralized application development is creating schedulers that can initiate on-chain actions without relying on off-chain keeper services. Keepers introduce centralization risks and can suspend operation unexpectedly, halting any time-based logic.
By leveraging native cross-chain messaging—whether Avalanche Warp Messaging (AWM), Polkadot's XCM, or Cosmos IBC—we can build a fully on-chain, self-sustaining scheduler that requires zero external dependencies. This "Ping-Pong" mechanism has three core components:
- Asynchronous messages sent natively between chains or subnets
- Local task execution when each message arrives
- Automated forwarding to keep the loop alive
Below we describe the pattern, show example code for Avalanche, Polkadot and Cosmos, explain why the loop must run continuously (no on-chain "delay" or "sleep"), and compare scheduling costs for a one-hour period on each network.
- Seed: when the first task is scheduled, Chain A sends a "check_tasks" message to Chain B.
- Process: Chain B inspects and executes due tasks, then—if tasks remain—sends a "check_tasks" message back to Chain A.
- Repeat: each incoming message triggers local execution and a follow-up ping. The two chains ping-pong continuously until all tasks are done.
- Continuous Execution: because on-chain code cannot "sleep," the scheduler runs as fast as each cross-chain transaction finalizes.
- Fully decentralized—no off-chain keepers or relayers, everything runs in consensus.
- Trustless—execution enforced by the blockchain itself.
- Predictable costs—native messaging fees are transparent and generally lower than third-party keeper subscriptions.
contract SchedulerA {
struct Task {
address target;
bytes data;
uint256 executeAt;
bool done;
}
mapping(uint256 => Task) public tasks;
uint256 public taskCount;
uint256 public lastPing;
uint256 constant MIN_INTERVAL = 30;
function receiveWarp(bytes memory) external {
require(block.timestamp >= lastPing + MIN_INTERVAL, "Too frequent");
lastPing = block.timestamp;
_processTasks();
if (_hasPending()) {
sendWarpMessage(subnetB, "check_tasks");
}
}
function _processTasks() internal {
for (uint256 i = 0; i < taskCount; i++) {
if (!tasks[i].done && block.timestamp >= tasks[i].executeAt) {
(bool success,) = tasks[i].target.call(tasks[i].data);
tasks[i].done = success;
}
}
}
function _hasPending() internal view returns (bool) {
for (uint256 i = 0; i < taskCount; i++) {
if (!tasks[i].done) return true;
}
return false;
}
}// Pseudocode for a Substrate pallet
fn on_xcm(origin: OriginFor<T>, msg: Xcm<()>) -> DispatchResult {
Scheduler::<T>::process_tasks()?;
if Scheduler::<T>::has_pending() {
XcmPallet::send_xcm(
Destination::X1(Parachain(PARACHAIN_B_ID.into())),
Xcm(vec![Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 1_000_000_000,
call: ("check_tasks", ()).encode().into(),
}]),
)?;
}
Ok(())
}// Pseudocode for a CosmWasm contract
pub fn ibc_packet_receive(
deps: DepsMut,
_env: Env,
_msg: IbcPacket
) -> IbcReceiveResponse {
Scheduler::process_tasks(deps.storage, &_env.block)?;
if Scheduler::has_pending(deps.storage) {
return IbcReceiveResponse::new()
.add_packet(IbcPacket::new(
to_chain_b_port(),
b"check_tasks".to_vec(),
// timeouts etc.
));
}
IbcReceiveResponse::default()
}Extend beyond two networks into a ring topology: Chain A → Chain B → Chain C → Chain A. Each chain forwards to the next, boosting availability and distributing load. New chains can join by inserting themselves into the ring.
Because the scheduler loop cannot pause on-chain, it executes one ping per cross-chain transaction. Each ping costs the native messaging fee of the source chain. The effective "interval" between pings equals that chain's block finalization time.
- Block finalization time: 2 seconds
- Cost per ping: 0.001 AVAX
- Pings per hour: 3600 s / 2 s = 1800
- Total hourly cost: 1800 × 0.001 = 1.8 AVAX (≈ $36 at $20/AVAX)
- Block time: 6 seconds
- Average XCM ping fee: ~0.01 DOT
- Pings per hour: 3600 / 6 = 600
- Total hourly cost: 600 × 0.01 = 6 DOT (≈ $24 at $4/DOT)
- Block time: 5 seconds
- Cost per ping: 0.005 ATOM
- Pings per hour: 3600 / 5 = 720
- Total hourly cost: 720 × 0.005 = 3.6 ATOM (≈ $14.40 at $4/ATOM)
- Real-Time Gaming Events: event triggers every few seconds or minutes
- Short-Lived Auctions: NFT bids over 15–30 minutes
- Micro-Loan Expiry: flash loans with sub-hour deadlines
By abstracting scheduling into a native cross-chain messaging pattern, we eliminate all off-chain dependencies, achieve trustless execution, and integrate seamlessly across multiple chains. Whether on Avalanche with AWM, Polkadot via XCM or any Cosmos SDK chain over IBC, the Ping-Pong scheduler provides truly decentralized, continuous automation.