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 LayerSecurity5.7 ChainStateVerifier

5.7 ChainStateVerifier

Security Guardrail RejectPause PLANNED Spec started capital · Direct P5 · Execution rails pending stub

Cross-check every order’s chain-derived inputs (nonce, balance, allowance) against multiple sources before signing.

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

LayerSecurity  Security
Bot classGuardrail
AuthorityRejectPause
StatusPLANNED
ReadinessSpec started
Runs beforeOrder signing
Runs afterRPCFailoverManager elects primary provider
Applies toEvery order before signing — verifies chain-derived fields against multiple RPC sources
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

Operational profile

Modes supportedquarantine

2. Purpose

Cross-check every order’s chain-derived inputs (nonce, balance, allowance) against multiple sources before signing.

3. Why This Bot Matters

  • Single RPC source used for nonce/balance without cross-checking

    A stale or compromised RPC causes double-spend attempts or orders built on wrong nonce.

  • Reorg not detected before signing

    An order signed on top of a reorged block may reference a state that no longer exists, causing failed settlement.

  • Balance check skipped

    Orders submitted without sufficient pUSD collateral fail at settlement, wasting gas and degrading UX.

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
pUSD balance of trading walletonchainYesVerify wallet has sufficient collateral before signing.
Latest block number and block hashonchainYesCross-check across providers to detect reorg or fork.

5. Required Internal Inputs

InputSourceRequired?Use
Elected primary RPC provider from RPCFailoverManagerRPCFailoverManagerYesDetermine which provider to use as primary; verify against secondary.
KillSwitch active flagKillSwitchYesBlock all chain reads during global pause.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
require_quorum2Only 2 of 3 providers agree on stateFewer than require_quorum providers agree on block hashMinimum number of RPC providers that must agree on block state before signing.
halt_on_mismatchTrueBlock hash mismatch detected between providersBlock hash mismatch with halt_on_mismatch=trueIf true, halt all signing until providers agree on block state.

7. Detailed Parameter Instructions

require_quorum

What it means

Minimum number of RPC providers that must agree on block state before signing.

Default

{ "require_quorum": 2 }

Why this default matters

Quorum of 2 prevents a single compromised provider from poisoning the chain state view.

Threshold logic

ConditionAction
agreeing_count >= require_quorumAPPROVE — proceed to signing
agreeing_count < require_quorumREJECT — CHAIN_STATE_MISMATCH

Developer check

if (agreeing.count < p.require_quorum) return reject('CHAIN_STATE_MISMATCH');

User-facing English

Chain state could not be verified. Orders are paused.

halt_on_mismatch

What it means

If true, halt all signing until providers agree on block state.

Default

{ "halt_on_mismatch": true }

Why this default matters

Halting on mismatch prevents signing on a potentially reorged state.

Threshold logic

ConditionAction
block hashes agree across require_quorum providersAPPROVE
block hash mismatch AND halt_on_mismatch=trueREJECT — CHAIN_STATE_MISMATCH

Developer check

if (p.halt_on_mismatch && !quorumAgreesOnBlockHash()) return reject('CHAIN_STATE_MISMATCH');

User-facing English

A network inconsistency was detected. Orders are paused until it resolves.

8. Default Configuration

{
  "bot_id": "sec.chain_state_verifier",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "require_quorum": 2,
    "halt_on_mismatch": true,
    "reorg_depth_alert": 2,
    "publish_to": [
      "governance_audit",
      "risk_compliance"
    ]
  }
}

9. Implementation Flow

  1. Receive order before signing.
  2. Check KillSwitch; if active, REJECT(KILL_SWITCH_ACTIVE).
  3. FETCH primary provider from RPCFailoverManager.
  4. FETCH block_number and block_hash from primary and at least one secondary provider.
  5. Compare block hashes at same block_number across providers.
  6. If mismatch detected and halt_on_mismatch=true: REJECT(CHAIN_STATE_MISMATCH).
  7. Check reorg depth; if > reorg_depth_alert blocks, emit WARN.
  8. FETCH pUSD balance of trading wallet from primary provider.
  9. If balance < order.size_usd: REJECT(CHAIN_STATE_MISMATCH) — insufficient collateral.
  10. Emit RiskVote(APPROVE) with block_hash, block_number, and balance snapshot.

10. Reference Implementation

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. IF/THEN/ELSE = decision. Translate directly to TypeScript, Python, Go, or Rust.

