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.2 AllowanceMonitor

5.2 AllowanceMonitor

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

Track ERC-20 allowances per token and contract; alert and shrink to a tight ceiling on idle.

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 beforeAny order that requires an on-chain allowance
Runs afterStrategy OrderIntent
Applies toAll ERC-20 allowances on Polygon for the trading wallet
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

Operational profile

Modes supportedquarantine

2. Purpose

Track ERC-20 allowances per token and contract; alert and shrink to a tight ceiling on idle.

3. Why This Bot Matters

  • Unbounded ERC-20 allowance left idle

    A compromised or malicious contract can drain the wallet's pUSD balance at any future time.

  • Allowance not shrunk after idle period

    Unlimited approvals compound risk; industry incidents show dormant allowances are frequently exploited.

  • No alert on unbounded approval

    Operators remain unaware of excessive exposure until an incident occurs.

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
ERC-20 allowance(owner, spender) on PolygononchainYesRead current pUSD allowance for CTFExchangeV2 and NegRiskAdapter.
Block timestamp for last useonchainYesDetermine idle duration to trigger auto-shrink.

5. Required Internal Inputs

InputSourceRequired?Use
Configured max_allowance_usd and idle_revoke_hAdmin UIYesThresholds for alert and auto-shrink decisions.
KillSwitch active flagKillSwitchYesHalt allowance operations when kill switch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
max_allowance_usd500Current allowance > 0.9 * max_allowance_usdCurrent allowance > max_allowance_usd (unbounded detected)Maximum pUSD allowance tolerated for any (token, spender) pair.
auto_shrinkTrueauto_shrink disabled and allowance > max_allowance_usdWhen true, automatically submit a shrink-to-exact transaction before signing.

7. Detailed Parameter Instructions

max_allowance_usd

What it means

Maximum pUSD allowance tolerated for any (token, spender) pair.

Default

{ "max_allowance_usd": 500 }

Why this default matters

Conservative ceiling limits blast radius if the spender contract is compromised.

Threshold logic

ConditionAction
allowance <= max_allowance_usdAPPROVE
allowance > max_allowance_usd AND auto_shrink=trueEmit shrink tx, then APPROVE
allowance > max_allowance_usd AND auto_shrink=falseREJECT — ALLOWANCE_EXCEEDS_CEILING

Developer check

if (allowance > p.max_allowance_usd && !p.auto_shrink) return reject('ALLOWANCE_EXCEEDS_CEILING');

User-facing English

Your pUSD approval for this contract exceeds the safety ceiling and will be reduced.

auto_shrink

What it means

When true, automatically submit a shrink-to-exact transaction before signing.

Default

{ "auto_shrink": true }

Why this default matters

Auto-shrink on by default closes the window proactively without requiring manual intervention.

Threshold logic

ConditionAction
auto_shrink=true AND allowance > max_allowance_usdSubmit approve(spender, exact_needed) on-chain before signing
auto_shrink=falseAlert only; no on-chain action

Developer check

if (p.auto_shrink && allowance > needed) await shrinkAllowance(token, spender, needed);

User-facing English

Your approval has been automatically adjusted to the minimum required for this order.

8. Default Configuration

{
  "bot_id": "sec.allowance_monitor",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "max_allowance_usd": 500,
    "idle_revoke_h": 48,
    "auto_shrink": true,
    "alert_on_unbounded": true
  }
}

9. Implementation Flow

  1. Receive order intent before submission.
  2. Check KillSwitch; if active, REJECT(KILL_SWITCH_ACTIVE).
  3. FETCH current pUSD allowance for (CTFExchangeV2, wallet) from Polygon RPC.
  4. If allowance > max_allowance_usd and auto_shrink=true: submit shrink tx, wait for confirmation.
  5. If allowance > max_allowance_usd and auto_shrink=false: REJECT(ALLOWANCE_EXCEEDS_CEILING).
  6. Check idle duration; if > idle_revoke_h: trigger revoke allowance for idle spenders.
  7. Emit RiskVote(APPROVE) with current allowance snapshot.
  8. Log result to governance audit trail.

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.

