Skip to main content
In Part 1, you learned how to schedule future smart contract calls using Hedera’s Schedule Service. Now, let’s build something more sophisticated: a capacity-aware DeFi rebalancer that automatically adjusts its scheduling strategy based on network conditions. What makes this advanced? Most blockchain automation requires off-chain infrastructure to periodically check and execute operations. Even with Hedera’s Schedule Service, naively scheduling all operations at fixed intervals can create network congestion when many contracts compete for the same execution window. This tutorial demonstrates how to build intelligent on-chain automation that:
  • Queries network capacity before scheduling operations
  • Uses exponential backoff with jitter to find optimal execution times
  • Self-sustains by automatically rescheduling after each execution
  • Gracefully handles network congestion and capacity constraints
  • Supports multiple scheduling methods (scheduleCall and scheduleCallWithPayer)
  • Demonstrates one-shot immediate execution using executeCallOnPayerSignature
You can take a look at the complete code in the tutorial-hss-rebalancer-capacity-aware repository.

What You’ll Build

A RebalancerCapacityAware contract that:
  1. Starts a rebalancing loop with configurable intervals
  2. Checks network capacity using hasScheduleCapacity() before scheduling
  3. Applies intelligent retry logic with exponential backoff and randomized jitter
  4. Supports two scheduling methods: scheduleCall and scheduleCallWithPayer
  5. Executes rebalances automatically via scheduled transactions
  6. Reschedules itself after each execution, creating a self-sustaining loop
  7. Demonstrates one-shot execution using executeCallOnPayerSignature
  8. Can be stopped by canceling pending scheduled transactions
This pattern is perfect for:
  • DeFi vault rebalancing
  • Periodic token distributions
  • Automated treasury management
  • Time-based protocol operations

Prerequisites


Table of Contents

  1. Setup Project
  2. Step 1: Understanding the Architecture
  3. Step 2: Create the Rebalancer Contract
  4. Step 3: Deploy the Contract
  5. Step 4: Configure the Contract
  6. Step 5: Start Rebalancing
  7. Step 6: Monitor Rebalancing Operations
  8. Step 7: Stop Rebalancing
  9. Step 8: One-Shot Immediate Execution (Optional)
  10. Step 9: Run Tests (Optional)
  11. Conclusion
  12. Additional Resources

Setup Project

If you completed Part 1, you can use the same project. Otherwise, set up a new project:
Make sure to select “Hardhat 3 -> Typescript Hardhat Project using Mocha and Ethers.js” and accept the default values. Hardhat will configure your project correctly and install the required dependencies.
Key differences in Hardhat 3:
  • compile → build
    npx hardhat compile is now npx hardhat build. This is the big one. The v3 migration guide explicitly shows using the build task.
  • project init switch
    v2 commonly used npx hardhat or npx hardhat init to bootstrap. In v3 it’s npx hardhat --init.
  • keystore helper commands are new
    v3’s recommended flow includes a keystore plugin with commands like npx hardhat keystore set HEDERA_RPC_URL and npx hardhat keystore set HEDERA_PRIVATE_KEY. These weren’t standard in v2.
  • Foundry-compatible Solidity tests
    In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
  • Enhanced Network Management
    v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
📚 Learn more from the official Hardhat documentation.
Before we make any changes to our Hardhat configuration file, let’s set some configuration variables we will be referring to within the file later.
For HEDERA_RPC_URL, we’ll have https://testnet.hashio.io/api
For HEDERA_PRIVATE_KEY, enter the HEX Encoded Private Key for your ECDSA account from the Hedera Portal. We also need a second private key for testing purposes:
For HEDERA_PRIVATE_KEY_2, enter another HEX Encoded Private Key for a second ECDSA account. Now let’s remove the default contracts and scripts that come with the Hardhat project:

Install Dependencies

Next, install the required dependencies:
Note that we are installing the latest code from the main branch when we install @hiero-ledger/hiero-contracts. This also gets installed at @hashgraph/smart-contracts so we can easily call these contracts from our own contract. Configure hardhat.config.ts:
hardhat.config.ts

Step 1: Understanding the Architecture

Before diving into code, let’s understand the key concepts that make this rebalancer capacity-aware.

The Capacity Problem

When multiple contracts schedule transactions for the same future time:
  • Network capacity for that second may be exhausted
  • Subsequent scheduling attempts fail
  • Operations get delayed or fail entirely

The Solution: Capacity-Aware Scheduling

Our rebalancer uses three key Hedera features: 1. hasScheduleCapacity(expirySecond, gasLimit)
  • Queries if a specific future second can accept a scheduled transaction
  • Returns true if capacity is available, false otherwise
  • Allows contracts to “probe” future availability
