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.1 WalletPermissionGuard

5.1 WalletPermissionGuard

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

Enforce that each strategy can only call the wallet methods the user has explicitly granted, scoped per session.

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 signing or submission
Runs afterStrategy OrderIntent and Risk guardrails
Applies toEvery pending order before signature
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

Operational profile

Modes supportedquarantine

2. Purpose

Enforce that each strategy can only call the wallet methods the user has explicitly granted, scoped per session.

3. Why This Bot Matters

  • Strategy calls an unauthorized wallet method

    Unexpected asset movement or signing actions outside the user-granted scope, undermining non-custodial guarantees.

  • Method whitelist not enforced

    A compromised strategy could sign arbitrary orders, draining pUSD balances.

  • Permission scope not session-bound

    Stale grants from a previous session silently persist, violating least-privilege.

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
Pending order contract address and methodclob_authYesCheck that the target contract method is on the per-strategy whitelist.
CTFExchangeV2 method signaturesonchainYesValidate that the called method exists and is recognised in the V2 ABI.

5. Required Internal Inputs

InputSourceRequired?Use
Per-strategy method whitelist and contract allowlistAdmin UIYesAuthoritative grant set for each strategy session.
Active session expiry timestampSessionKeyManagerYesReject calls from expired sessions.
KillSwitch active flagKillSwitchYesHard reject all calls when kill switch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
method_whitelist[]Non-empty but no CTFExchangeV2.matchOrders in listEmpty list — fail-closed, no methods permittedList of wallet method identifiers the strategy is allowed to invoke.
max_per_call_size_usd1000Order size > 0.8 * max_per_call_size_usdOrder size > max_per_call_size_usdMaximum pUSD value of a single signing call.

7. Detailed Parameter Instructions

method_whitelist

What it means

List of wallet method identifiers the strategy is allowed to invoke.

Default

{ "method_whitelist": "[]" }

Why this default matters

Default empty list is fail-closed; no signing is permitted until explicitly granted.

Threshold logic

ConditionAction
method in method_whitelistAPPROVE — proceed to contract check
method NOT in method_whitelistREJECT — WALLET_PERMISSION_DENIED

Developer check

if (!p.method_whitelist.includes(order.method)) return reject('WALLET_PERMISSION_DENIED');

User-facing English

This action is not in your approved methods list.

max_per_call_size_usd

What it means

Maximum pUSD value of a single signing call.

Default

{ "max_per_call_size_usd": 1000 }

Why this default matters

A conservative default limits blast radius of a compromised session.

Threshold logic

ConditionAction
size_usd <= max_per_call_size_usdAPPROVE
size_usd > max_per_call_size_usdREJECT — WALLET_PERMISSION_DENIED (size exceeded)

Developer check

if (order.size_usd > p.max_per_call_size_usd) return reject('WALLET_PERMISSION_DENIED');

User-facing English

This order exceeds the per-call size limit set for your session.

8. Default Configuration

{
  "bot_id": "sec.wallet_permission_guard",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "method_whitelist": [],
    "contract_allowlist": [],
    "max_per_call_size_usd": 1000,
    "require_reapproval_h": 24
  }
}

9. Implementation Flow

  1. Receive pending order before signing.
  2. Check KillSwitch; if active, REJECT(KILL_SWITCH_ACTIVE).
  3. Check session expiry; if expired, REJECT(SESSION_KEY_EXPIRED).
  4. Verify order.method is in method_whitelist; if not, REJECT(WALLET_PERMISSION_DENIED).
  5. Verify order.contract_address is in contract_allowlist; if not, REJECT(WALLET_PERMISSION_DENIED).
  6. Verify order.size_usd <= max_per_call_size_usd; if exceeded, REJECT(WALLET_PERMISSION_DENIED).
  7. Emit RiskVote(APPROVE) with inputs_used and checked_at.
  8. Log decision 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.

