1. Bot Identity
| Layer | Security Security |
|---|
| Bot class | Guardrail |
|---|
| Authority | RejectPause |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Any order that requires an on-chain allowance |
|---|
| Runs after | Strategy OrderIntent |
|---|
| Applies to | All ERC-20 allowances on Polygon for the trading wallet |
|---|
| Default mode | shadow_only |
|---|
| User-visible | Advanced details only |
|---|
| Developer owner | Polytraders core |
|---|
Operational profile
| Modes supported | quarantine |
|---|
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.
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|
| max_allowance_usd | 500 | Current allowance > 0.9 * max_allowance_usd | Current allowance > max_allowance_usd (unbounded detected) | Maximum pUSD allowance tolerated for any (token, spender) pair. |
| auto_shrink | True | auto_shrink disabled and allowance > max_allowance_usd | — | When 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
| Condition | Action |
|---|
| allowance <= max_allowance_usd | APPROVE |
| allowance > max_allowance_usd AND auto_shrink=true | Emit shrink tx, then APPROVE |
| allowance > max_allowance_usd AND auto_shrink=false | REJECT — 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
| Condition | Action |
|---|
| auto_shrink=true AND allowance > max_allowance_usd | Submit approve(spender, exact_needed) on-chain before signing |
| auto_shrink=false | Alert 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
- Receive order intent before submission.
- Check KillSwitch; if active, REJECT(KILL_SWITCH_ACTIVE).
- FETCH current pUSD allowance for (CTFExchangeV2, wallet) from Polygon RPC.
- If allowance > max_allowance_usd and auto_shrink=true: submit shrink tx, wait for confirmation.
- If allowance > max_allowance_usd and auto_shrink=false: REJECT(ALLOWANCE_EXCEEDS_CEILING).
- Check idle duration; if > idle_revoke_h: trigger revoke allowance for idle spenders.
- Emit RiskVote(APPROVE) with current allowance snapshot.
- 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 allowance — internal
{
"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
| Code | Severity | Meaning | Action | User-facing message |
|---|
KILL_SWITCH_ACTIVE | HARD_REJECT | Global kill switch is active. | Immediately return DENY. | Trading is currently paused. |
ALLOWANCE_EXCEEDS_CEILING | HARD_REJECT | ERC-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_DATA | HARD_REJECT | RPC 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_CEILING | WARN | Allowance is within 10% of max_allowance_usd. | Log warning; continue. | Your approval is near the safety ceiling. |
ALLOWANCE_SHRUNK | INFO | auto_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
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_sec_allowancemonitor_decisions_total | counter | count | decision | Total RiskVote decisions. |
polytraders_sec_allowancemonitor_shrinks_total | counter | count | spender | Number of auto-shrink transactions submitted. |
polytraders_sec_allowancemonitor_allowance_usd | gauge | usd | spender | Current pUSD allowance per spender after last check. |
polytraders_sec_allowancemonitor_eval_latency_ms | histogram | ms | | Latency of the allowance check including RPC call. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
AllowanceExceedsCeiling | rate(polytraders_sec_allowancemonitor_decisions_total{decision='DENY'}[5m]) > 0 | P1 | #runbook-allowancemonitor-ceiling |
AllowanceMonitorShrinkFailed | rate(polytraders_sec_allowancemonitor_shrinks_total[5m]) == 0 AND allowance > ceiling | P1 | #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
| Situation | User-facing explanation |
|---|
| Allowance shrunk automatically | Your pUSD approval was reduced to the minimum needed for this order to limit exposure. |
| Order blocked — allowance too high | Your pUSD approval is above the safety limit and auto-shrink is disabled. Please reduce it manually. |
| Allowance about to be revoked | Your approval for an inactive contract is being revoked after idle period. |
18. Failure-Mode Block
| main_failure_mode | Allowance check bypassed or RPC call fails, leaving unbounded approval in place. |
|---|
| false_positive_risk | Shrinking allowance during a burst of orders could cause the next order to fail if the shrink tx is not confirmed in time. |
|---|
| false_negative_risk | Stale RPC response showing a lower allowance than actual, causing an oversize order to proceed. |
|---|
| safe_fallback | If RPC is unreachable, fail-closed: reject orders until allowance can be verified. |
|---|
| required_dependencies | Polygon RPC, Admin UI config, KillSwitch |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
RPC_UNREACHABLE | Block Polygon RPC endpoint | | Automatic when RPC recovers; RPCFailoverManager switches provider. |
UNBOUNDED_ALLOWANCE | Set wallet allowance to uint256.max | | Automatic after shrink tx confirmed. |
SHRINK_TX_FAILS | Simulate on-chain tx revert during shrink | | Manual allowance management required. |
20. State & Persistence
Cold-start recovery
Re-read from Polygon RPC on restart; fail-closed if RPC unavailable.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | single-threaded event loop |
| Max in-flight | 100 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 500 |
| Backpressure strategy | queue with max depth 50 |
| Locking / mutual exclusion | mutex on allowance cache write during shrink tx |
22. Dependencies
Depends on (must run first)
Emits to (downstream consumers)
Sibling bots (same OrderIntent)
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| Polygon RPC (read) | Polygon RPC | best-effort | Fail-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
| 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 | Monitors 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
| 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 allowance within ceiling | allowance=400, max_allowance_usd=500, auto_shrink=true | APPROVE without shrink |
| Auto-shrink and approve when allowance exceeds ceiling | allowance=2000, max_allowance_usd=500, auto_shrink=true | shrink tx submitted then APPROVE |
| Reject when allowance exceeds ceiling and auto_shrink=false | allowance=2000, auto_shrink=false | DENY(ALLOWANCE_EXCEEDS_CEILING) |
Integration Tests
| Test | Expected result |
|---|
| RPC unreachable triggers fail-closed | DENY until RPC recovers |
| Idle revoke fires after idle_revoke_h | revoke tx submitted for idle spender |
Property Tests
| Property | Required behaviour |
|---|
| auto_shrink=true never leaves allowance above ceiling post-check | Always true |
| Empty/unreachable RPC always produces DENY | Always true |
27. Operational Runbook
AllowanceExceedsCeiling alerts require immediate review — check auto_shrink status and RPC health.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate 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.
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 |