// AllowanceMonitor
FUNCTION checkAllowance(pendingOrder, wallet):
  IF FETCH(internal.killswitch).active:
    EMIT RiskVote(DENY, KILL_SWITCH_ACTIVE); RETURN
  spender = pendingOrder.contract_address  // CTFExchangeV2
  allowance = FETCH(onchain.erc20.allowance(pUSD, wallet, spender))
  IF allowance == null:  // RPC failure
    EMIT RiskVote(DENY, STALE_DATA); RETURN
  IF allowance > params.max_allowance_usd:
    IF params.auto_shrink:
      AWAIT onchain.approve(pUSD, spender, pendingOrder.size_usd)
      allowance = pendingOrder.size_usd  // now safe
    ELSE:
      EMIT RiskVote(DENY, ALLOWANCE_EXCEEDS_CEILING); RETURN
  // Idle revoke check
  last_use = FETCH(onchain.last_transfer_event(spender))
  idle_h = (NOW() - last_use) / 3600
  IF idle_h > params.idle_revoke_h:
    AWAIT onchain.approve(pUSD, spender, 0)  // revoke
    EMIT RiskVote(DENY, ALLOWANCE_EXCEEDS_CEILING); RETURN
  EMIT RiskVote(APPROVE)
  LOG(governance.audit, {allowance, spender, wallet})

SDK calls used

  • onchain.erc20.allowance(pUSD_ADDRESS, wallet, CTFExchangeV2_ADDRESS)
  • onchain.approve(pUSD_ADDRESS, spender, amount)

Complexity: O(1) per order — single RPC read

11. Wire Examples

Input — what arrives on the wire

Order with safe allowanceinternal

{
  "intent_id": "int_2b3c4d5e6f7a8b9c",
  "contract_address": "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E",
  "size_usd": 200,
  "timestamp_ms": 1746768672000
}

Output — what the bot emits

RiskVote — APPROVE

{
  "vote_id": "sec.allowance_monitor.20260509T130000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "allowance_usd": 400,
    "ceiling_usd": 500,
    "shrunk": false
  },
  "checked_at": "2026-05-09T13:00:00Z"
}

RiskVote — DENY (exceeds ceiling, auto_shrink off)

{
  "vote_id": "sec.allowance_monitor.20260509T130100Z",
  "decision": "DENY",
  "reason_code": "ALLOWANCE_EXCEEDS_CEILING",
  "checked_at": "2026-05-09T13:01:00Z"
}

12. Decision Logic

APPROVE

Allowance is within max_allowance_usd or auto_shrink reduced it to the safe ceiling.

RESHAPE_REQUIRED

Not applicable — monitor either approves or rejects.

REJECT

Allowance exceeds ceiling and auto_shrink is disabled (ALLOWANCE_EXCEEDS_CEILING).

WARNING_ONLY

Warn when allowance approaches 90% of max_allowance_usd.

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "vote_id": "sec.allowance_monitor.20260509T130000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "token": "pUSD",
    "spender": "CTFExchangeV2",
    "allowance_usd": 400,
    "ceiling_usd": 500,
    "shrunk": false
  },
  "checked_at": "2026-05-09T13:00:00Z"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Immediately return DENY.Trading is currently paused.
ALLOWANCE_EXCEEDS_CEILINGHARD_REJECTERC-20 allowance for a spender exceeds max_allowance_usd and auto_shrink is disabled.Return DENY and emit alert.Your pUSD approval is above the safety limit. Please reduce it.
STALE_DATAHARD_REJECTRPC call to read allowance failed or returned stale data.Return DENY; retry on next order.Could not verify your approval status. Please try again.
ALLOWANCE_NEAR_CEILINGWARNAllowance is within 10% of max_allowance_usd.Log warning; continue.Your approval is near the safety ceiling.
ALLOWANCE_SHRUNKINFOauto_shrink triggered and approval reduced to exact order size.Log info; proceed.Your approval was adjusted to the minimum needed.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_sec_allowancemonitor_decisions_totalcountercountdecisionTotal RiskVote decisions.
polytraders_sec_allowancemonitor_shrinks_totalcountercountspenderNumber of auto-shrink transactions submitted.
polytraders_sec_allowancemonitor_allowance_usdgaugeusdspenderCurrent pUSD allowance per spender after last check.
polytraders_sec_allowancemonitor_eval_latency_mshistogrammsLatency of the allowance check including RPC call.

Alerts

AlertConditionSeverityRunbook
AllowanceExceedsCeilingrate(polytraders_sec_allowancemonitor_decisions_total{decision='DENY'}[5m]) > 0P1#runbook-allowancemonitor-ceiling
AllowanceMonitorShrinkFailedrate(polytraders_sec_allowancemonitor_shrinks_total[5m]) == 0 AND allowance > ceilingP1#runbook-allowancemonitor-shrink