2. Exponential Backoff with Jitter
  • If desired time lacks capacity, try progressively later times: +1s, +2s, +4s, +8s…
  • Add random jitter to avoid “thundering herd” where all contracts retry at the same moment
  • Spreads load across multiple seconds
3. Hedera PRNG System Contract (0x169)
  • Provides pseudorandom seeds for jitter calculation
  • Enables true on-chain randomness without external oracles
  • Each contract gets different jitter, naturally distributing load

Scheduling Methods

This tutorial demonstrates three different scheduling approaches:
Important: executeCallOnPayerSignature is not supported for recursive/looped/cron operations due to Hedera mainnet recursion protection (NO_SCHEDULING_ALLOWED_AFTER_SCHEDULED_RECURSION). Use scheduleCall or scheduleCallWithPayer for all automated recurring scheduling.

How It Works Together

Why This MattersOn traditional EVM chains, you’d need:
  • Off-chain service to monitor network congestion
  • Manual intervention to adjust timing
  • External keeper network that understands capacity
On Hedera, the contract itself is capacity-aware and self-adjusting!

Step 2: Create the Rebalancer Contract

Create RebalancerCapacityAware.sol in your contracts directory:
contracts/RebalancerCapacityAware.sol
How It Works
  1. setPayer(): Configures which address will pay for scheduled transactions (typically the contract itself)
  2. setSchedulingMethod(): Switches between scheduleCall (false) and scheduleCallWithPayer (true)
  3. startRebalancing(): Initializes the loop and schedules the first rebalance using capacity-aware logic
  4. _findAvailableSecond(): The core capacity-awareness algorithm:
    • First checks if desired time has capacity
    • If not, tries exponentially increasing delays: +1s, +2s, +4s, +8s…
    • Adds random jitter (0 to baseDelay) to each attempt
    • Uses Hedera’s PRNG for true on-chain randomness
  5. rebalance(): Executed automatically by scheduled transactions:
    • Increments counter (in real DeFi, would perform actual rebalancing)
    • Schedules next execution using capacity-aware logic
    • Creates self-sustaining loop
  6. stopRebalancing(): Cancels pending schedule and marks loop inactive
  7. demoImmediateExecution(): Demonstrates one-shot execution using executeCallOnPayerSignature
  8. HBAR Requirement: Contract must hold HBAR to pay for all scheduled executions
Build the contract:

Step 3: Deploy the Contract

Create deploy. ts in the scripts directory:
scripts/deploy.ts
Deploy:
Copy the deployed contract address and set it as an environment variable for the next steps.
Expected output:
Set the contract address as an environment variable:
In order to decode events emitted from the contract, the contract must be verified.
You can then upload the verify-bundles/RebalancerCapacityAware/metadata.json file to Hashscan to verify this contract.

Step 4: Configure the Contract

Before starting the rebalancing loop, you need to configure the payer and scheduling method.

Set the Contract as Payer

Create setPayer.ts in the scripts directory:
scripts/setPayer. ts
Run the script:

Choose a Scheduling Method

You have two options for scheduling. Choose one: Option 1: scheduleCall (default) Create setSchedulingMethodScheduleCall.ts:
scripts/setSchedulingMethodScheduleCall.ts
Option 2: scheduleCallWithPayer (contract as payer) Create setSchedulingMethodScheduleCallWithPayer.ts:
scripts/setSchedulingMethodScheduleCallWithPayer. ts

Step 5: Start Rebalancing

Create startRebalancing.ts in the scripts directory:
scripts/startRebalancing.ts
Run the script:
Expected output:
What’s Happening
  1. startRebalancing(15) calculates desired time: now + 15 seconds
  2. Contract checks: hasScheduleCapacity(desiredTime, 2_000_000)?
  3. If capacity available → schedules at desired time
  4. If not → applies exponential backoff with jitter to find available slot
  5. Emits RebalancingStarted with actual scheduled time and scheduling method
  6. After ~15 seconds, network automatically executes rebalance()
  7. rebalance() schedules next execution → creates self-sustaining loop

Step 6: Monitor Rebalancing Operations

Create monitorRebalancing. ts to observe the rebalancing loop:
scripts/monitorRebalancing.ts
Run the monitoring script:
You’ll see output like:
Note that the Rebalance Count increments every ~15 seconds as scheduled transactions execute automatically. When the contract runs out of HBAR, scheduling will fail, and the count will stop increasing however the state remains Active: true until you explicitly stop rebalancing.

Check Contract Config

You can also create a simple script to check the current configuration:
scripts/getConfig.ts
With output like:

View Events on HashScan

