1. Bot Identity
| Layer | Security Security |
|---|
| Bot class | Guardrail |
|---|
| Authority | RejectPause |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Wallet signing modal is shown to user |
|---|
| Runs after | Strategy OrderIntent and contract/permission checks |
|---|
| Applies to | Every EIP-712 signing request before user confirmation |
|---|
| Default mode | shadow_only |
|---|
| User-visible | Advanced details only |
|---|
| Developer owner | Polytraders core |
|---|
Operational profile
| Modes supported | quarantine |
|---|
2. Purpose
Render a plain-English summary of every EIP-712 signature before the wallet shows the modal.
3. Why This Bot Matters
User signs a phishing or misrouted order without understanding it
Funds transferred to an unintended address or contract under user signature.
Domain separator not validated before display
A forged domain could cause a user to unknowingly sign for a different chain or contract.
Strategy deviates from declared envelope
User signs an order outside the parameters they approved at strategy setup.
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_preview_for | ['all'] | Order fields deviate from strategy envelope | Domain separator mismatch or envelope breach > 20% | List of order types that require a preview before signing. |
| block_on_envelope_mismatch | True | Order deviates 10–20% from declared envelope | Order deviates > 20% from declared envelope | Block signing if the order exceeds the strategy's declared parameters. |
7. Detailed Parameter Instructions
require_preview_for
What it means
List of order types that require a preview before signing.
Default
{ "require_preview_for": ["all"] }
Why this default matters
Default ['all'] ensures every signing request is previewed without exception.
Threshold logic
| Condition | Action |
|---|
| order_type in require_preview_for | Generate preview; await user acknowledgement |
| domain separator mismatch | REJECT — CONTRACT_GUARD_DOMAIN_MISMATCH |
Developer check
if (p.require_preview_for.includes('all') || p.require_preview_for.includes(order.type)) generatePreview(order);
User-facing English
Please review this order before signing.
block_on_envelope_mismatch
What it means
Block signing if the order exceeds the strategy's declared parameters.
Default
{ "block_on_envelope_mismatch": true }
Why this default matters
Blocking on mismatch prevents a compromised strategy from signing orders the user did not intend.
Threshold logic
| Condition | Action |
|---|
| deviation <= 10% | APPROVE — no warning |
| deviation 10–20% | WARN in preview |
| deviation > 20% AND block_on_envelope_mismatch=true | REJECT — SIGNATURE_ENVELOPE_BREACH |
Developer check
if (p.block_on_envelope_mismatch && deviation > 0.20) return reject('SIGNATURE_ENVELOPE_BREACH');
User-facing English
This order is outside your strategy's declared parameters.
8. Default Configuration
{
"bot_id": "sec.signature_previewer",
"version": "0.1.0",
"mode": "hard_guard",
"defaults": {
"require_preview_for": [
"all"
],
"min_detail_level": "standard",
"block_on_envelope_mismatch": true,
"retain_log_days": 30
}
}
9. Implementation Flow
- Receive EIP-712 typed data for pending signing request.
- Check KillSwitch; if active, REJECT(KILL_SWITCH_ACTIVE).
- Verify domain separator version == '2' and verifyingContract in V2 allow-list.
- Resolve token IDs to market names via Gamma API.
- Compute diff between order fields and strategy declared envelope.
- If deviation > 20% and block_on_envelope_mismatch: REJECT(SIGNATURE_ENVELOPE_BREACH).
- Render plain-English preview: market name, side, size in pUSD, price, expiry, contract.
- Emit preview to wallet UI; await user acknowledgement.
- Emit RiskVote(APPROVE) after acknowledgement; log to 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.
// SignaturePreviewer
FUNCTION previewSignature(typedData, strategyEnvelope):
IF FETCH(internal.killswitch).active:
EMIT RiskVote(DENY, KILL_SWITCH_ACTIVE); RETURN
// Domain check
IF typedData.domain.version != '2':
EMIT RiskVote(DENY, CONTRACT_GUARD_DOMAIN_MISMATCH); RETURN
// Resolve market name
meta = FETCH(gamma.markets[typedData.message.tokenId])
marketName = meta ? meta.question : '(unresolved)'
// Envelope diff
deviation = ABS(typedData.message.size_usd - strategyEnvelope.size_usd)
/ strategyEnvelope.size_usd
IF deviation > 0.20 AND params.block_on_envelope_mismatch:
EMIT RiskVote(DENY, SIGNATURE_ENVELOPE_BREACH); RETURN
// Render preview
preview = {
market: marketName,
side: typedData.message.side,
size_pusd: typedData.message.size_usd,
price: typedData.message.price,
contract: typedData.domain.verifyingContract
}
EMIT preview TO wallet_ui
AWAIT user.acknowledge()
EMIT RiskVote(APPROVE)
LOG(governance.audit, {preview, decision: 'APPROVE'})
SDK calls used
gamma.get_market(token_id)clob_auth.get_typed_data(intent_id)internal.killswitch.status()
Complexity: O(1) per signing request — single Gamma API call
11. Wire Examples
Input — what arrives on the wire
EIP-712 typed data for BUY order — clob_auth
{
"intent_id": "int_3c4d5e6f7a8b9c0d",
"domain": {
"name": "CTFExchange",
"version": "2",
"chainId": 137,
"verifyingContract": "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
},
"message": {
"side": "BUY",
"tokenId": "0x5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e",
"size_usd": 400,
"price": 0.55,
"timestamp_ms": 1746768672000
}
}
Output — what the bot emits
RiskVote — APPROVE with preview
{
"vote_id": "sec.signature_previewer.20260509T140000Z",
"decision": "APPROVE",
"reason_code": null,
"preview": {
"market": "US Election — Winner",
"side": "BUY",
"size_pusd": 400,
"price": 0.55
},
"checked_at": "2026-05-09T14:00:00Z"
}
12. Decision Logic
APPROVE
Domain valid, order within envelope, user has acknowledged preview.
RESHAPE_REQUIRED
Not applicable — previewer approves or rejects; it does not modify orders.
REJECT
Domain separator mismatch, envelope breach > 20%, or KillSwitch active.
WARNING_ONLY
Warn in preview when envelope deviation is 10–20%.
13. Standard Decision Output
This bot returns a RiskVote object. See RiskVote schema.
{
"vote_id": "sec.signature_previewer.20260509T140000Z",
"decision": "APPROVE",
"reason_code": null,
"evidence": {
"market": "US Election \u2014 Winner",
"side": "BUY",
"size_pusd": 400,
"price": 0.55,
"domain_ok": true,
"envelope_deviation_pct": 2.0,
"user_acknowledged": true
},
"checked_at": "2026-05-09T14: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. |
CONTRACT_GUARD_DOMAIN_MISMATCH | HARD_REJECT | EIP-712 domain version is not '2' or verifyingContract is not in V2 allow-list. | Return DENY; emit security alert. | The security parameters in this order do not match the current exchange. |
SIGNATURE_ENVELOPE_BREACH | HARD_REJECT | Order parameters deviate more than 20% from strategy declared envelope. | Return DENY; display explanation to user. | This order is outside your strategy's declared parameters. |
SIGNATURE_ENVELOPE_WARN | WARN | Order deviates 10–20% from envelope. | Display warning in preview; allow user to proceed. | This order is slightly outside your typical parameters. Review before signing. |
MARKET_UNRESOLVED | INFO | Gamma API unavailable; market name could not be resolved. | Show raw token ID with warning banner. | Market name could not be loaded. Showing raw order details. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_sec_signaturepreviewer_decisions_total | counter | count | decision | Total preview decisions. |
polytraders_sec_signaturepreviewer_envelope_breaches_total | counter | count | | Orders rejected for envelope breach. |
polytraders_sec_signaturepreviewer_domain_mismatches_total | counter | count | | Domain separator mismatches detected. |
polytraders_sec_signaturepreviewer_preview_latency_ms | histogram | ms | | Time to render and display preview. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
SignaturePreviewerDomainMismatch | rate(polytraders_sec_signaturepreviewer_domain_mismatches_total[5m]) > 0 | P0 | #runbook-signaturepreviewer-domain |
SignaturePreviewerEnvelopeBreach | rate(polytraders_sec_signaturepreviewer_envelope_breaches_total[5m]) > 0 | P1 | #runbook-signaturepreviewer-envelope |
16. Developer Reporting
{
"bot_id": "sec.signature_previewer",
"decision": "APPROVE",
"inputs_used": [
"eip712.typed_data",
"gamma.market_metadata",
"strategy.envelope"
],
"checked_at": "2026-05-09T14:00:00Z"
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| Preview shown before signing | You are about to sign an order. Please review the details and confirm. |
| Order blocked — envelope breach | This order is outside the parameters you set for this strategy and has been blocked. |
| Order blocked — domain mismatch | The security parameters in this order do not match the current exchange. It has been blocked. |
18. Failure-Mode Block
| main_failure_mode | Preview not shown because Gamma API is unreachable, leaving user to sign a raw typed blob. |
|---|
| false_positive_risk | Legitimate strategy update triggers envelope mismatch alert before envelope cache refreshes. |
|---|
| false_negative_risk | Market name resolves stale, showing an outdated description while order targets a different market. |
|---|
| safe_fallback | If Gamma API is unreachable, display raw typed data with a warning that market names could not be resolved. Block if domain check also fails. |
|---|
| required_dependencies | Gamma API for market metadata, StrategyConfig for envelope, KillSwitch |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
DOMAIN_VERSION_V1 | Set typedData.domain.version='1' | | Automatic on next order with correct domain. |
ENVELOPE_BREACH | Set order.size_usd to 5x strategy envelope max | | Strategy must re-declare envelope or reduce order size. |
GAMMA_API_DOWN | Block Gamma API endpoint | | Automatic when Gamma API recovers. |
20. State & Persistence
Cold-start recovery
Preview log rebuilt from audit trail on restart.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | async per signing request |
| Max in-flight | 50 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 2000 |
| Backpressure strategy | queue; reject if user does not acknowledge within timeout |
| Locking / mutual exclusion | none — each preview is independent |
22. Dependencies
Depends on (must run first)
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|
| gov.builder_attribution | Log every signed preview and decision. | GovernanceLog entry on each APPROVE or DENY. |
Sibling bots (same OrderIntent)
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| Gamma API | https://gamma-api.polymarket.com | 99.9% | Show raw typed data with warning; domain check still enforced. |
23. Security Surfaces
Abuse vectors considered
- Phishing payload with misleading market name in typed data
- Forged domain separator to redirect signing to different chain
Mitigations
- Market names resolved from trusted Gamma API, not from order payload
- Domain separator validated against V2 allow-list before display
24. Polymarket V2 Compatibility
| Aspect | Value |
|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | yes |
| Aware of negative-risk markets | yes |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | Validates EIP-712 domain version '2' in every signing request; displays pUSD amounts and builder code in preview. |
API surfaces declared
clob_authgammainternal
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 |
|---|
| Preview renders market name from Gamma API | typed_data with token_id=0xABC, Gamma returns 'US Election' | Preview shows 'US Election' |
| Reject when domain version is '1' | eip712_domain_version='1' | DENY(CONTRACT_GUARD_DOMAIN_MISMATCH) |
| Reject when envelope deviation > 20% | order.size_usd=2000, envelope.max_size_usd=1000 | DENY(SIGNATURE_ENVELOPE_BREACH) |
Integration Tests
| Test | Expected result |
|---|
| Gamma API unreachable — raw typed data shown with warning | Preview displayed with raw fields and warning banner; domain check still runs |
| User acknowledgement required before APPROVE | RiskVote APPROVE not emitted until user confirms |
Property Tests
| Property | Required behaviour |
|---|
| Domain separator mismatch always produces DENY | Always true |
| Every signing request generates at least a minimal preview | Always true |
27. Operational Runbook
Domain mismatch and envelope breach alerts are P0/P1 security events requiring immediate investigation.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
SignaturePreviewerDomainMismatch | | | | |
SignaturePreviewerEnvelopeBreach | | | | |
Manual overrides
Healthcheck
GET /internal/health/signaturepreviewer → green if Gamma API reachable; last preview rendered within 30s.; red if Gamma API unreachable for > 60s or domain mismatch alert firing.
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 |