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.9 WalletFundingGuard

5.9 WalletFundingGuard

Security Guardrail Reject PLANNED Spec ready capital · Direct P5 · Execution rails pending stub

Rejects any OrderIntent whose required pUSD collateral cannot be covered by the funded balance of the assigned wallet, including a configurable buffer. Prevents the system from submitting orders that would fail at the exchange for insufficient funds, which burns latency and creates noisy reject metrics.

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
AuthorityReject
StatusPLANNED
ReadinessSpec ready
Runs beforeexec.smart_router
Runs afterexec.order_lifecycle_manager
Applies toPer OrderIntent
Default modeshadow
User-visibleYes
Developer ownerSecurity pod

Operational profile

OwnershipSecurity pod · on-call sec-oncall · #polytraders-sec · escalates to Head of Security · P1
Latency budgetp50: 8ms · p99: 60ms
Modes supportedoffshadowadvisoryenforcedquarantine
Data freshnessmax_market_data_age_ms=5000 · max_orderbook_age_ms=5000 · max_external_feed_age_ms=5000 · on stale → REJECT — never assume funding.
Human overrideyes · by Security on-call · logs SEC_FUNDING_OVERRIDE · time-bound: Per intent · scope: Single intent_id · second approval required

2. Purpose

Rejects any OrderIntent whose required pUSD collateral cannot be covered by the funded balance of the assigned wallet, including a configurable buffer. Prevents the system from submitting orders that would fail at the exchange for insufficient funds, which burns latency and creates noisy reject metrics.

3. Why This Bot Matters

  • Insufficient-funds rejects

    Each one wastes a CLOB round-trip and pollutes monitoring with noise that hides real issues.

  • Funding-race conditions

    Two strategies competing for the same wallet's collateral can both pass an unsynchronised check; the second order rejects on-chain.

  • Operational embarrassment

    An incident where the system tried to trade on a wallet that was never funded looks worse than catching it cleanly.

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
On-chain pUSD balance per walletERC-20 contractYesAuthoritative balance source.

5. Required Internal Inputs

InputSourceRequired?Use
Wallet → strategy assignmentConfigYesWhich wallet funds which OrderIntent.
In-flight collateral reservationexec.order_lifecycle_managerYesCollateral already committed to resting orders.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
funding_buffer_usd25255Buffer that must remain free after this order is accounted for.
balance_cache_ttl_ms5000500015000How long an on-chain balance read can be cached before it must be re-fetched.

7. Detailed Parameter Instructions

funding_buffer_usd

What it means

Buffer that must remain free after this order is accounted for.

Default

{ "funding_buffer_usd": 25 }

Why this default matters

$25 buffer covers worst-case fee + slippage on a normal Polymarket binary order.

Threshold logic

ConditionAction
≥ $25PASS
< $25REJECT

Developer check

if (free - intent.size_usd < p.funding_buffer_usd) reject('SEC_FUNDING');

User-facing English

We did not place this order because the wallet does not have enough money to cover it safely.

balance_cache_ttl_ms

What it means

How long an on-chain balance read can be cached before it must be re-fetched.

Default

{ "balance_cache_ttl_ms": 5000 }

Why this default matters

5 seconds is a reasonable trade-off for an active trading session.

Threshold logic

ConditionAction
5000Default

Developer check

if (now - balanceCachedAt > p.balance_cache_ttl_ms) refresh();

User-facing English

(Internal.)

8. Default Configuration

{
  "funding_buffer_usd": 25,
  "balance_cache_ttl_ms": 5000
}

9. Implementation Flow

— not yet authored —

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.

with wallet_lock(wallet):
  bal = balance(wallet, ttl=p.balance_cache_ttl_ms)
  free = bal - reserved(wallet)
  if intent.size_usd > free - p.funding_buffer_usd: return reject('SEC_FUNDING')
  reserve(wallet, intent.size_usd, intent.intent_id)
  return pass_()

11. Wire Examples

Input — what arrives on the wire

{
  "intent_id": "intent_005",
  "size_usd": 90,
  "wallet_address": "0xabc"
}

Output — what the bot emits

{
  "vote": "REJECT",
  "reason_code": "SEC_FUNDING",
  "explain": "Wallet 0xabc has $80 free; order for $90 would breach $25 buffer."
}

12. Decision Logic

APPROVE

Compute free = balance - reserved. Reserve collateral on PASS.

RESHAPE_REQUIRED

This bot does not reshape orders.

REJECT

Reject if order would breach the funding buffer.

WARNING_ONLY

No warn-only path defined.

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "vote": "REJECT",
  "reason_code": "SEC_FUNDING",
  "explain": "Wallet 0xabc has $80 free; order for $90 would breach $25 buffer."
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
SEC_FUNDINGP1Sec FundingSee decision output and developer log for context.We did not place this order because the wallet does not have enough money to cover it safely.
SEC_FUNDING_OKP1Sec Funding OkSee decision output and developer log for context.We did not place this order because the wallet does not have enough money to cover it safely.
SEC_FUNDING_RACE_LOSTP1Sec Funding Race LostSee decision output and developer log for context.We did not place this order because the wallet does not have enough money to cover it safely.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
funding_rejects_totalcountereventmarket_id, reason_codeFunding rejects total.
funding_pass_totalcountereventbot_idFunding pass total.
balance_refresh_totalcountereventbot_idBalance refresh total.
reservation_race_totalcountereventbot_idReservation race total.

