1. Bot Identity
| Layer | Security Security |
|---|
| Bot class | Guardrail |
|---|
| Authority | RejectPause |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Order signing |
|---|
| Runs after | RPCFailoverManager elects primary provider |
|---|
| Applies to | Every order before signing — verifies chain-derived fields against multiple RPC sources |
|---|
| Default mode | shadow_only |
|---|
| User-visible | Advanced details only |
|---|
| Developer owner | Polytraders core |
|---|
Operational profile
| Modes supported | quarantine |
|---|
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.
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|
| require_quorum | 2 | Only 2 of 3 providers agree on state | Fewer than require_quorum providers agree on block hash | Minimum number of RPC providers that must agree on block state before signing. |
| halt_on_mismatch | True | Block hash mismatch detected between providers | Block hash mismatch with halt_on_mismatch=true | If 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
| Condition | Action |
|---|
| agreeing_count >= require_quorum | APPROVE — proceed to signing |
| agreeing_count < require_quorum | REJECT — 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
| Condition | Action |
|---|
| block hashes agree across require_quorum providers | APPROVE |
| block hash mismatch AND halt_on_mismatch=true | REJECT — 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
- Receive order before signing.
- Check KillSwitch; if active, REJECT(KILL_SWITCH_ACTIVE).
- FETCH primary provider from RPCFailoverManager.
- FETCH block_number and block_hash from primary and at least one secondary provider.
- Compare block hashes at same block_number across providers.
- If mismatch detected and halt_on_mismatch=true: REJECT(CHAIN_STATE_MISMATCH).
- Check reorg depth; if > reorg_depth_alert blocks, emit WARN.
- FETCH pUSD balance of trading wallet from primary provider.
- If balance < order.size_usd: REJECT(CHAIN_STATE_MISMATCH) — insufficient collateral.
- 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 check — internal
{
"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
| Code | Severity | Meaning | Action | User-facing message |
|---|
KILL_SWITCH_ACTIVE | HARD_REJECT | Global kill switch is active. | Immediately return DENY. | Trading is currently paused. |
CHAIN_STATE_MISMATCH | HARD_REJECT | Block hash disagreement across providers, reorg detected, or insufficient wallet balance. | Return DENY; emit alert. | A chain inconsistency was detected. Orders are paused. |
RPC_QUORUM_LOST | HARD_REJECT | Fewer than require_quorum providers responded. | Return DENY; defer to RPCFailoverManager. | Not enough network providers available. |
CHAIN_STATE_REORG_WARN | WARN | Reorg depth approaching reorg_depth_alert threshold. | Emit warn; continue checking. | A minor chain reorganisation is in progress. |
CHAIN_STATE_OK | INFO | All checks passed; chain state verified. | Emit RiskVote(APPROVE). | |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_sec_chainstateverifier_decisions_total | counter | count | decision | Total chain state verification decisions. |
polytraders_sec_chainstateverifier_mismatches_total | counter | count | reason | Block hash mismatches or balance failures detected. |
polytraders_sec_chainstateverifier_quorum_count | gauge | count | | Current number of providers in quorum agreement. |
polytraders_sec_chainstateverifier_verify_latency_ms | histogram | ms | | Total latency of chain state verification. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
ChainStateMismatch | rate(polytraders_sec_chainstateverifier_mismatches_total[5m]) > 0 | P0 | #runbook-chainstate-mismatch |
ChainStateQuorumLow | polytraders_sec_chainstateverifier_quorum_count < require_quorum | P1 | #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
| Situation | User-facing explanation |
|---|
| Orders paused — chain state mismatch | A network inconsistency was detected. Orders are paused until the network is consistent. |
| Orders paused — insufficient balance | Your pUSD balance is below the order size. Please top up your wallet. |
| Reorg warning | A chain reorganisation was detected. Your orders have been paused briefly as a precaution. |
18. Failure-Mode Block
| main_failure_mode | All providers agree on a stale or forked state, causing the quorum check to pass while chain data is wrong. |
|---|
| false_positive_risk | Brief network partition causes providers to temporarily disagree, triggering halt_on_mismatch for legitimate orders. |
|---|
| false_negative_risk | Quorum of 2 compromised providers pass while a third honest provider disagrees but is below quorum. |
|---|
| safe_fallback | If fewer than require_quorum providers respond, fail-closed: REJECT until quorum is restored. |
|---|
| required_dependencies | RPCFailoverManager for provider health, Polygon RPC pool, KillSwitch |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
BLOCK_HASH_MISMATCH | Return different block hashes from two providers at same block number | | Automatic when providers re-sync. |
INSUFFICIENT_BALANCE | Set wallet pUSD balance to 0 | | User tops up wallet. |
REORG_DETECTION | Present chain tip that reorgs 3 blocks | | Automatic when chain stabilises. |
20. State & Persistence
Cold-start recovery
Re-fetch from all providers on restart; fail-closed if quorum unavailable.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | parallel provider fetches per order |
| Max in-flight | 200 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 500 |
| Backpressure strategy | drop newest above queue depth |
| Locking / mutual exclusion | read-write lock on block state cache |
22. Dependencies
Depends on (must run first)
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|
| gov.builder_attribution | Log block state verification results. | GovernanceLog entry on each APPROVE or DENY. |
Sibling bots (same OrderIntent)
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| Polygon RPC pool | Configured provider endpoints | best-effort | DENY 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
| Aspect | Value |
|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | no |
| Aware of negative-risk markets | no |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | Verifies 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
| Field | Value |
|---|
| spec | 2.0.0 |
| implementation | 0.1.0 |
| schema | 2 |
| released | None |
| planned_release | Q3-2026 |
Migration history
| Date | From | To | Reason | Action taken |
|---|
| 2026-04-28 | n/a | v2-spec | Spec drafted post-CLOB-V2 cutover; bot not yet implemented | Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain) |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|
| Approve when block hashes agree across require_quorum providers | 2 providers return same block hash at block 58420100 | APPROVE with block evidence |
| Reject when block hashes diverge and halt_on_mismatch=true | provider A hash != provider B hash at same block | DENY(CHAIN_STATE_MISMATCH) |
| Reject when wallet balance < order size | balance=100 pUSD, order size=400 pUSD | DENY(CHAIN_STATE_MISMATCH) |
Integration Tests
| Test | Expected result |
|---|
| Reorg detected — halt fires and alert emitted | DENY(CHAIN_STATE_MISMATCH) and alert; auto-resume when chain stabilises |
| RPCFailoverManager quorum lost propagates to ChainStateVerifier | DENY(RPC_QUORUM_LOST) before any balance check |
Property Tests
| Property | Required behaviour |
|---|
| Block hash disagreement always produces DENY when halt_on_mismatch=true | Always true |
| Insufficient balance always produces DENY regardless of chain state | Always true |
27. Operational Runbook
ChainStateMismatch is a P0 event. Investigate provider disagreement immediately — could indicate network attack or RPC failure.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate 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.
29. Developer Checklist
Ready-to-ship score: 27/27 sections complete · 100%
| Requirement | Status |
|---|
| 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 |