Keeper Network

Keepers automate subscription charges and earn a 0.5% bounty per charge.

What is a keeper?

A keeper is any wallet running software that monitors on-chain subscriptions and callscharge() when they become due. Keepers are permissionless — anyone can run one. They earn a 0.5% bounty on every successful charge.

The reference keeper (@cron/keeper) is production-grade and handles discovery, event watching, gas checks, and batch execution.

Quick start

bash
cd packages/keeper
cp .env.example .env

Edit .env:

bash
# Required
KEEPER_PRIVATE_KEY=0x...           # Wallet that executes charges and receives bounties
CHAIN=baseSepolia                  # base | baseSepolia | arbitrum | arbitrumSepolia
CRON_REGISTRY_ADDRESS=0x...        # Deployed CronRegistry address
RPC_URL=https://sepolia.base.org   # RPC endpoint

# Optional tuning
BATCH_SIZE=10                      # Max subscriptions per batch charge tx
POLL_INTERVAL_MS=60000             # How often to poll for due charges (default: 1 min)
MAX_GAS_PRICE_GWEI=50              # Skip charges if gas exceeds this
MIN_PROFIT_USD=0.01                # Skip charges below this profitability threshold
DISCOVERY_FROM_BLOCK=0             # Start block for bootstrapping subscription history
bash
npm run start

How the keeper works

  1. Bootstrap — on startup, reads all SubscriptionCreated events from DISCOVERY_FROM_BLOCK to build an initial subscription list.
  2. Live watching — subscribes to real-time events to catch new subscriptions and cancellations.
  3. Poll loop — every POLL_INTERVAL_MS, filters due subscriptions and executes batch charges.
  4. Gas guard — checks current gas price before executing. Skips if gas exceeds MAX_GAS_PRICE_GWEI.
  5. Profitability filter — calculates expected bounty minus estimated gas cost. Skips unprofitable charges.

Keeper statistics

The keeper logs statistics to stdout on each cycle:

bash
[keeper] Cycle #42
  Subscriptions tracked: 1,247
  Due this cycle: 18
  Charged: 17 | Failed: 1
  Bounty earned: 0.085 USDC
  Total bounty all-time: 12.43 USDC
  Avg gas price: 0.08 gwei

Running multiple keepers

The protocol is fully permissionless — multiple keepers can operate simultaneously. If two keepers try to charge the same subscription, the second transaction reverts (the subscription is no longer due). Only the first keeper earns the bounty.

[*] Running in production

Use a process manager like pm2 or a Docker container to keep the keeper running continuously. Set DISCOVERY_FROM_BLOCK to the block your registry was deployed at to speed up the initial sync.
bash
# Run with pm2
npm install -g pm2
pm2 start npm --name "cron-keeper" -- run start
pm2 save
pm2 startup

Build your own keeper

You can build a custom keeper using the SDK directly:

typescript
import { CronClient } from "@cron/sdk";

const cron = new CronClient({
  chain: "base",
  walletClient,  // keeper wallet
});

async function chargeLoop() {
  // Get all subscriptions that are currently due
  const due = await cron.charges.getDue();

  for (const { planId, subscriber } of due) {
    try {
      const tx = await cron.charges.charge({ planId, subscriber });
      console.log("Charged:", tx.hash);
    } catch (err) {
      // Subscription may have already been charged by another keeper
      console.error("Charge failed:", err.message);
    }
  }
}

setInterval(chargeLoop, 60_000);