// WalletPermissionGuard
FUNCTION checkWalletPermission(pendingOrder, session):
  // 0. KillSwitch
  IF FETCH(internal.killswitch).active:
    EMIT RiskVote(DENY, KILL_SWITCH_ACTIVE); RETURN
  // 1. Session expiry
  IF session.expires_at < NOW():
    EMIT RiskVote(DENY, SESSION_KEY_EXPIRED); RETURN
  // 2. Method whitelist
  IF pendingOrder.method NOT IN session.method_whitelist:
    EMIT RiskVote(DENY, WALLET_PERMISSION_DENIED); RETURN
  // 3. Contract allowlist
  IF pendingOrder.contract_address NOT IN session.contract_allowlist:
    EMIT RiskVote(DENY, WALLET_PERMISSION_DENIED); RETURN
  // 4. Size cap
  IF pendingOrder.size_usd > params.max_per_call_size_usd:
    EMIT RiskVote(DENY, WALLET_PERMISSION_DENIED); RETURN
  // 5. Approve
  EMIT RiskVote(APPROVE)
  LOG(governance.audit, {decision, intent_id, method, session_id})

SDK calls used

  • clob_auth.get_session(user_id)
  • internal.killswitch.status()

Complexity: O(n) where n = whitelist size (small constant)

11. Wire Examples

Input — what arrives on the wire

Permitted matchOrders callinternal

{
  "intent_id": "int_1a2b3c4d5e6f7a8b",
  "method": "matchOrders",
  "contract_address": "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E",
  "size_usd": 400,
  "timestamp_ms": 1746768672000
}

Output — what the bot emits

RiskVote — APPROVE

{
  "vote_id": "sec.wallet_permission_guard.20260509T120000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "checked_at": "2026-05-09T12:00:00Z"
}

RiskVote — DENY (method not in whitelist)

{
  "vote_id": "sec.wallet_permission_guard.20260509T120100Z",
  "decision": "DENY",
  "reason_code": "WALLET_PERMISSION_DENIED",
  "checked_at": "2026-05-09T12:01:00Z"
}

12. Decision Logic

APPROVE

Method is whitelisted, contract is allowlisted, size within limit, and session is active.

RESHAPE_REQUIRED

Not applicable — this guard does not reshape; it only approves or rejects.

REJECT

Method not in whitelist, contract not in allowlist, size exceeded, session expired, or KillSwitch active.

WARNING_ONLY

Warn when call size exceeds 80% of max_per_call_size_usd.

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "vote_id": "sec.wallet_permission_guard.20260509T120000Z",
  "decision": "DENY",
  "reason_code": "WALLET_PERMISSION_DENIED",
  "evidence": {
    "method": "transfer",
    "in_whitelist": false
  },
  "checked_at": "2026-05-09T12:00:00Z"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Immediately return DENY.Trading is currently paused.
SESSION_KEY_EXPIREDHARD_REJECTThe active session key has expired.Return DENY; prompt user to re-authorise.Your session has expired. Please re-authorise.
WALLET_PERMISSION_DENIEDHARD_REJECTMethod or contract not in session whitelist, or size cap exceeded.Return DENY and emit security alert.This action is not permitted in your current session.
PERMISSION_SCOPE_WARNWARNOrder size is between 80% and 100% of max_per_call_size_usd.Log warning; continue to next check.This order is close to your per-call size limit.
SESSION_ABOUT_TO_EXPIREINFOSession expires within require_reapproval_h hours.Emit INFO; notify user to prepare re-authorisation.Your session will expire soon. Consider re-authorising.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_sec_walletpermissionguard_decisions_totalcountercountdecision, reason_codeTotal RiskVote decisions emitted.
polytraders_sec_walletpermissionguard_denied_totalcountercountreason_codeDenied calls by reason.
polytraders_sec_walletpermissionguard_session_expiry_sgaugesecondsstrategy_idSeconds until current session expires per strategy.
polytraders_sec_walletpermissionguard_eval_latency_mshistogrammsWall-clock latency of permission check.

Alerts

AlertConditionSeverityRunbook
WalletPermissionDeniedrate(polytraders_sec_walletpermissionguard_denied_total[5m]) > 0P1#runbook-walletpermission-denied
WalletPermissionSessionExpiredpolytraders_sec_walletpermissionguard_session_expiry_s < 300P2#runbook-walletpermission-session

16. Developer Reporting