16. Developer Reporting

{
  "bot_id": "sec.allowance_monitor",
  "decision": "APPROVE",
  "inputs_used": [
    "onchain.allowance",
    "config.max_allowance_usd"
  ],
  "checked_at": "2026-05-09T13:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Allowance shrunk automaticallyYour pUSD approval was reduced to the minimum needed for this order to limit exposure.
Order blocked — allowance too highYour pUSD approval is above the safety limit and auto-shrink is disabled. Please reduce it manually.
Allowance about to be revokedYour approval for an inactive contract is being revoked after idle period.

18. Failure-Mode Block

main_failure_modeAllowance check bypassed or RPC call fails, leaving unbounded approval in place.
false_positive_riskShrinking allowance during a burst of orders could cause the next order to fail if the shrink tx is not confirmed in time.
false_negative_riskStale RPC response showing a lower allowance than actual, causing an oversize order to proceed.
safe_fallbackIf RPC is unreachable, fail-closed: reject orders until allowance can be verified.
required_dependenciesPolygon RPC, Admin UI config, KillSwitch

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
RPC_UNREACHABLEBlock Polygon RPC endpointAutomatic when RPC recovers; RPCFailoverManager switches provider.
UNBOUNDED_ALLOWANCESet wallet allowance to uint256.maxAutomatic after shrink tx confirmed.
SHRINK_TX_FAILSSimulate on-chain tx revert during shrinkManual allowance management required.

20. State & Persistence

Cold-start recovery

Re-read from Polygon RPC on restart; fail-closed if RPC unavailable.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight100
Idempotency keyintent_id
Per-call timeout (ms)500
Backpressure strategyqueue with max depth 50
Locking / mutual exclusionmutex on allowance cache write during shrink tx

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchKillSwitch gate before any on-chain read.DENY(KILL_SWITCH_ACTIVE) short-circuits.
sec.rpc_failover_managerRPC endpoint health for allowance reads.Healthy RPC required; failover to secondary on error.

Emits to (downstream consumers)

BotWhyContract
gov.builder_attributionAudit log every decision and shrink event.GovernanceLog entry on each event.

Sibling bots (same OrderIntent)

External services

ServiceEndpointSLA assumedOn failure
Polygon RPC (read)Polygon RPCbest-effortFail-closed on unreachable RPC.

23. Security Surfaces

Abuse vectors considered

  • RPC returning stale allowance to prevent shrink from firing
  • Rapid allowance inflation between check and order submission

Mitigations

  • Allowance re-read immediately before every signing action
  • auto_shrink submits tx and waits for confirmation before approving

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
NotesMonitors pUSD allowances for CTFExchangeV2 and NegRiskAdapter on Polygon; auto-shrinks to exact-needed before signing.

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 allowance within ceilingallowance=400, max_allowance_usd=500, auto_shrink=trueAPPROVE without shrink
Auto-shrink and approve when allowance exceeds ceilingallowance=2000, max_allowance_usd=500, auto_shrink=trueshrink tx submitted then APPROVE
Reject when allowance exceeds ceiling and auto_shrink=falseallowance=2000, auto_shrink=falseDENY(ALLOWANCE_EXCEEDS_CEILING)

Integration Tests

TestExpected result
RPC unreachable triggers fail-closedDENY until RPC recovers
Idle revoke fires after idle_revoke_hrevoke tx submitted for idle spender

Property Tests

PropertyRequired behaviour
auto_shrink=true never leaves allowance above ceiling post-checkAlways true
Empty/unreachable RPC always produces DENYAlways true

27. Operational Runbook

AllowanceExceedsCeiling alerts require immediate review — check auto_shrink status and RPC health.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
AllowanceExceedsCeiling
AllowanceMonitorShrinkFailed

Manual overrides

Healthcheck

GET /internal/health/allowancemonitor → green if All allowances at or below max_allowance_usd; last check within 60s.; red if Any allowance above ceiling for more than 120s.

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 shrink and ceiling logicCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
auto_shrink fires correctly on simulated unbounded allowanceFailure injection test on stagingPass

Promote to General live

GateHow measuredThreshold
Zero AllowanceExceedsCeiling 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