Navigate to your contract’s events page to see: RebalanceScheduled Events:
  • Shows when capacity-aware scheduling found an available slot
  • chosenTime === desiredTime means ideal time had capacity
  • chosenTime > desiredTime means backoff was needed
  • schedulingMethod shows which method was used
RebalanceExecuted Events:
  • Confirms automatic execution by the network
  • Tracks total rebalance operations performed
View live events at: https://hashscan.io/testnet/contract/$CONTRACT_ADDRESS/events

Step 7: Stop Rebalancing

Create stopRebalancing.ts to halt the loop:
scripts/stopRebalancing.ts
Run:
Expected output:
What Happened
  1. stopRebalancing() called deleteSchedule(lastScheduleAddress)
  2. Pending scheduled transaction was canceled (best effort)
  3. config.active set to false
  4. Even if a scheduled rebalance() executes, the require(config.active) check prevents further scheduling
  5. Loop is fully stopped

Step 8: One-Shot Immediate Execution (Optional)

This demo shows how to use executeCallOnPayerSignature for a single, immediate function call. This method is not loopable due to Hedera’s recursion protection. Create demoImmediateExecution.ts:
scripts/demoImmediateExecution.ts
Make sure the payer is set first, then run:
With output like:
You should see a DemoActionExecuted event emitted.

Step 9: Run Tests (Optional)

You can find both types of tests in the tutorial-hss-rebalancer-capacity-aware repository. You will find the following files: The repository includes both Solidity unit tests and TypeScript integration tests.

Solidity Unit Tests (contracts/RebalancerCapacityAware.t.sol)

These tests validate:
  • Initial state: Verifies contract deploys with inactive configuration
  • Payer configuration: Tests setting and changing the payer address
  • Scheduling method switching: Verifies switching between scheduleCall and scheduleCallWithPayer
  • Start/stop logic: Confirms only inactive rebalancers can be started and active ones can be stopped
  • Configuration validation: Ensures interval must be greater than zero
  • HBAR handling: Verifies contract can receive HBAR for funding scheduled operations
  • State management: Tests that rebalance count and timestamps are properly maintained

TypeScript Integration Tests (test/RebalancerCapacityAware.ts)

These tests run against Hedera testnet and validate:
  • Deployment and funding: Deploys with substantial HBAR balance and validates initial state
  • scheduleCall method: Tests automated recurring rebalancing with scheduleCall
  • scheduleCallWithPayer method: Tests automated recurring rebalancing with scheduleCallWithPayer (contract as payer)
  • executeCallOnPayerSignature: Demonstrates one-shot immediate execution
  • deleteSchedule: Verifies schedule deletion via stopRebalancing
  • Capacity awareness: Tests that the contract successfully finds available time slots using hasScheduleCapacity
  • Input validation: Tests error handling for invalid inputs
  • Scheduling method switching: Verifies switching between scheduling methods
Run the tests:
You can also run both the solidity and mocha tests altogether:
Which should output something like:

Conclusion

You’ve built a sophisticated capacity-aware DeFi rebalancer that demonstrates advanced patterns with Hedera’s Schedule Service! In this tutorial, you learned how to:
  • Query network capacity using hasScheduleCapacity()
  • Implement exponential backoff with randomized jitter
  • Use Hedera’s PRNG for true on-chain randomness
  • Build self-sustaining loops that automatically reschedule
  • Choose between scheduling methods: scheduleCall vs scheduleCallWithPayer
  • Handle one-shot execution using executeCallOnPayerSignature
  • Handle network congestion gracefully
  • Cancel scheduled operations when needed

Key Takeaways

  • Capacity-aware scheduling prevents network congestion. Contracts cooperate with the network’s throttling model
  • Exponential backoff + jitter distributes load. Avoids “thundering herd” where all contracts compete for the same slot
  • True on-chain randomness via PRNG. No external oracles needed for jitter calculation
  • Multiple scheduling methods for different use cases. Use scheduleCall or scheduleCallWithPayer for recurring operations, executeCallOnPayerSignature for one-shots
  • This level of network awareness doesn’t exist on most EVM chains. Hedera enables truly intelligent on-chain automation

Real-World Applications

This pattern can be extended to:
  • DeFi Vaults: Automatic portfolio rebalancing based on price oracles
  • Liquidity Management: Periodic adjustment of AMM positions
  • Treasury Operations: Scheduled fund distributions or buybacks
  • Yield Optimization: Regular harvesting and compounding of rewards
  • DAO Governance: Time-delayed execution of approved proposals
All without relying on off-chain infrastructure or keeper networks!

Additional Resources

Writer: Kiran Pachhai, Developer Advocate