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
- The keeper watches the fillr program for
Activecommitments (via getProgramAccounts polling or the indexer's WebSocket). - For each commitment whose trigger condition is met, the keeper requests a quote from Jupiter for
payment_amount × (1 - 20bps)frompayment_mint→target_mint. - If Jupiter returns a route with
out_amount ≥ min_fillandeffective_price ≤ max_price, the keeper proceeds; otherwise it skips. - The keeper fetches Jupiter's
/swap-instructionsendpoint with the PaymentVault PDA asuserPublicKey, extracts the swap data + accounts, and assembles anexecute_commitmenttx. - The keeper simulates the tx locally. If simulation succeeds, the tx is submitted (and optionally sent as a Jito bundle for MEV protection).
- On success: the keeper receives the bounty (in lamports) and the commitment closes.
- 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.
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:
envSOLANA_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.