In this guide, we’ll walk you through building a Web3 wallet auto top-up system using Kwala Workflows, a low-code, YAML-based automation platform for blockchain developers.
With Kwala, you can connect on-chain events to automated smart contract executions without cron jobs, servers, or custom scripts. In this use case, you’ll learn how to automatically monitor wallet balances and top-up low balances in real time through smart contracts and event-driven workflows powered by Kwala.
Objective
Build a two-part workflow system that:
- Continuously checks wallet balances and detects when a wallet’s native POL balance drops below a threshold of
0.1 POL.
- Automatically transfers funds to top up the wallet whenever a low-balance event is emitted.
This setup enables self-sustaining wallets that stay funded without manual monitoring.
Step 1: Smart contract setup
We use two Solidity contracts:
1. PolNativeBalanceChecker.sol
This contract checks a wallet’s POL balance and emits a LowPolBalance event when the balance is below the predefined threshold.
File name: PolChecker.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title PolNativeBalanceChecker
/// @notice Emits LowPolBalance(user,balanceWei) when a checked address balance < THRESHOLD (0.1 POL).
contract PolNativeBalanceChecker {
uint256 public constant THRESHOLD = 0.1 ether; // 0.1 POL
event LowPolBalance(address indexed user, uint256 balanceWei);
/// @notice Returns native balance in wei
function polBalanceOf(address user) public view returns (uint256) {
return user.balance;
}
/// @notice Check `user` balance and emit event if below threshold.
function checkAndWarn(address user) public returns (uint256 balanceWei) {
balanceWei = user.balance;
if (balanceWei < THRESHOLD) {
emit LowPolBalance(user, balanceWei);
}
}
/// @notice Convenience: check caller
function checkMe() external returns (uint256) {
return checkAndWarn(msg.sender);
}
}
Deploy this contract on Polygon Amoy Testnet and copy the deployed contract address, for example: 0x85bc99d6207aD8284fb2bD0C5A4ebaA6F4B140e4.
2. AutoTopUp.sol
This contract performs the top-up of wallets that fall below the balance threshold.
File name: AutoTopUp.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title AutoTopUp
/// @notice Minimal "self-executable" top-up vault.
/// - If `to` has < 0.1 POL, calling `topUpIfLow(to)` will send 1 POL from this contract to `to`.
/// - Anyone can call `topUpIfLow` (so it is "self-executable"); the contract must be funded first.
/// - Includes a tiny non-reentrant guard.
contract AutoTopUp {
uint256 public constant TOPUP_AMOUNT = 1 ether; // send 1 POL
uint256 public constant THRESHOLD = 0.1 ether; // top-up when recipient < 0.1 POL
bool private locked;
event TopUpExecuted(address indexed to, uint256 amountWei);
event TopUpSkipped(address indexed to, uint256 currentBalanceWei);
event Received(address indexed from, uint256 amountWei);
modifier nonReentrant() {
require(!locked, "reentrant");
locked = true;
_;
locked = false;
}
// allow the contract to receive POL (treasury should send funds here)
receive() external payable {
emit Received(msg.sender, msg.value);
}
/// @notice If `to` has balance < THRESHOLD, transfer TOPUP_AMOUNT from this contract to `to`.
/// @dev Callable by anyone. Returns true on successful transfer, false if skipped due to sufficient balance.
function topUpIfLow(address payable to) external nonReentrant returns (bool) {
require(to != address(0), "invalid recipient");
uint256 recipientBal = to.balance;
if (recipientBal >= THRESHOLD) {
emit TopUpSkipped(to, recipientBal);
return false;
}
require(address(this).balance >= TOPUP_AMOUNT, "insufficient vault balance");
(bool sent, ) = to.call{ value: TOPUP_AMOUNT }("");
require(sent, "transfer failed");
emit TopUpExecuted(to, TOPUP_AMOUNT);
return true;
}
/// @notice View helper: whether a top-up would succeed right now.
function canTopUp(address to) public view returns (bool) {
if (to == address(0)) return false;
return (to.balance < THRESHOLD) && (address(this).balance >= TOPUP_AMOUNT);
}
/// @notice Convenience: current contract (vault) balance
function vaultBalance() external view returns (uint256) {
return address(this).balance;
}
}
Deploy AutoTopUp and fund it with enough POL so it can perform top-ups.
Step 2: Setting up Kwala workflows
This automation uses two Kwala workflows that communicate via emitted events.
Workflow 1: BCPollingperMinute polling workflow
Purpose: Continuously monitor a wallet’s POL balance by calling checkAndWarn().
Trigger parameters:
- Execute After:
immediate
- Repeat Every:
1m every minute
- Expires In:
1759213800 timestamp
Action: Calls checkAndWarn(address user) on the deployed PolNativeBalanceChecker contract. If the wallet’s balance is below 0.1 POL, the contract emits LowPolBalance(address,uint256).
YAML 1: Polling workflow
Name: polling1_e81e
Trigger:
TriggerSourceContract: NA
TriggerChainID: NA
TriggerEventName: NA
TriggerEventFilter: NA
TriggerSourceContractABI: NA
TriggerPrice: NA
RecurringSourceContract: NA
RecurringChainID: NA
RecurringEventName: NA
RecurringEventFilter: NA
RecurringSourceContractABI: NA
RecurringPrice: NA
RepeatEvery: 1m
ExecuteAfter: immediate
ExpiresIn: 1759213800
Meta: NA
ActionStatusNotificationPOSTURL:
ActionStatusNotificationAPIKey: NA
Actions:
- Name: poll1
Type: call
APIEndpoint: NA
APIPayload:
Message: NA
TargetContract: 0x85bc99d6207aD8284fb2bD0C5A4ebaA6F4B140e4
TargetFunction: function checkAndWarn(address user)
TargetParams:
- 0x0F0C0b9683180DF0a13Fc176aFFd92E1F7f380f0
ChainID: 80002
EncodedABI: NA
Bytecode: NA
Metadata: NA
RetriesUntilSuccess: 5
Execution:
Mode: sequential
The polling workflow calls the checker every minute for the target wallet address, for example: 0x0F0C…f380f0.
Workflow 2: AutoTopUp1 auto top-up workflow
Purpose: Automatically top up wallets whenever a LowPolBalance event is emitted.
Trigger parameters:
- Trigger Source Contract:
0x85bc99d6207aD8284fb2bD0C5A4ebaA6F4B140e4
- Trigger Chain ID:
80002 Polygon Amoy
- Trigger Event Name:
LowPolBalance(address,uint256)
- Execute After:
event
- Repeat Every:
event
When triggered, this workflow calls the AutoTopUp contract’s topUpIfLow() function to send funds.
YAML 2: AutoTopUp workflow
Name: autotopup1_e81e
Trigger:
TriggerSourceContract: 0x85bc99d6207aD8284fb2bD0C5A4ebaA6F4B140e4
TriggerChainID: 80002
TriggerEventName: LowPolBalance(address,uint256)
TriggerEventFilter: NA
TriggerSourceContractABI: WwogIHsKICAgICJhbm9ueW1vdXMiOiBmYWxzZSwKICAgICJpbnB1dHMiOiBbCiAgICAgIHsKICAgICAgICAiaW5kZXhlZCI6IHRydWUsCiAgICAgICAgImludGVybmFsVHlwZSI6ICJhZGRyZXNzIiwKICAgICAgICAibmFtZSI6ICJ1c2VyIiwKICAgICAgICAidHlwZSI6ICJhZGRyZXNzIgogICAgICB9LAogICAgICB7CiAgICAgICAgImluZGV4ZWQiOiBmYWxzZSwKICAgICAgICAiaW50ZXJuYWxUeXBlIjogInVpbnQyNTYiLAogICAgICAgICJuYW1lIjogImJhbGFuY2VXZWkiLAogICAgICAgICJ0eXBlIjogInVpbnQyNTYiCiAgICAgIH0KICAgIF0sCiAgICAibmFtZSI6ICJMb3dQb2xCYWxhbmNlIiwKICAgICJ0eXBlIjogImV2ZW50IgogIH0sCiAgewogICAgImlucHV0cyI6IFtdLAogICAgIm5hbWUiOiAiVEhSRVNIT0xEIiwKICAgICJvdXRwdXRzIjogWwogICAgICB7CiAgICAgICAgImludGVybmFsVHlwZSI6ICJ1aW50MjU2IiwKICAgICAgICAibmFtZSI6ICIiLAogICAgICAgICJ0eXBlIjogInVpbnQyNTYiCiAgICAgIH0KICAgIF0sCiAgICAic3RhdGVNdXRhYmlsaXR5IjogInZpZXciLAogICAgInR5cGUiOiAiZnVuY3Rpb24iCiAgfSwKICB7CiAgICAiaW5wdXRzIjogWwogICAgICB7CiAgICAgICAgImludGVybmFsVHlwZSI6ICJhZGRyZXNzIiwKICAgICAgICAibmFtZSI6ICJ1c2VyIiwKICAgICAgICAidHlwZSI6ICJhZGRyZXNzIgogICAgICB9CiAgICBdLAogICAgIm5hbWUiOiAiY2hlY2tBbmRXYXJuIiwKICAgICJvdXRwdXRzIjogWwogICAgICB7CiAgICAgICAgImludGVybmFsVHlwZSI6ICJ1aW50MjU2IiwKICAgICAgICAibmFtZSI6ICJiYWxhbmNlV2VpIiwKICAgICAgICAidHlwZSI6ICJ1aW50MjU2IgogICAgICB9CiAgICBdLAogICAgInN0YXRlTXV0YWJpbGl0eSI6ICJub25wYXlhYmxlIiwKICAgICJ0eXBlIjogImZ1bmN0aW9uIgogIH0sCiAgewogICAgImlucHV0cyI6IFtdLAogICAgIm5hbWUiOiAiY2hlY2tNZSIsCiAgICAib3V0cHV0cyI6IFsKICAgICAgewogICAgICAgICJpbnRlcm5hbFR5cGUiOiAidWludDI1NiIsCiAgICAgICAgIm5hbWUiOiAiIiwKICAgICAgICAidHlwZSI6ICJ1aW50MjU2IgogICAgICB9CiAgICBdLAogICAgInN0YXRlTXV0YWJpbGl0eSI6ICJub25wYXlhYmxlIiwKICAgICJ0eXBlIjogImZ1bmN0aW9uIgogIH0sCiAgewogICAgImlucHV0cyI6IFsKICAgICAgewogICAgICAgICJpbnRlcm5hbFR5cGUiOiAiYWRkcmVzcyIsCiAgICAgICAgIm5hbWUiOiAidXNlciIsCiAgICAgICAgInR5cGUiOiAiYWRkcmVzcyIKICAgICAgfQogICAgXSwKICAgICJuYW1lIjogInBvbEJhbGFuY2VPZiIsCiAgICAib3V0cHV0cyI6IFsKICAgICAgewogICAgICAgICJpbnRlcm5hbFR5cGUiOiAidWludDI1NiIsCiAgICAgICAgIm5hbWUiOiAiIiwKICAgICAgICAidHlwZSI6ICJ1aW50MjU2IgogICAgICB9CiAgICBdLAogICAgInN0YXRlTXV0YWJpbGl0eSI6ICJ2aWV3IiwKICAgICJ0eXBlIjogImZ1bmN0aW9uIgogIH0KXQ==
TriggerPrice: NA
RecurringSourceContract: 0x85bc99d6207aD8284fb2bD0C5A4ebaA6F4B140e4
RecurringChainID: 80002
RecurringEventName: LowPolBalance(address,uint256)
RecurringEventFilter: NA
RecurringSourceContractABI: WwogIHsKICAgICJhbm9ueW1vdXMiOiBmYWxzZSwKICAgICJpbnB1dHMiOiBbCiAgICAgIHsKICAgICAgICAiaW5kZXhlZCI6IHRydWUsCiAgICAgICAgImludGVybmFsVHlwZSI6ICJhZGRyZXNzIiwKICAgICAgICAibmFtZSI6ICJ1c2VyIiwKICAgICAgICAidHlwZSI6ICJhZGRyZXNzIgogICAgICB9LAogICAgICB7CiAgICAgICAgImluZGV4ZWQiOiBmYWxzZSwKICAgICAgICAiaW50ZXJuYWxUeXBlIjogInVpbnQyNTYiLAogICAgICAgICJuYW1lIjogImJhbGFuY2VXZWkiLAogICAgICAgICJ0eXBlIjogInVpbnQyNTYiCiAgICAgIH0KICAgIF0sCiAgICAibmFtZSI6ICJMb3dQb2xCYWxhbmNlIiwKICAgICJ0eXBlIjogImV2ZW50IgogIH0sCiAgewogICAgImlucHV0cyI6IFtdLAogICAgIm5hbWUiOiAiVEhSRVNIT0xEIiwKICAgICJvdXRwdXRzIjogWwogICAgICB7CiAgICAgICAgImludGVybmFsVHlwZSI6ICJ1aW50MjU2IiwKICAgICAgICAibmFtZSI6ICIiLAogICAgICAgICJ0eXBlIjogInVpbnQyNTYiCiAgICAgIH0KICAgIF0sCiAgICAic3RhdGVNdXRhYmlsaXR5IjogInZpZXciLAogICAgInR5cGUiOiAiZnVuY3Rpb24iCiAgfSwKICB7CiAgICAiaW5wdXRzIjogWwogICAgICB7CiAgICAgICAgImludGVybmFsVHlwZSI6ICJhZGRyZXNzIiwKICAgICAgICAibmFtZSI6ICJ1c2VyIiwKICAgICAgICAidHlwZSI6ICJhZGRyZXNzIgogICAgICB9CiAgICBdLAogICAgIm5hbWUiOiAiY2hlY2tBbmRXYXJuIiwKICAgICJvdXRwdXRzIjogWwogICAgICB7CiAgICAgICAgImludGVybmFsVHlwZSI6ICJ1aW50MjU2IiwKICAgICAgICAibmFtZSI6ICJiYWxhbmNlV2VpIiwKICAgICAgICAidHlwZSI6ICJ1aW50MjU2IgogICAgICB9CiAgICBdLAogICAgInN0YXRlTXV0YWJpbGl0eSI6ICJub25wYXlhYmxlIiwKICAgICJ0eXBlIjogImZ1bmN0aW9uIgogIH0sCiAgewogICAgImlucHV0cyI6IFtdLAogICAgIm5hbWUiOiAiY2hlY2tNZSIsCiAgICAib3V0cHV0cyI6IFsKICAgICAgewogICAgICAgICJpbnRlcm5hbFR5cGUiOiAidWludDI1NiIsCiAgICAgICAgIm5hbWUiOiAiIiwKICAgICAgICAidHlwZSI6ICJ1aW50MjU2IgogICAgICB9CiAgICBdLAogICAgInN0YXRlTXV0YWJpbGl0eSI6ICJub25wYXlhYmxlIiwKICAgICJ0eXBlIjogImZ1bmN0aW9uIgogIH0sCiAgewogICAgImlucHV0cyI6IFsKICAgICAgewogICAgICAgICJpbnRlcm5hbFR5cGUiOiAiYWRkcmVzcyIsCiAgICAgICAgIm5hbWUiOiAidXNlciIsCiAgICAgICAgInR5cGUiOiAiYWRkcmVzcyIKICAgICAgfQogICAgXSwKICAgICJuYW1lIjogInBvbEJhbGFuY2VPZiIsCiAgICAib3V0cHV0cyI6IFsKICAgICAgewogICAgICAgICJpbnRlcm5hbFR5cGUiOiAidWludDI1NiIsCiAgICAgICAgIm5hbWUiOiAiIiwKICAgICAgICAidHlwZSI6ICJ1aW50MjU2IgogICAgICB9CiAgICBdLAogICAgInN0YXRlTXV0YWJpbGl0eSI6ICJ2aWV3IiwKICAgICJ0eXBlIjogImZ1bmN0aW9uIgogIH0KXQ==
RecurringPrice: NA
RepeatEvery: event
ExecuteAfter: event
ExpiresIn: 1759213800
Meta: NA
ActionStatusNotificationPOSTURL:
ActionStatusNotificationAPIKey: NA
Actions:
- Name: autotopup1
Type: call
APIEndpoint: NA
APIPayload:
Message: NA
TargetContract: 0x8905777D8617034ACD8088D9273186D449436e6C
TargetFunction: function topUpIfLow(address payable)
TargetParams:
- 0x0F0C0b9683180DF0a13Fc176aFFd92E1F7f380f0
ChainID: 80002
EncodedABI: NA
Bytecode: NA
Metadata: NA
RetriesUntilSuccess: 5
Execution:
Mode: sequential
The auto-top-up workflow listens for LowPolBalance events emitted by the checker contract and then calls the top-up action on the AutoTopUp contract.
Step 3: Deploy and test the workflows
- Save and compile both YAML workflows in the Kwala Dashboard.
- Deploy Workflow 1 (polling) and Workflow 2 (auto top-up).
- Wait until both workflows show Claimed.
- Fund the deployed AutoTopUp contract with sufficient POL.
When a monitored wallet’s balance drops below 0.1 POL:
- Workflow 1 polling calls
checkAndWarn() and the checker emits LowPolBalance.
- Workflow 2 detects the event and calls
topUpIfLow() on the AutoTopUp contract, which transfers 1 POL to the wallet.
Step 4: Monitor workflow execution
Use the Kwala Dashboard to inspect:
- Workflow Logs: verify triggers and action calls.
- Execution Status: confirm successful top-ups.
- Retries and Failures: troubleshoot any missed or failed attempts.
Output
Once both workflows run successfully, the AutoTopUp contract transfers 1 POL to the wallet address whenever its balance goes below 0.1 POL, creating a self-regulating on-chain wallet.
If you add Telegram or other notifications to the workflows, you can surface messages like:
“AutoTopUp executed — wallet 0x0F0C…F380f0 rebalanced successfully.”
Conclusion
With thr two YAML workflows, we created an auto top-up wallet system using Kwala Workflows without backend scripts, centralized orchestration, or manual intervention.
This use case highlights how Kwala bridges event-driven Web3 logic with automated smart contract execution, enabling developers to build real-world, autonomous blockchain systems quickly and reliably.
Next steps