Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Chainlink deviation alert

Template: chainlink-deviation-alert ยท category: monitoring ยท risk: monitor_only

Compares two Chainlink price feeds on a schedule and pages when they disagree by more than a basis-point threshold โ€” a primary oracle vs a fallback, or two oracles for the same asset. A disagreement is the signal a feed has frozen, been manipulated, or a fallback is drifting.

When to use it

  • cross-check the oracle your protocol relies on against a second source
  • catch a stuck/laggy feed before it prices a liquidation wrong
  • watch a fallback oracle drift away from the primary

Generate it

rflow new --template chainlink-deviation-alert
 
# or add the workflow to an existing project
rflow add workflow chainlink-deviation-alert

Non-interactive (CI/agents):

rflow new --template chainlink-deviation-alert --yes --output ./deviation \
  --answer deviation_bps=50

Inputs

keytypedefault
project_namestringchainlink-deviation-alert
network / chain_id / rpc_env / rpc_urlnetwork / chain_id / env_var / stringethereum / 1 / ETH_RPC / a public RPC
feed_a_name / feed_a_addresscontract / addressFeedA / zero placeholder (replace!)
feed_b_name / feed_b_addresscontract / addressFeedB / zero placeholder (replace!)
feed_decimalsint8
deviation_bpsint50
poll_croncron*/5 * * * *
channelstringops

Deviation is |a - b| * 10000 / min(a, b) basis points (100 bps = 1%), computed with mul_div in full precision. Both feeds must be positive and share the same decimals (true for same-asset USD feeds, which are 8).

Required env vars

DATABASE_URL, the RPC env var (default ETH_RPC), TG_BOT_TOKEN, TG_CHAT_ID โ€” all listed in the generated .env.example.

Safety notes

Monitor-only: no signer: and no relayers:, so the relayer engine never boots. The trigger is a cron, so a persistent gap re-alerts every tick โ€” widen poll_cron or add a dedupe list if that is too chatty (a two-feed comparison can't use the read trigger's edge-triggered mode). The workflow ends in on_failure: dead_letter.

Run it locally

docker compose up -d     # postgres on localhost:5448
# fill .env, set both feed addresses
rflow validate
rflow start

Common modifications

  • tighten deviation_bps to page on smaller disagreements
  • compare a Chainlink feed against a DEX TWAP by swapping one contract
  • widen poll_cron to cut RPC load