1. Bot Identity
| Layer | Risk Risk |
|---|
| Bot class | Guardrail |
|---|
| Authority | VetoReshape |
|---|
| Status | PLANNED |
|---|
| Readiness | Planned |
|---|
| Runs before | ExecutionPlan emit |
|---|
| Runs after | Strategy OrderIntent |
|---|
| Applies to | Every OrderIntent — refuses trades whose realistic fee and Polygon gas burden consumes more than the configured fraction of expected edge |
|---|
| Default mode | planned |
|---|
| User-visible | summary-only |
|---|
| Developer owner | Polytraders core — Risk pod |
|---|
Operational profile
| Modes supported | quarantine |
|---|
2. Purpose
FeeAndGasGuard estimates the total transaction cost (CLOB maker/taker fee plus Polygon gas) for a proposed order and rejects or downsizes it when the cost-to-edge ratio exceeds the configured ceiling. It prevents strategies from placing orders where fee drag exceeds the expected edge, turning a positive-expected-value intent into a negative-EV execution.
3. Why This Bot Matters
Fee drag exceeds edge
Placing an order where fees exceed the expected edge results in a systematically negative-EV trade that erodes capital over many repetitions.
Gas spike not accounted for
Polygon gas spikes during congestion can materially increase transaction cost; ignoring gas makes fee estimates unreliable during high-traffic periods.
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_fee_to_edge_ratio | 0.5 | 0.35 | 0.5 | Maximum ratio of total transaction cost (fee + gas) to expected edge. Orders where cost/edge > this threshold are blocked or downsized. |
| max_fee_bps | 100 | 75 | 100 | Absolute maximum fee rate in basis points regardless of the edge ratio. Protects against operator-set fee spikes on specific markets. |
| min_order_usd | 10 | None | None | Minimum order size in pUSD. Below this threshold, fixed gas costs dominate the fee structure and the cost-to-edge ratio is always unfavourable. |
7. Detailed Parameter Instructions
max_fee_to_edge_ratio
What it means
Maximum ratio of total transaction cost (fee + gas) to expected edge. Orders where cost/edge > this threshold are blocked or downsized.
Default
{ "max_fee_to_edge_ratio": 0.5 }
Why this default matters
At 0.5, at least 50% of the expected edge survives after costs; below this threshold execution becomes net-negative in expectation.
Threshold logic
| Condition | Action |
|---|
| cost_to_edge <= 0.35 | APPROVE |
| 0.35 < cost_to_edge <= 0.5 | WARN — FEE_GUARD_COST_APPROACHING |
| cost_to_edge > 0.5 | HARD_REJECT — FEE_GUARD_COST_EXCEEDS_EDGE |
Developer check
if (totalCost / expectedEdge > params.max_fee_to_edge_ratio) return reject('FEE_GUARD_COST_EXCEEDS_EDGE');
User-facing English
This trade's fees would consume too much of the expected gain.
max_fee_bps
What it means
Absolute maximum fee rate in basis points regardless of the edge ratio. Protects against operator-set fee spikes on specific markets.
Default
{ "max_fee_bps": 100 }
Why this default matters
100 bps is the taker fee ceiling defined in the V2 protocol; orders on markets above this rate indicate a misconfiguration or anomalous market.
Threshold logic
| Condition | Action |
|---|
| effective_fee_bps <= 75 | APPROVE |
| 75 < effective_fee_bps <= 100 | WARN |
| effective_fee_bps > 100 | HARD_REJECT — FEE_GUARD_RATE_ANOMALY |
Developer check
if (effectiveFeeBps > params.max_fee_bps) return reject('FEE_GUARD_RATE_ANOMALY');
User-facing English
The fee rate for this market is unusually high. The order was blocked to protect against unexpected costs.
min_order_usd
What it means
Minimum order size in pUSD. Below this threshold, fixed gas costs dominate the fee structure and the cost-to-edge ratio is always unfavourable.
Default
{ "min_order_usd": 10 }
Why this default matters
Gas costs are fixed per transaction; below 10 pUSD the gas component alone can exceed the expected edge on a typical order.
Threshold logic
| Condition | Action |
|---|
| intent.size_usd >= 10 | Proceed with fee check |
| intent.size_usd < 10 | HARD_REJECT — FEE_GUARD_ORDER_TOO_SMALL |
Developer check
if (intent.size_usd < params.min_order_usd) return reject('FEE_GUARD_ORDER_TOO_SMALL');
User-facing English
The order size is too small to trade economically after fees. Please use a larger order size.
8. Default Configuration
{
"bot_id": "risk.fee_and_gas_guard",
"version": "0.1.0",
"mode": "hard_guard",
"defaults": {
"max_fee_to_edge_ratio": 0.5,
"max_fee_bps": 100,
"min_order_usd": 10
},
"locked": {
"max_fee_bps": {
"max": 100
},
"min_order_usd": {
"min": 1
}
}
}
9. Implementation Flow
- Receive OrderIntent with market_id, side, size_usd, price, and expected_edge_bps from strategy.
- Check KillSwitch; if active, HARD_REJECT(KILL_SWITCH_ACTIVE).
- Fetch current market probability p = (best_ask + best_bid) / 2 from CLOB.
- Fetch operator-set fee rate for the market from CLOB (taker: max 100 bps; maker: max 50 bps).
- Compute CLOB fee: fee_usd = size_usd * fee_rate_bps / 10000 * p * (1-p).
- Fetch current Polygon gas price; estimate gas cost for CTFExchangeV2.matchOrders().
- Compute total_cost_usd = fee_usd + gas_cost_usd.
- If intent.size_usd < min_order_usd, HARD_REJECT(FEE_GUARD_ORDER_TOO_SMALL).
- If effective_fee_bps > max_fee_bps, HARD_REJECT(FEE_GUARD_RATE_ANOMALY).
- Compute edge_usd = size_usd * expected_edge_bps / 10000.
- If total_cost_usd / edge_usd > max_fee_to_edge_ratio, HARD_REJECT(FEE_GUARD_COST_EXCEEDS_EDGE).
- APPROVE with fee_estimate_usd and cost_to_edge_ratio attached.
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.
FUNCTION evaluateFeeAndGas(intent):
ks = FETCH internal.killswitch.status
IF ks.active: EMIT RiskVote(HARD_REJECT, KILL_SWITCH_ACTIVE); RETURN
IF intent.size_usd < params.min_order_usd:
EMIT RiskVote(HARD_REJECT, FEE_GUARD_ORDER_TOO_SMALL); RETURN
feeRate = FETCH clob_public.fee_rate(intent.market_id)
prices = FETCH clob_public.prices(intent.market_id)
gasPrice = FETCH onchain.gas_price()
IF feeRate IS NULL OR prices IS NULL OR gasPrice IS NULL:
EMIT RiskVote(HARD_REJECT, FEE_GUARD_DATA_UNAVAILABLE); RETURN
IF feeRate.bps > params.max_fee_bps:
EMIT RiskVote(HARD_REJECT, FEE_GUARD_RATE_ANOMALY); RETURN
p = (prices.ask + prices.bid) / 2
feeUsd = intent.size_usd * (feeRate.bps / 10000) * p * (1 - p)
gasUsd = estimateGasCost(gasPrice, 'matchOrders')
totalCost = feeUsd + gasUsd
edgeUsd = intent.size_usd * (intent.expected_edge_bps / 10000)
ratio = totalCost / edgeUsd
IF ratio > params.max_fee_to_edge_ratio:
EMIT RiskVote(HARD_REJECT, FEE_GUARD_COST_EXCEEDS_EDGE,
total_cost=totalCost, ratio=ratio); RETURN
IF ratio > params.max_fee_to_edge_ratio * 0.7:
annotations.append(WARN(FEE_GUARD_COST_APPROACHING, ratio=ratio))
EMIT RiskVote(APPROVE, fee_estimate_usd=totalCost, cost_to_edge=ratio)
SDK calls used
clob_public.fee_rate(market_id)clob_public.prices(market_id)onchain.gas_price()internal.killswitch.status()
Complexity: O(1)
11. Wire Examples
Input — what arrives on the wire
OrderIntent — fee check — internal
{
"intent_id": "int_f6a7b8c9d0e10006",
"market_id": "0x4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e",
"size_usd": 150,
"price": 0.5,
"expected_edge_bps": 40,
"generated_at_ms": 1746800000000
}
Output — what the bot emits
RiskVote — HARD_REJECT cost exceeds edge
{
"guard_id": "risk.fee_and_gas_guard",
"decision": "HARD_REJECT",
"severity": "HARD",
"reason_code": "FEE_GUARD_COST_EXCEEDS_EDGE",
"message": "Cost 4.20 pUSD / edge 6.00 pUSD = ratio 0.70 exceeds ceiling 0.50.",
"constraints": {},
"checked_at": "2026-05-10T13:00:00Z"
}
12. Decision Logic
APPROVE
Total transaction cost is within the max_fee_to_edge_ratio and both fee_bps and order size are within limits.
RESHAPE_REQUIRED
Not currently implemented; fee-to-edge ratio cannot be improved by resizing (it is largely size-independent for the CLOB fee formula). Future version may restrict to maker-only to reduce fee.
REJECT
Fee-to-edge ratio exceeds the hard ceiling, fee rate is anomalously high, order size is below min_order_usd, or KillSwitch is active.
WARNING_ONLY
— not yet authored —13. Standard Decision Output
This bot returns a RiskVote object. See RiskVote schema.
{
"guard_id": "risk.fee_and_gas_guard",
"decision": "HARD_REJECT",
"severity": "HARD",
"reason_code": "FEE_GUARD_COST_EXCEEDS_EDGE",
"message": "Estimated cost 4.20 pUSD (ratio 0.70) exceeds max_fee_to_edge_ratio 0.50 for edge 6.00 pUSD.",
"constraints": {},
"inputs_used": [
"clob_public.fee_rate",
"clob_public.prices",
"onchain.gas_price",
"internal.expected_edge"
],
"checked_at": "2026-05-10T13:00:00Z"
}
14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|
KILL_SWITCH_ACTIVE | HARD_REJECT | Global kill switch active. | Immediate HARD_REJECT. | Trading is paused. Please try again later. |
FEE_GUARD_COST_EXCEEDS_EDGE | HARD_REJECT | Total cost-to-edge ratio exceeds the hard ceiling. | HARD_REJECT; log total_cost_usd, edge_usd, and ratio. | This trade's fees would consume too much of the expected gain. |
FEE_GUARD_RATE_ANOMALY | HARD_REJECT | Effective fee rate exceeds max_fee_bps (V2 protocol ceiling). | HARD_REJECT; alert on-call — anomalous fee rate detected. | The fee rate for this market is unusually high. |
FEE_GUARD_ORDER_TOO_SMALL | HARD_REJECT | Order size is below min_order_usd; gas costs dominate. | HARD_REJECT. | The order size is too small to be economical after fees. |
FEE_GUARD_COST_APPROACHING | WARN | Cost-to-edge ratio between warning and hard threshold. | Attach WARN annotation; APPROVE. | |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_risk_feeandgasguard_decisions_total | counter | count | decision, reason_code | Total decisions by type and reason. |
polytraders_risk_feeandgasguard_cost_to_edge_ratio | gauge | ratio | market_id | Current cost-to-edge ratio at last evaluation per market. |
polytraders_risk_feeandgasguard_gas_cost_usd | gauge | usd | | Estimated Polygon gas cost in pUSD at last evaluation. |
polytraders_risk_feeandgasguard_eval_latency_ms | histogram | milliseconds | | Latency from intent to RiskVote emit. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
FeeAndGasGuardGasSpike | polytraders_risk_feeandgasguard_gas_cost_usd > 2.0 | P2 | #runbook-feeandgas-gas |
FeeAndGasGuardRateAnomaly | rate(polytraders_risk_feeandgasguard_decisions_total{reason_code='FEE_GUARD_RATE_ANOMALY'}[5m]) > 0 | P1 | #runbook-feeandgas-anomaly |
16. Developer Reporting
{
"bot_id": "risk.fee_and_gas_guard",
"decision": "HARD_REJECT",
"reason_code": "FEE_GUARD_COST_EXCEEDS_EDGE",
"inputs_used": [
"clob_public.fee_rate",
"clob_public.prices",
"onchain.gas_price"
],
"metrics": {
"fee_usd": 3.8,
"gas_usd": 0.4,
"total_cost_usd": 4.2,
"edge_usd": 6.0,
"cost_to_edge_ratio": 0.7,
"fee_rate_bps": 60,
"prob": 0.5
},
"checked_at": "2026-05-10T13:00:00Z"
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| Order blocked — fees exceed edge | The estimated fees for this trade would consume more than half of the expected gain. The order was blocked to protect your returns. |
| Order blocked — fee rate anomaly | The fee rate for this market is unusually high. The order was blocked to protect against unexpected costs. |
| Order blocked — order too small | Orders below the minimum size are not economical after fees. Please increase the order size. |
18. Failure-Mode Block
| main_failure_mode | Approving an order because the expected_edge estimate from the strategy is overstated, making the cost-to-edge ratio appear acceptable when it is not. |
|---|
| false_positive_risk | Rejecting a valid order because the Polygon gas price estimate is temporarily inflated during congestion. |
|---|
| false_negative_risk | Approving an order because the fee rate cache is stale and does not reflect a recent operator fee increase. |
|---|
| safe_fallback | If fee rate or gas price data is unavailable, HARD_REJECT with FEE_GUARD_DATA_UNAVAILABLE. Never approve on missing fee data. |
|---|
| required_dependencies | CLOB fee rate endpoint, Polygon gas price oracle, CLOB market prices, Strategy expected_edge, KillSwitch active flag |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
GAS_PRICE_UNAVAILABLE | Block onchain gas oracle | | Returns to normal once gas oracle is reachable. |
FEE_RATE_ANOMALY | Set mock fee_rate.bps=150 in CLOB mock | | Returns to normal once fee rate is corrected. |
HIGH_GAS_COST | Set gas price 10x normal in mock | | Returns to APPROVE once gas price normalises. |
20. State & Persistence
Cold-start recovery
On cold start, both caches populated from CLOB and onchain oracle before first evaluation.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | single-threaded event loop |
| Max in-flight | 300 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 100 |
| Backpressure strategy | drop newest |
| Locking / mutual exclusion | fee and gas caches are read-only at evaluation; writes use TTL-based replacement |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|
| risk.kill_switch | Global brake checked first. | HARD_REJECT(KILL_SWITCH_ACTIVE) short-circuits all fee evaluation. |
Emits to (downstream consumers)
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| CLOB API (fee rates + prices) | https://clob.polymarket.com | 99.95% / 200ms p99 | HARD_REJECT(FEE_GUARD_DATA_UNAVAILABLE) if fee data unavailable. |
| Polygon gas oracle | onchain gas price feed | best-effort | HARD_REJECT(FEE_GUARD_DATA_UNAVAILABLE) if gas price unavailable. |
23. Security Surfaces
Abuse vectors considered
- Inflating expected_edge_bps in the intent to make the cost-to-edge ratio appear acceptable
- Submitting during low-gas windows to bypass gas-inclusive cost checks, then executing during high-gas periods
Mitigations
- expected_edge_bps is validated against a per-strategy cap from the internal config; values above the cap are clipped
- Gas price is fetched at evaluation time, not at intent generation time
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 | no |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | Fee computation uses V2 formula: C * feeRate * p * (1-p). Fees are operator-set at match time (not on signed order). Taker max 100 bps; maker max 50 bps; 1 bp granularity. |
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 cost-to-edge within threshold | total_cost=2.0, edge=6.0, ratio=0.33 | APPROVE |
| Reject when cost-to-edge exceeds ceiling | total_cost=4.2, edge=6.0, ratio=0.70 | HARD_REJECT(FEE_GUARD_COST_EXCEEDS_EDGE) |
| Reject when fee_bps > max_fee_bps | effective_fee_bps=120, max=100 | HARD_REJECT(FEE_GUARD_RATE_ANOMALY) |
| Reject when order below min_order_usd | size_usd=5, min=10 | HARD_REJECT(FEE_GUARD_ORDER_TOO_SMALL) |
Integration Tests
| Test | Expected result |
|---|
| Gas spike causes rejection in live environment | HARD_REJECT(FEE_GUARD_COST_EXCEEDS_EDGE) when Polygon gas price increases significantly during congestion |
| KillSwitch bypasses fee check | HARD_REJECT(KILL_SWITCH_ACTIVE) without fetching fee or gas data |
Property Tests
| Property | Required behaviour |
|---|
| Approved order always has cost-to-edge ratio <= max_fee_to_edge_ratio | Always true |
| Missing fee data never results in APPROVE | Always true — fail-closed on data unavailability |
27. Operational Runbook
FeeAndGasGuard incidents typically involve a gas spike or an anomalous fee rate on a newly listed market. Verify gas price normality before adjusting parameters.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
FeeAndGasGuardGasSpike | Check Polygon gas price on external tracker; confirm spike is network-wide, not a data feed error. | | | Infra on-call if gas oracle is misbehaving. |
FeeAndGasGuardRateAnomaly | Identify market_id from reason_code log; check CLOB fee rate endpoint for that market directly. | | | Risk pod lead if fee rate is genuinely anomalous. |
Manual overrides
polytraders risk set-gas-override --gas-usd <value> --duration 300s — During a gas oracle outage; sets a conservative fixed gas estimate to unblock orders.
Healthcheck
GET /internal/health/feeandgasguard → green: CLOB fee rate cache age < 60s, gas price cache age < 15s, no FEE_GUARD_RATE_ANOMALY in last 5m; red: Fee or gas data cache expired or unavailable
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 |