// ChainStateVerifier
FUNCTION verifyChainState(pendingOrder, providers):
  IF FETCH(internal.killswitch).active:
    EMIT RiskVote(DENY, KILL_SWITCH_ACTIVE); RETURN
  // Fetch block state from multiple providers
  blockStates = []
  FOR provider IN providers:
    state = FETCH(provider.eth_getBlockByNumber('latest'))
    IF state != null: blockStates.append({provider, block_number: state.number, hash: state.hash})
  // Quorum check on block hash
  hashCounts = COUNT_BY(blockStates, x => x.hash)
  topHash = hashCounts.max_count_hash
  agreeing = blockStates.filter(x => x.hash == topHash)
  IF agreeing.count < params.require_quorum:
    EMIT RiskVote(DENY, CHAIN_STATE_MISMATCH); RETURN
  // Balance check
  balance = FETCH(primary.erc20.balanceOf(pUSD, pendingOrder.wallet))
  IF balance < pendingOrder.size_usd:
    EMIT RiskVote(DENY, CHAIN_STATE_MISMATCH); RETURN
  EMIT RiskVote(APPROVE, {block_number: topHash.block_number, balance})
  LOG(governance.audit, {block_number, balance, quorum_count: agreeing.count})

SDK calls used

  • provider.eth_getBlockByNumber('latest')
  • provider.erc20.balanceOf(pUSD_ADDRESS, wallet)
  • internal.killswitch.status()

Complexity: O(p) where p = provider count; single block per provider

11. Wire Examples

Input — what arrives on the wire

Pending order requiring chain state checkinternal

{
  "intent_id": "int_6f7a8b9c0d1e2f3a",
  "wallet": "0xUserWallet123",
  "size_usd": 400,
  "contract_address": "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E",
  "timestamp_ms": 1746768672000
}

Output — what the bot emits

RiskVote — APPROVE

{
  "vote_id": "sec.chain_state_verifier.20260509T180000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "block_number": 58420100,
    "quorum_count": 3,
    "balance_pusd": 1200
  },
  "checked_at": "2026-05-09T18:00:00Z"
}

12. Decision Logic

APPROVE

Block state agrees across require_quorum providers, no reorg detected, and wallet balance sufficient.

RESHAPE_REQUIRED

Not applicable — verifier approves or rejects.

REJECT

Block hash mismatch across providers, reorg detected, or insufficient balance.

WARNING_ONLY

Warn when reorg depth approaches reorg_depth_alert.

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "vote_id": "sec.chain_state_verifier.20260509T180000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "block_number": 58420100,
    "block_hash": "0xabc123def456",
    "quorum_count": 3,
    "balance_pusd": 1200,
    "order_size_pusd": 400
  },
  "checked_at": "2026-05-09T18:00:00Z"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Immediately return DENY.Trading is currently paused.
CHAIN_STATE_MISMATCHHARD_REJECTBlock hash disagreement across providers, reorg detected, or insufficient wallet balance.Return DENY; emit alert.A chain inconsistency was detected. Orders are paused.
RPC_QUORUM_LOSTHARD_REJECTFewer than require_quorum providers responded.Return DENY; defer to RPCFailoverManager.Not enough network providers available.
CHAIN_STATE_REORG_WARNWARNReorg depth approaching reorg_depth_alert threshold.Emit warn; continue checking.A minor chain reorganisation is in progress.
CHAIN_STATE_OKINFOAll checks passed; chain state verified.Emit RiskVote(APPROVE).

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_sec_chainstateverifier_decisions_totalcountercountdecisionTotal chain state verification decisions.
polytraders_sec_chainstateverifier_mismatches_totalcountercountreasonBlock hash mismatches or balance failures detected.
polytraders_sec_chainstateverifier_quorum_countgaugecountCurrent number of providers in quorum agreement.
polytraders_sec_chainstateverifier_verify_latency_mshistogrammsTotal latency of chain state verification.

Alerts

AlertConditionSeverityRunbook
ChainStateMismatchrate(polytraders_sec_chainstateverifier_mismatches_total[5m]) > 0P0#runbook-chainstate-mismatch
ChainStateQuorumLowpolytraders_sec_chainstateverifier_quorum_count < require_quorumP1#runbook-chainstate-quorum

16. Developer Reporting

