Keepers are the permissionless workers that execute commitments. Anyone can run one. They earn a SOL bounty per successful fill.

Why a keeper exists

The fillr program is reactive: it only acts when an external signer calls one of its instructions. create_commitment is called by the buyer, cancel_commitment by the owner, but execute_commitment needs someone to monitor the chain, check Jupiter for a viable route, and submit the swap when the moment is right.

That someone is a keeper — and because the on-chain bounds enforcement is strict (the program verifies price + min-fill atomically), keepers are aligned with users by default. A keeper cannot front-run, sandwich, or shortchange a fill; the worst they can do is fail to execute, which costs only their own tx fee.

How it works

Keeper poll loop
01Watchopen commitments02QuoteJupiter /v6/quote03Racebuild + sign tx04ExecuteCPI Jupiterloop · ~1son reject: retry
Permissionless. Anyone can run a keeper. First to deliver a valid fill wins the bounty; the program rejects everyone else atomically.
  1. The keeper watches the fillr program for Active commitments (via getProgramAccounts polling or the indexer's WebSocket).
  2. For each commitment whose trigger condition is met, the keeper requests a quote from Jupiter for payment_amount × (1 - 20bps) from payment_minttarget_mint.
  3. If Jupiter returns a route with out_amount ≥ min_fill and effective_price ≤ max_price, the keeper proceeds; otherwise it skips.
  4. The keeper fetches Jupiter's /swap-instructions endpoint with the PaymentVault PDA as userPublicKey, extracts the swap data + accounts, and assembles an execute_commitment tx.
  5. The keeper simulates the tx locally. If simulation succeeds, the tx is submitted (and optionally sent as a Jito bundle for MEV protection).
  6. On success: the keeper receives the bounty (in lamports) and the commitment closes.
  7. On revert: the keeper applies a per-route cooldown to avoid repeating the same failure.

Incentives

The keeper's revenue per fill is the SOL bounty set by the commitment owner. Their cost is the Solana tx fee + priority fee + any Jito tip. Reasonable bounty defaults (0.001 SOL) profitably cover ~5-10 priority-fee units in normal conditions.

On mainnet, fillr's reference keeper bids ~20% of the bounty as priority fee. This balances “land my tx” against keeper margin. Other keepers may bid differently — that's fine. The first valid tx to land wins; the rest fail cheaply.

Running a keeper

The reference keeper lives at keeper/ in the fillr repo. It's a TypeScript process that runs on Fly.io or any Node host. Required env:

env
SOLANA_RPC_URL=<helius-or-triton-rpc> KEEPER_KEYPAIR_JSON=<the keypair's secret-key byte array> POLL_INTERVAL_MS=8000 WORKER_CONCURRENCY=4 REVERT_COOLDOWN_MS=30000

Fund the keeper wallet with ~5 SOL — tx fees come from this balance and the bounty refills it over time.

MEV considerations

The on-chain bounds checks remove the typical MEV vectors:

  • Sandwich: impossible. The post-CPI price check rejects any execution above max_price, regardless of pool state.
  • Front-run: redundant. A faster keeper might land their tx first, but the buyer's outcome is identical either way (price ≤ max_price is enforced atomically).
  • Back-run: not applicable — the swap itself is the trade. There's nothing for an attacker to back-run.

The one risk is griefing: a keeper repeatedly submitting losing txs to waste CU. This is self-limiting (the keeper pays the failed-tx fee out of pocket) and the in-process cooldown blocks the same (commitment, route) pair from being retried for 30 seconds after a revert.