Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board
HomeBy LayerIntelligence4.7 OnChainWatcher

4.7 OnChainWatcher

Intelligence Signal Service Read-only BETA Limited live capital · Indirect P2 · Data normalisation pending stub

OnChainWatcher monitors Polygon on-chain events for a configured list of high-signal wallet addresses, detecting CTFExchangeV2 order fills, position transfers, and pUSD balance changes that exceed min_position_change_usd. It emits an ObservationReport for each qualifying wallet event after a publish_delay_s hold (to prevent front-running its own signals). Output feeds liquidity-aware strategies and the LiquidityGuard risk bot with smart-money flow intelligence. OnChainWatcher is strictly read-only — it never submits or signs orders.

v3 readiness

Docs27/27
donehow scored
Impl0/15
pendinghow scored
Backtest0/4
pendinghow scored
Runtime0/8
pendinghow scored

A bot is done when all four scores are. What does done mean?

1. Bot Identity

LayerIntelligence  Intelligence
Bot classSignal Service
AuthorityRead-only
StatusBETA
ReadinessLimited live
Runs beforeStrategy layer, LiquidityGuard, OrderFlowAnalyzer
Runs afterPolygon RPC subscription established; watched_wallets config loaded
Applies toAll Polymarket markets where watched wallets hold or have recently changed positions
Default modelimited_live
User-visibleAdvanced details only
Developer ownerPolytraders core — Intelligence pod

2. Purpose

OnChainWatcher monitors Polygon on-chain events for a configured list of high-signal wallet addresses, detecting CTFExchangeV2 order fills, position transfers, and pUSD balance changes that exceed min_position_change_usd. It emits an ObservationReport for each qualifying wallet event after a publish_delay_s hold (to prevent front-running its own signals). Output feeds liquidity-aware strategies and the LiquidityGuard risk bot with smart-money flow intelligence. OnChainWatcher is strictly read-only — it never submits or signs orders.

3. Why This Bot Matters

  • High-signal wallet entry not detected

    Strategy misses a leading indicator; enters after the smart-money move has already re-priced the market, capturing reduced or negative edge.

  • publish_delay_s misconfigured to zero

    OnChainWatcher signals are published immediately, allowing the trading infrastructure itself to front-run other participants using its own on-chain data feed.

  • Watched wallet list stale after wallet rotation

    Previously high-signal addresses are no longer the active trading wallets; signals become irrelevant and may mislead strategies.

  • RPC outage causes missed position changes during high-activity period

    Smart-money entries during the blackout are not detected; strategies enter a market after the signal window has closed.

No worked examples on this bot yet. Worked examples are optional but strongly recommended — they turn an abstract failure mode into something a developer can verify in a fixture.

4. Required Polymarket Inputs

InputSourceRequired?Use
CTFExchangeV2 OrderFilled events for watched wallet addressesonchain (Polygon RPC event log subscription)YesPrimary detection of watched wallet entries and exits in Polymarket markets.
pUSD ERC-20 Transfer events for watched walletsonchain (Polygon RPC)NoDetect pUSD balance changes indicating position funding or profit-taking not captured by OrderFilled.
Market condition_id from Polymarket Data API for transaction hash mappingdata_apiYesResolve CTFExchangeV2 token IDs to Polymarket condition_ids in ObservationReport payloads.

5. Required Internal Inputs

InputSourceRequired?Use
watched_wallets listconfig / operator-managed registryYesFilter all Polygon on-chain events to only the configured high-signal wallet addresses.
KillSwitch active flagKillSwitchYesContinue monitoring on-chain but suppress ObservationReport emissions when KillSwitch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
min_position_change_usd50020050Minimum pUSD value of a single wallet position change to qualify for an ObservationReport emission.
publish_delay_s1550Seconds to hold a detected wallet event before emitting ObservationReport. Prevents the infrastructure from acting on its own signal faster than public latency.
poll_interval_s123060Seconds between Polygon RPC polls for new on-chain events. Aligns with Polygon block time.

7. Detailed Parameter Instructions

min_position_change_usd

What it means

Minimum pUSD value of a single wallet position change to qualify for an ObservationReport emission.

Default

{ "min_position_change_usd": 500 }

Why this default matters

$500 filters out dust transactions and small test fills, ensuring only meaningful position changes generate signals.

Threshold logic

ConditionAction
change ≥ 500 pUSDNormal — emit ObservationReport
200–500 pUSDWARN — small position; emit with ONCHAINWATCHER_SMALL_POSITION warning
< 50 pUSDReject — dust threshold; do not emit