{
  "bot_id": "sec.wallet_permission_guard",
  "decision": "DENY",
  "reason_code": "WALLET_PERMISSION_DENIED",
  "inputs_used": [
    "session.whitelist",
    "order.method"
  ],
  "checked_at": "2026-05-09T12:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Order blocked — method not permittedThis action is not in your approved methods list for the current session.
Order blocked — session expiredYour session has expired. Please re-authorise to continue trading.
Order blocked — size exceededThis order is larger than the per-call limit set for your session.

18. Failure-Mode Block

main_failure_modeA strategy calling an out-of-scope wallet method because the whitelist check was bypassed or misconfigured.
false_positive_riskRejecting a legitimate order because the method whitelist was not updated after a strategy configuration change.
false_negative_riskApproving an out-of-scope call if the whitelist is overly broad (e.g., wildcard entries).
safe_fallbackIf the whitelist cannot be loaded, fail-closed: reject all calls with WALLET_PERMISSION_DENIED.
required_dependenciesSessionKeyManager for session expiry, Admin UI for whitelist config, KillSwitch

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
EMPTY_WHITELISTStart bot with method_whitelist=[]Admin populates whitelist via signed action.
SESSION_EXPIREDSet session.expires_at to past timestampUser re-authorises session.
KILL_SWITCH_ONSet killswitch.active=trueManual KillSwitch reset.

20. State & Persistence

Cold-start recovery

Reload whitelist from Admin UI on restart; fail-closed if unavailable.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight500
Idempotency keyintent_id
Per-call timeout (ms)10
Backpressure strategydrop newest
Locking / mutual exclusionread-only whitelist; no write locks needed

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchKillSwitch gate runs first.DENY(KILL_SWITCH_ACTIVE) short-circuits all checks.
sec.session_key_managerProvides session expiry and scope.Session expiry triggers DENY(SESSION_KEY_EXPIRED).

Emits to (downstream consumers)

BotWhyContract
gov.builder_attributionAudit log every decision.GovernanceLog entry on each APPROVE or DENY.

Sibling bots (same OrderIntent)

External services

ServiceEndpointSLA assumedOn failure
CLOB API (auth)https://clob.polymarket.com99.9%Fail-closed on unreachable session store.

23. Security Surfaces

Abuse vectors considered

  • Strategy attempting to call an unauthorized method to bypass fee caps
  • Session token replay after expiry

Mitigations

  • Whitelist checked on every call; no caching of positive decisions
  • Session expiry enforced by monotonic clock comparison

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldyes
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesEnforces per-session method whitelists scoped to CTFExchangeV2 on Polygon; pUSD size limits apply per call.

API surfaces declared

clob_authonchaininternal

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 method in whitelist and size within limitmethod='matchOrders', whitelist=['matchOrders'], size_usd=500APPROVE
Reject when method not in whitelistmethod='transfer', whitelist=['matchOrders']DENY(WALLET_PERMISSION_DENIED)
Reject when size exceeds max_per_call_size_usdsize_usd=2000, max_per_call_size_usd=1000DENY(WALLET_PERMISSION_DENIED)

Integration Tests

TestExpected result
Expired session triggers DENY before whitelist checkDENY(SESSION_KEY_EXPIRED) without whitelist lookup
KillSwitch active short-circuits all checksDENY(KILL_SWITCH_ACTIVE)

Property Tests

PropertyRequired behaviour
Empty whitelist always produces DENYAlways true — fail-closed
Every DENY emits a security alertAlways true

27. Operational Runbook

Every WALLET_PERMISSION_DENIED is a security event. Investigate whether the strategy is misconfigured or attempting scope escalation.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
WalletPermissionDenied
WalletPermissionSessionExpired

Manual overrides

Healthcheck

GET /internal/health/walletpermissionguard → green if Session store reachable; whitelist non-empty for active strategies.; red if Session store unreachable or all whitelists empty.

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 all whitelist and session expiry pathsCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Zero DENY alerts from legitimate strategy calls over 24h shadowGrafana WalletPermissionDenied alert history0 false positives

Promote to General live

GateHow measuredThreshold
Session expiry injection test fires DENY correctlyFailure injection testPass

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