{
  "bot_id": "sec.chain_state_verifier",
  "decision": "APPROVE",
  "inputs_used": [
    "onchain.block_hash",
    "onchain.pusd_balance",
    "rpc_failover.primary"
  ],
  "checked_at": "2026-05-09T18:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Orders paused — chain state mismatchA network inconsistency was detected. Orders are paused until the network is consistent.
Orders paused — insufficient balanceYour pUSD balance is below the order size. Please top up your wallet.
Reorg warningA chain reorganisation was detected. Your orders have been paused briefly as a precaution.

18. Failure-Mode Block

main_failure_modeAll providers agree on a stale or forked state, causing the quorum check to pass while chain data is wrong.
false_positive_riskBrief network partition causes providers to temporarily disagree, triggering halt_on_mismatch for legitimate orders.
false_negative_riskQuorum of 2 compromised providers pass while a third honest provider disagrees but is below quorum.
safe_fallbackIf fewer than require_quorum providers respond, fail-closed: REJECT until quorum is restored.
required_dependenciesRPCFailoverManager for provider health, Polygon RPC pool, KillSwitch

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
BLOCK_HASH_MISMATCHReturn different block hashes from two providers at same block numberAutomatic when providers re-sync.
INSUFFICIENT_BALANCESet wallet pUSD balance to 0User tops up wallet.
REORG_DETECTIONPresent chain tip that reorgs 3 blocksAutomatic when chain stabilises.

20. State & Persistence

Cold-start recovery

Re-fetch from all providers on restart; fail-closed if quorum unavailable.

21. Concurrency & Idempotency

AspectSpecification
Execution modelparallel provider fetches per order
Max in-flight200
Idempotency keyintent_id
Per-call timeout (ms)500
Backpressure strategydrop newest above queue depth
Locking / mutual exclusionread-write lock on block state cache

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchKillSwitch gate.DENY(KILL_SWITCH_ACTIVE) short-circuits.
sec.rpc_failover_managerProvides healthy primary RPC provider.Primary provider from RPCFailoverManager used for block state read.

Emits to (downstream consumers)

BotWhyContract
gov.builder_attributionLog block state verification results.GovernanceLog entry on each APPROVE or DENY.

Sibling bots (same OrderIntent)

External services

ServiceEndpointSLA assumedOn failure
Polygon RPC poolConfigured provider endpointsbest-effortDENY if quorum cannot be established.

23. Security Surfaces

Abuse vectors considered

  • Colluding RPC providers presenting a forged fork to pass quorum check
  • Stale balance read causing order submission despite insufficient pUSD

Mitigations

  • Quorum requires agreement across independently configured providers
  • Balance checked immediately before signing; no caching of positive balance

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
NotesVerifies pUSD balance and Polygon block state before CTFExchangeV2 order signing; uses quorum across V2 RPC providers.

API surfaces declared

onchaininternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ3-2026

Migration history

DateFromToReasonAction taken
2026-04-28n/av2-specSpec drafted post-CLOB-V2 cutover; bot not yet implementedDesigned against V2 schema (pUSD, builder codes, V2 EIP-712 domain)

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Approve when block hashes agree across require_quorum providers2 providers return same block hash at block 58420100APPROVE with block evidence
Reject when block hashes diverge and halt_on_mismatch=trueprovider A hash != provider B hash at same blockDENY(CHAIN_STATE_MISMATCH)
Reject when wallet balance < order sizebalance=100 pUSD, order size=400 pUSDDENY(CHAIN_STATE_MISMATCH)

Integration Tests

TestExpected result
Reorg detected — halt fires and alert emittedDENY(CHAIN_STATE_MISMATCH) and alert; auto-resume when chain stabilises
RPCFailoverManager quorum lost propagates to ChainStateVerifierDENY(RPC_QUORUM_LOST) before any balance check

Property Tests

PropertyRequired behaviour
Block hash disagreement always produces DENY when halt_on_mismatch=trueAlways true
Insufficient balance always produces DENY regardless of chain stateAlways true

27. Operational Runbook

ChainStateMismatch is a P0 event. Investigate provider disagreement immediately — could indicate network attack or RPC failure.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
ChainStateMismatch
ChainStateQuorumLow

Manual overrides

Healthcheck

GET /internal/health/chainstateverifier → green if Quorum met; last successful verification within 10s.; red if Quorum < require_quorum or no successful verification in 60s.

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 block hash quorum and balance checkCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Block hash mismatch injection fires DENY and alert correctlyFailure injection testPass

Promote to General live

GateHow measuredThreshold
Zero ChainStateMismatch alerts in 48h shadowGrafana alert history0 alerts

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