Developer check

if (p.min_position_change_usd < p.hard) return; // silently skip dust

User-facing English

Only meaningful wallet movements above a minimum size generate intelligence signals.

publish_delay_s

What it means

Seconds to hold a detected wallet event before emitting ObservationReport. Prevents the infrastructure from acting on its own signal faster than public latency.

Default

{ "publish_delay_s": 15 }

Why this default matters

15 s gives on-chain event latency time to propagate broadly, ensuring the signal is not used for front-running relative to public market participants.

Threshold logic

ConditionAction
delay ≥ 15 sNormal
5–15 sWARN — reduced front-run protection window
< 5 sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL; insufficient delay

Developer check

if (p.publish_delay_s < 5) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

Wallet signals are held briefly before being published to ensure fairness in market access.

poll_interval_s

What it means

Seconds between Polygon RPC polls for new on-chain events. Aligns with Polygon block time.

Default

{ "poll_interval_s": 12 }

Why this default matters

12 s matches the Polygon block interval, ensuring every block is checked without redundant polls.

Threshold logic

ConditionAction
interval ≤ 12 sNormal — one poll per block
12–30 sWARN — may miss events in high-throughput windows
> 60 sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if (p.poll_interval_s > p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

On-chain events are checked every Polygon block to ensure no wallet movement is missed.

8. Default Configuration

{
  "bot_id": "intel.onchainwatcher",
  "version": "2.1.0",
  "mode": "limited_live",
  "defaults": {
    "min_position_change_usd": 500,
    "publish_delay_s": 15,
    "poll_interval_s": 12
  },
  "locked": {
    "min_position_change_usd": {
      "min": 50
    },
    "publish_delay_s": {
      "min": 5
    },
    "poll_interval_s": {
      "max": 60
    }
  }
}

9. Implementation Flow

  1. On startup, subscribe to Polygon RPC event log for CTFExchangeV2 OrderFilled events and pUSD Transfer events, filtered to watched_wallets addresses.
  2. On each block (poll_interval_s), fetch new events since last_processed_block.
  3. For each event, check if wallet address is in watched_wallets. If not, skip.
  4. Resolve CTFExchangeV2 token_id to condition_id via data_api. If resolution fails, buffer event and retry on next block.
  5. Compute position_change_pusd from event size fields (denominated in pUSD).
  6. If position_change_pusd < min_position_change_usd hard floor (50 pUSD), discard as dust.
  7. If position_change_pusd < min_position_change_usd default (500 pUSD), flag ONCHAINWATCHER_SMALL_POSITION.
  8. Add event to publish_queue with emit_at = now_ms() + publish_delay_s * 1000.
  9. Check KillSwitch. If active, continue monitoring and queuing but suppress emissions.
  10. On each tick, drain publish_queue of events whose emit_at <= now_ms().
  11. For each drained event (if KillSwitch not active), emit ObservationReport with: report_id, trace_id, wallet_address, condition_id, side (BUY/SELL), position_change_pusd, price, block_number, tx_hash, warnings.
  12. Log per-block summary: events_detected, events_emitted, events_queued, dust_skipped, killswitch_active.

10. Reference Implementation

Subscribes to Polygon RPC event log for CTFExchangeV2 OrderFilled events filtered to watched_wallets, resolves token IDs to condition_ids, applies dust filter and size threshold, holds events for publish_delay_s, then emits ObservationReports.

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output.

FUNCTION watchBlock(block_number):
  // --- 0. RPC health check ---
  IF rpc.last_block_age_s > 2 * params.poll_interval_s:
    EMIT WARN 'STALE_DATA — RPC unresponsive'
    RETURN

  // --- 1. Fetch new events since last_processed_block ---
  events = rpc.getLogs(
    address   = CTFEXCHANGEV2_ADDR,
    topics    = [ORDER_FILLED_SIG, PUSD_TRANSFER_SIG],
    fromBlock = last_processed_block + 1,
    toBlock   = block_number
  )

  // --- 2. KillSwitch gate ---
  ks = FETCH internal.killswitch.status

  FOR event IN events:
    // --- 3. Wallet filter ---
    wallet = event.makerAddress OR event.takerAddress
    IF wallet NOT IN params.watched_wallets:
      CONTINUE

    // --- 4. Resolve token_id -> condition_id ---
    condition_id = data_api.GET('/token/' + event.tokenId + '/condition')
    IF condition_id IS NULL:
      buffer_for_retry(event)
      CONTINUE

    // --- 5. Dust filter ---
    position_change_pusd = toPusdUnits(event.fillAmount)
    IF position_change_pusd < params.min_position_change_usd.hard:
      dust_skipped_counter += 1
      CONTINUE

    // --- 6. Warning threshold ---
    warnings = []
    IF position_change_pusd < params.min_position_change_usd.default:
      warnings.append('ONCHAINWATCHER_SMALL_POSITION')

    side = 'BUY' IF event.makerSide == 'SELL' ELSE 'SELL'

    // --- 7. Queue with publish delay ---
    publish_queue.add({
      wallet_address:          wallet,
      condition_id:            condition_id,
      side:                    side,
      position_change_pusd:    position_change_pusd,
      price:                   event.price,
      block_number:            block_number,
      tx_hash:                 event.transactionHash,
      emit_at_ms:              now_ms() + params.publish_delay_s * 1000,
      warnings:                warnings
    })

  // --- 8. Drain publish queue ---
  FOR item IN publish_queue.due_items(now_ms()):
    // KillSwitch suppress
    IF ks.active:
      LOG INFO 'KILL_SWITCH_ACTIVE — suppressing ObservationReport'
      CONTINUE

    EMIT ObservationReport {
      report_id:              'rep_ocw_' + item.condition_id[:6] + '_' + now_ms(),
      trace_id:               new_trace_id(),
      bot_id:                 'intel.onchainwatcher',
      kind:                   'ObservationReport',
      wallet_address:         item.wallet_address,
      condition_id:           item.condition_id,
      side:                   item.side,
      position_change_pusd:   item.position_change_pusd,
      price:                  item.price,
      block_number:           item.block_number,
      tx_hash:                item.tx_hash,
      publish_delay_applied_s: params.publish_delay_s,
      warnings:               item.warnings,
      emitted_at_ms:          now_ms()
    }

  last_processed_block = block_number

SDK calls used

  • rpc.getLogs(address=CTFEXCHANGEV2_ADDR, topics=[ORDER_FILLED_SIG], fromBlock, toBlock)
  • data_api.GET('/token/<tokenId>/condition')
  • toPusdUnits(fillAmount)

Complexity: O(E) per block where E = OrderFilled events for watched wallets; typically O(1) in steady state

11. Wire Examples

Input — what arrives on the wire

{
  "label": "Polygon RPC CTFExchangeV2 OrderFilled event for watched wallet",
  "source": "onchain",
  "payload": {
    "event": "OrderFilled",
    "makerAddress": "0xF1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0",
    "takerAddress": "0x000000000000000000000000000000000000dead",
    "tokenId": "87654321",
    "fillAmount": "12500000000",
    "price": "0.68",
    "transactionHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab",
    "block_number": 72346001,
    "timestamp_ms": 1746703000000
  }
}

Output — what the bot emits

{
  "label": "ObservationReport — high-signal wallet BUY detected",
  "payload": {
    "report_id": "rep_ocw_0xf1a2_1746703000000",
    "trace_id": "trc_0xbeef0102030405060708",
    "bot_id": "intel.onchainwatcher",
    "kind": "ObservationReport",
    "wallet_address": "0xF1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0",
    "condition_id": "0xf1a2b30000000000000000000000000000000000000000000000000000000000",
    "side": "BUY",
    "position_change_pusd": 12500,
    "price": 0.68,
    "block_number": 72346001,
    "tx_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab",
    "publish_delay_applied_s": 15,
    "warnings": [],
    "emitted_at_ms": 1746703015000
  }
}

12. Decision Logic

APPROVE

Not applicable — OnChainWatcher is read-only; it never approves or submits orders.

RESHAPE_REQUIRED

Not applicable.

REJECT

Events below the hard dust floor (50 pUSD) are silently discarded. Events are suppressed (not emitted) only when KillSwitch is active (KILL_SWITCH_ACTIVE).

WARNING_ONLY

ONCHAINWATCHER_SMALL_POSITION is included as a warning when position_change_pusd is between the hard floor and the default threshold.

13. Standard Decision Output

This bot returns a ObservationReport object. See ObservationReport schema.

{
  "report_id": "rep_ocw_0xf1a2_1746703000000",
  "trace_id": "trc_0xbeef0102030405060708",
  "bot_id": "intel.onchainwatcher",
  "kind": "ObservationReport",
  "wallet_address": "0xF1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0",
  "condition_id": "0xf1a2b30000000000000000000000000000000000000000000000000000000000",
  "side": "BUY",
  "position_change_pusd": 12500,
  "price": 0.68,
  "block_number": 72346001,
  "tx_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab",
  "publish_delay_applied_s": 15,
  "warnings": [],
  "emitted_at_ms": 1746703015000
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
ONCHAINWATCHER_LARGE_WALLET_ENTRYWARNWatched wallet made a position change >= min_position_change_usd on a Polymarket market.Emit ObservationReport after publish_delay_s; downstream strategies consume as smart-money signal.A significant wallet movement was detected on this market.
ONCHAINWATCHER_SMALL_POSITIONWARNWallet position change is between the hard floor (50 pUSD) and default threshold (500 pUSD).Emit ObservationReport with ONCHAINWATCHER_SMALL_POSITION warning; strategies apply lower weight to this signal.
ONCHAINWATCHER_RESOLUTION_RETRYWARNtoken_id could not be resolved to a condition_id via data_api; event buffered for retry.Buffer event; retry on next block; emit ObservationReport once resolved.
STALE_DATAWARNRPC provider unresponsive for > 2× poll_interval_s; on-chain state may be stale.Halt ObservationReport emissions until RPC recovers; alert on-call.
KILL_SWITCH_ACTIVEHARD_REJECTKillSwitch active; ObservationReport emissions suppressed.Continue monitoring and queuing events but suppress all emissions.Wallet monitoring is paused while trading is suspended system-wide.
MARKET_CLOSEDEXPLAINOrderFilled event received for a condition_id that is already closed or resolved.Skip emission; log for audit trail only.
PARAMETER_CHANGE_REQUIRES_APPROVALHARD_REJECTA parameter change violates a locked bound (e.g. publish_delay_s < 5 or poll_interval_s > 60).Reject config change; do not apply.
ONCHAINWATCHER_EMPTY_WALLET_LISTWARNwatched_wallets config is empty; no on-chain events will be captured.Log WARN at startup; emit no ObservationReports until wallets are configured.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_intel_onchainwatcher_events_detected_totalcountercountwallet_addressOn-chain events detected for watched wallets per block cycle.
polytraders_intel_onchainwatcher_observations_emitted_totalcountercountsideObservationReports emitted after publish_delay_s, broken down by side (BUY/SELL).
polytraders_intel_onchainwatcher_dust_skipped_totalcountercountEvents discarded as dust (below min_position_change_usd hard floor).
polytraders_intel_onchainwatcher_rpc_block_lag_sgaugesecondsAge of the most recently processed Polygon block.
polytraders_intel_onchainwatcher_publish_queue_depthgaugecountNumber of detected events currently sitting in the publish queue awaiting publish_delay_s.
polytraders_intel_onchainwatcher_resolution_retries_totalcountercountTotal token_id -> condition_id resolution retries due to data_api failures.

Alerts

AlertConditionSeverityRunbook
OnChainWatcherRPCDownpolytraders_intel_onchainwatcher_rpc_block_lag_s > 60page#runbook-onchainwatcher-rpc-down
OnChainWatcherQueueBuilduppolytraders_intel_onchainwatcher_publish_queue_depth > 50warn#runbook-onchainwatcher-queue-buildup
OnChainWatcherStaleDatarate(polytraders_intel_onchainwatcher_events_detected_total[10m]) == 0 AND polytraders_intel_onchainwatcher_rpc_block_lag_s < 60warn#runbook-onchainwatcher-stale-data
OnChainWatcherHighResolutionRetriesrate(polytraders_intel_onchainwatcher_resolution_retries_total[10m]) > 5warn#runbook-onchainwatcher-resolution-retries

Dashboards

  • Grafana — Intelligence / OnChainWatcher wallet event rate and queue depth
  • Grafana — Intelligence / per-wallet position change size distribution

16. Developer Reporting

{
  "bot_id": "intel.onchainwatcher",
  "block_number": 72346001,
  "events_detected": 8,
  "events_emitted": 3,
  "events_queued": 2,
  "dust_skipped": 3,
  "condition_id_resolutions_failed": 0,
  "killswitch_active": false
}

17. Plain-English Reporting

SituationUser-facing explanation
Strategy increased size on a market after wallet signalA tracked high-activity wallet made a significant move on this market. The system detected this as a potential informed signal and adjusted positioning accordingly.
Signal delay before strategy actsWallet signals are intentionally held briefly before being published to ensure no participant has an unfair advantage based on on-chain latency.
Strategy reduced size on a market after wallet exit signalA tracked wallet reduced its position. This was treated as a potential exit signal, and exposure was trimmed as a precaution.

18. Failure-Mode Block

main_failure_modeRPC provider outage during a high-activity period causes OnChainWatcher to miss wallet fills, leading strategies to miss smart-money entry signals and potentially enter markets after the signal window has closed.
false_positive_riskA wash-trade or self-transfer between two watched wallets is counted as a large position change, generating a spurious buy signal on a market where no genuine informed entry occurred.
false_negative_riskA watched wallet operating through a multi-step proxy contract has its CTFExchangeV2 fills attributed to an intermediate address not in watched_wallets, causing the signal to be missed entirely.
safe_fallbackIf RPC is unavailable for > 2× poll_interval_s, emit STALE_DATA WARN and halt new ObservationReport emissions until RPC recovers. Do not emit from buffered events based on data older than 3 blocks.
required_dependenciesPolygon RPC (event log access), Polymarket Data API for token_id → condition_id resolution, KillSwitch active flag readable, watched_wallets config non-empty

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
RPC_OUTAGEBlock TCP to primary Polygon RPC for 30 sAutomatic failover to secondary RPC; missed blocks re-fetched; queued events emitted after publish_delay_s
DATA_API_DOWNReturn 503 from data_api for 60 sOnce data_api recovers, buffered events resolved and emitted
KILL_SWITCH_ONSet killswitch.active=true; inject large wallet BUY eventQueued events emitted on first drain tick after KillSwitch reset
DUST_FLOODInject 100 OrderFilled events of 10 pUSD each for watched walletsAutomatic; no action required
QUEUE_BUILDUPInject 60 large wallet events while blocking emit drain for 30 sOnce drain resumes, remaining queued events emitted normally

20. State & Persistence

Cold-start recovery

On cold start, last_processed_block loaded from Redis. Missed blocks since last checkpoint re-fetched from RPC. publish_queue drained normally.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight1
Idempotency keytx_hash + wallet_address
Per-call timeout (ms)5000
Backpressure strategydrop-after-buffer — if publish_queue > 200 entries, oldest entries dropped with STALE_DATA warning
Locking / mutual exclusionRedis SETNX on tx_hash to prevent duplicate event processing across restarts

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchKillSwitch gate suppresses ObservationReport emissions.

Emits to (downstream consumers)

BotWhyContract
risk.liquidity_guard
strat.liquidity_aware_strategies

Used by (auto-aggregated)

4.14

External services

ServiceEndpointSLA assumedOn failure
Polygon RPC (Alchemy / Infura)Polygon mainnet99.9% / 200 ms p99
Polymarket Data API (token_id resolution)https://data-api.polymarket.com99.9% / 500 ms p99

23. Security Surfaces

Abuse vectors considered

  • Adversary discovers watched_wallets list and wash-trades to inject false buy/sell signals
  • RPC provider returns crafted event logs to fake large wallet fills for a specific condition_id

Mitigations

  • watched_wallets list is operator-confidential config; not exposed in ObservationReport payloads
  • publish_delay_s minimum (5 s) prevents use as a sub-second front-run vector
  • All ObservationReports are informational only — LiquidityGuard and strategies independently validate market state before acting on signals

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesOnChainWatcher subscribes to CTFExchangeV2 OrderFilled events on Polygon; all position_change_pusd values are denominated in pUSD. The V1 CTFExchange contract is no longer monitored.

API surfaces declared

onchaindata_apiinternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation2.1.0
schema2
released2026-04-28

Migration history

DateFromToReasonAction taken
2026-04-28v1v2CLOB V2 cutover — CTFExchangeV2 contract address, pUSD denomination, and updated OrderFilled event ABIUpdated on-chain subscription to CTFExchangeV2 (V1 CTFExchange removed). Position change amounts updated from USDC.e to pUSD denomination. OrderFilled ABI updated for V2 field names (removed nonce/feeRateBps from event). No signed-order plumbing in this bot.

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Large wallet BUY above threshold emits ObservationReport after publish_delay_sOrderFilled event for watched wallet, position_change_pusd=12500, publish_delay_s=15ObservationReport emitted 15 s after event detection with side=BUY, position_change_pusd=12500
Dust event below hard floor discarded silentlyOrderFilled event, position_change_pusd=30 pUSD, hard floor=50No ObservationReport; dust_skipped counter incremented
Small position between hard floor and default emits with ONCHAINWATCHER_SMALL_POSITION warningposition_change_pusd=300, min_position_change_usd default=500, hard=50ObservationReport emitted with warnings=['ONCHAINWATCHER_SMALL_POSITION']
KillSwitch suppresses emissions but queuing continueskillswitch.active=true; large wallet BUY detectedEvent queued; no ObservationReport emitted; KILL_SWITCH_ACTIVE logged
RPC outage triggers STALE_DATA and halts emissionsRPC unavailable for 30 s (> 2× poll_interval_s=12)STALE_DATA WARN logged; no ObservationReports emitted during outage
Non-watched wallet event discardedOrderFilled event from address not in watched_walletsEvent silently discarded; no metrics incremented

Integration Tests

TestExpected result
Full lifecycle: wallet BUY detected, delayed, emitted, consumed by LiquidityGuardLiquidityGuard receives ObservationReport with correct condition_id, side=BUY, and position_change_pusd
RPC failover: primary RPC down, secondary takes over, missed blocks re-fetchedBuffered events emitted after RPC recovery with correct block_number
Data API outage delays condition_id resolution; event eventually resolved and emittedEvent buffered; ObservationReport emitted once data_api returns condition_id mapping

Property Tests

PropertyRequired behaviour
OnChainWatcher never submits, signs, or modifies any orderAlways true
No ObservationReport emitted when KillSwitch is activeAlways true
publish_delay_s >= 5 enforced; all emissions are delayedAlways true

27. Operational Runbook

OnChainWatcher incidents are most commonly RPC outages or data_api failures preventing condition_id resolution. An RPC outage causes smart-money signals to be missed for the outage duration. Page immediately on RPC down.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
OnChainWatcherRPCDown
OnChainWatcherQueueBuildup
OnChainWatcherStaleData
OnChainWatcherHighResolutionRetries

Manual overrides

Healthcheck

Endpoint: /internal/health/onchainwatcher | Green: rpc_block_lag_s < 24 AND Redis reachable AND publish_queue_depth < 50 AND watched_wallets non-empty | Red: rpc_block_lag_s > 60 OR Redis unreachable OR watched_wallets empty OR publish_queue_depth > 200

28. Promotion Gates

A bot does not advance to the next readiness state until every gate below is green. Gates are observable from production data — no subjective sign-off.

Promote to Shadow

GateHow measuredThreshold
Unit tests pass for dust filter, publish delay, KillSwitch suppression, and resolution retryCI test run100% pass
RPC integration test: OrderFilled event for watched wallet detected and queued correctlyIntegration test against Polygon testnetPass

Promote to Limited live

GateHow measuredThreshold
rpc_block_lag_s p99 < 15 s over 48 hpolytraders_intel_onchainwatcher_rpc_block_lag_s gaugep99 < 15 s
publish_delay_s enforced: no ObservationReport emitted before publish_delay_s after event detectionemitted_at_ms - event.timestamp_ms > publish_delay_s * 1000 for all emissions100% compliance

Promote to General live

GateHow measuredThreshold
Zero missed wallet events during 14-day soak (verified by post-hoc on-chain reconciliation)On-chain event log reconciliation script comparing detected vs on-chain fills0 missed qualifying events
KillSwitch suppression: zero ObservationReports when KillSwitch activeIntegration testPass
Dust filter: zero ObservationReports for events < 50 pUSDIntegration test with simulated dust eventsPass

29. Developer Checklist

Ready-to-ship score: 27/27 sections complete · 100%

RequirementStatus
Purpose defined✓ done
Required inputs listed✓ done
Parameters defined✓ done
Defaults defined✓ done
Warning thresholds defined✓ done
Hard thresholds defined✓ done
Safe fallback defined✓ done
Structured output defined✓ done
Developer log defined✓ done
Plain-English explanation✓ done
Unit tests defined✓ done
Integration tests defined✓ done
Property tests defined✓ done
Failure-mode block complete✓ done
Reference implementation pseudocode✓ done
Wire examples (input + output)✓ done
Reason codes listed✓ done
Metrics & logs defined✓ done
State & persistence defined✓ done
Concurrency & idempotency defined✓ done
Dependencies declared✓ done
Security surfaces declared✓ done
Polymarket V2 compatibility declared✓ done
Version & migration history declared✓ done
Operational runbook defined✓ done
Promotion gates defined✓ done
Failure-injection recipes defined✓ done