Dashboards

  • 5.9 overview dashboard

16. Developer Reporting

"Per decision: intent_id, wallet_address, balance_usd, reserved_usd, intent.size_usd, vote, reason_code."

17. Plain-English Reporting

SituationUser-facing explanation
When this bot actsWe did not place this order because the wallet does not have enough money to cover it safely.

18. Failure-Mode Block

main_failure_modeStale balance cache misses a withdrawal that just settled.
false_positive_riskJust-funded wallet read before the next cache refresh; mitigation: on-demand cache invalidation when an inbound transfer is observed.
false_negative_riskTwo intents racing on the same wallet both PASS because reservation lock is not held; mitigation: reservation is taken under a per-wallet mutex.
safe_fallbackIf the balance cannot be fetched at all, REJECT — never assume funding.
required_dependencies

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
Drain a wallet during shadow and assert all subsequent orders REJECTDrain a wallet during shadow and assert all subsequent orders REJECT.Bot detects within its latency budget and emits the corresponding reason code.Remove the injected fault; bot returns to healthy state within one debounce window.
Disconnect the balance RPC and assert REJECT-on-stale firesDisconnect the balance RPC and assert REJECT-on-stale fires.Bot detects within its latency budget and emits the corresponding reason code.Remove the injected fault; bot returns to healthy state within one debounce window.

20. State & Persistence

Per-wallet reserved collateral map (durable). Balance cache (in-memory, TTL).

State stores

NameKindKeyValue shapeTTLDurability
wallet_funding_guard_statein-memory + fast KV mirrorbot_idPer-wallet reserved collateral map (durable). Balance cache (in-memory, TTL).24hcrash-safe via KV mirror

Cold-start recovery

Cold-start hydrates from fast KV; missing keys default to safe fallback.

On restart

All in-flight decisions are re-evaluated; no bot decision is trusted across restart without re-emit.

21. Concurrency & Idempotency

AspectSpecification
Execution modelPer-wallet mutex. Read-modify-write on (balance, reserved).
Max in-flight32
Idempotency keyorder_intent_id
Replay-safeTrue
DeduplicationBy idempotency_key within a 60s window.
Ordering guaranteesPer-market_id FIFO; cross-market unordered.
Per-call timeout (ms)250
Backpressure strategyBounded queue; oldest-dropped with metric increment when full.
Locking / mutual exclusionPer-market_id mutex; no global locks.

22. Dependencies

Depends on (must run first)

Emits to (downstream consumers)

BotWhyContract
exec.smart_router

Requires (graph.requires)

exec.order_lifecycle_manager

Required before (graph.required_before)

exec.smart_router

ConsumesOrderIntent WalletBalance ReservationMap
EmitsRiskVote
Blocks ordersyes

23. Security Surfaces

Read-only RPC for ERC-20 balance.

Signing surface

None — bot does not sign or submit.

Abuse vectors considered

  • Reservation map is internal only and writes are mutex-guarded.

Mitigations

  • Rate-limit per source
  • Audit-log every override
  • Require role-based authz on admin paths

24. Polymarket V2 Compatibility

AspectValue
CLOB versionV2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldyes
Aware of negative-risk marketsyes
Multi-chain readyyes
SDK usedPolymarket CLOB V2 SDK
Settlement contractCTFExchangeV2
NotesReads pUSD ERC-20 balance directly; no V1/V2 difference.

25. Versioning & Migration

FieldValue
current0.1.0
contract_version1.0.0
last_breaking_changenone
deprecation_window_days30

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Free = $25 + intent.size → PASS.Synthetic fixture per template.Behaviour matches the rule described in the test name.
Free = intent.size → REJECT.Synthetic fixture per template.Behaviour matches the rule described in the test name.
Race two intents on the same wallet → exactly one PASS, one REJECT.Synthetic fixture per template.Behaviour matches the rule described in the test name.

Integration Tests

TestExpected result
End-to-end: drain a wallet to $24 and assert all OrderIntents are rejected.End-to-end behaviour matches the spec without manual intervention.

Property Tests

PropertyRequired behaviour
Sum of accepted intent sizes never exceeds (balance - funding_buffer_usd).Always true across all generated inputs.

27. Operational Runbook

If reject rate spikes from this bot, check whether a wallet was drained or whether the balance cache is failing to refresh.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
5.9_anomalyOpen the bot's reporting page and confirm the alert is real (not a metric hiccup).Inspect developer log entries for the affected market_id over the last 30 minutes.Force-clear via Admin UI if the rule is clearly stale; otherwise leave engaged and notify owner.Security pod

Manual overrides

  • polytraders bot pause 5.9 — Disables the bot's enforcement layer; downstream consumers fall back to safe defaults.

Healthcheck

GET /healthz/wallet_funding_guard → 200 if last successful evaluation < 60s ago.

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
Stubrace tests pass.Documented threshold met for the full window.

Promote to Limited live

GateHow measuredThreshold
Shadow14 days; reservations tracked but not enforced.Documented threshold met for the full window.
Advisory7 days.Documented threshold met for the full window.

Promote to General live

GateHow measuredThreshold
EnforcedSecurity Lead sign-off.Documented threshold met for the full window.

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