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

Stablecoin pause / blacklist monitor

Recipe (no bundled template) ยท category: monitoring ยท risk: monitor_only

Page the moment a stablecoin issuer freezes the token or blacklists an address. Centrally-controlled stablecoins (USDC/USDT-style) can pause() all transfers or blacklist a specific account โ€” if your protocol holds or routes that token, you want to know before your next transfer reverts. This recipe watches the issuer's Paused / Unpaused / Blacklisted events and keeps a paused() read as a backstop in case a log is missed.

Closest bundled starting point: the large transfer alert template โ€” the same no-signer, event โ†’ notify shape. Swap its ABI + events for the ones below.

When to use it

  • you custody or route a centrally-controlled stablecoin (USDC, USDT, โ€ฆ)
  • you want to freeze your own flows the instant the issuer pauses the token
  • you need to know if one of your addresses gets blacklisted

The config

Four workflows, one contract, no signer. Each event workflow is a thin notify; the fourth is a read-trigger backstop that pages if paused() ever reads true.

# recipe: full
rflow_version: 1
name: stablecoin-pause-monitor
 
config:
  port: 3948
  db_connection: ${DATABASE_URL}
 
# monitor-only: no signer and no relayers, so neither embedded engine boots.
# The event workflows index the token; the read workflow polls over eth_call.
networks:
  - name: ethereum
    chain_id: 1
    rpc: ${ETH_RPC}
 
contracts:
  # a USDC/USDT-style token that exposes Pausable + a blacklist. Swap in the
  # real token address and an ABI carrying its pause/blacklist events.
  Stablecoin:
    abi: ./abis/stablecoin.json
    addresses:
      ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
 
notifications:
  channels:
    ops:
      telegram:
        bot_token: ${TG_BOT_TOKEN}
        chat_id: ${TG_CHAT_ID}
 
workflows:
  # the token was paused - every transfer is now frozen. Page immediately.
  stablecoin-paused:
    trigger:
      event:
        contract: Stablecoin
        name: Paused
        network: ethereum
        confirmations: 0
        start_block: latest
        end_block: live
    steps:
      - id: alert
        notify:
          channel: ops
          message: "STABLECOIN PAUSED on ethereum by ${{ trigger.args.account }} - all transfers frozen (tx ${{ trigger.tx_hash }})"
    on_failure: dead_letter
 
  # the token was unpaused - transfers resume.
  stablecoin-unpaused:
    trigger:
      event:
        contract: Stablecoin
        name: Unpaused
        network: ethereum
        confirmations: 0
        start_block: latest
        end_block: live
    steps:
      - id: alert
        notify:
          channel: ops
          message: "stablecoin UNPAUSED on ethereum by ${{ trigger.args.account }} - transfers resumed (tx ${{ trigger.tx_hash }})"
    on_failure: dead_letter
 
  # an address was blacklisted - its balance is frozen. If it is one of yours,
  # you want to know before your next transfer reverts.
  stablecoin-blacklisted:
    trigger:
      event:
        contract: Stablecoin
        name: Blacklisted
        network: ethereum
        confirmations: 0
        start_block: latest
        end_block: live
    steps:
      - id: alert
        notify:
          channel: ops
          message: "stablecoin BLACKLISTED ${{ trigger.args.account }} on ethereum (tx ${{ trigger.tx_hash }})"
    on_failure: dead_letter
 
  # backstop: even if a Paused log is missed, this read trigger pages the
  # moment paused() first reads true (false->true crossing, edge-triggered)
  stablecoin-paused-backstop:
    trigger:
      read:
        contract: Stablecoin
        network: ethereum
        function: "paused()"
        every: 1m
        condition: "${{ output == true }}"
        mode: threshold
    steps:
      - id: alert
        notify:
          channel: ops
          message: "stablecoin paused() reads TRUE on ethereum - transfers are frozen"
    on_failure: dead_letter

Required env vars

DATABASE_URL, ETH_RPC, TG_BOT_TOKEN, TG_CHAT_ID.

Safety notes โ€” monitor-only

  • No signer, no relayers โ€” the config declares neither, so the relayer engine never boots and this workflow can only ever read and notify.
  • on_failure: dead_letter journals a failed notification instead of losing it โ€” a paged alert you can replay.
  • confirmations: 0 favours latency (you want a pause alert now); a pause in a reorged block would re-alert on the canonical block. Raise it if you prefer certainty over speed. The paused() backstop covers a missed log.
  • The bundled stablecoin.json here carries generic Paused(address) / Unpaused(address) / Blacklisted(address) events; real issuers differ (USDT uses Pause()/Unpause() with no args and AddedBlackList(address)). Fetch the real ABI (rflow abi fetch) and adjust the event names.

Common modifications

  • Escalate a pause to PagerDuty/Opsgenie instead of telegram โ€” add the channel (rflow add notification) and point the paused workflow at it.
  • Only my addresses: filter stablecoin-blacklisted with a where: "${{ lower(trigger.args.account) in constants.mine }}" over a list of your addresses.
  • Freeze your own flows: have the paused workflow flip a state: flag your money workflows gate on with an if:.