1. Bot Identity
| Layer | Execution Execution |
|---|
| Bot class | Execution Utility |
|---|
| Authority | Reshape |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Order signing and CLOB V2 submission; dust sweep runs on cron |
|---|
| Runs after | PartialFillHandler (remainder below min_economic_size) or strategy sizing step |
|---|
| Applies to | Any order with remainder below min_economic_size and any existing dust positions on cron |
|---|
| Default mode | shadow_only |
|---|
| User-visible | yes |
|---|
| Developer owner | Polytraders core — Execution pod |
|---|
Operational profile
| Modes supported | quarantine |
|---|
2. Purpose
DustAndRoundingCleaner prevents creation of dust positions — holdings so small that their eventual pUSD value at settlement is less than the transaction cost of acquiring or selling them. It rounds order sizes to economically viable minimums and sweeps existing dust positions on a configurable cron schedule.
3. Why This Bot Matters
Dust position created
A position of <$1 pUSD accumulates with no path to profitable exit; creates accounting noise and wastes position-budget slots.
Rounding not applied before submission
Orders with fractional pUSD sizes are rejected by CTFExchangeV2 which requires integer token units; submission fails with a parse error.
Dust sweep not scheduled
Over time, hundreds of dust positions accumulate from partial fills, consuming wallet gas allowance on Polygon and polluting portfolio reporting.
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 |
|---|
| min_economic_size_usd | 5 | 2 | 1 | Minimum order or position size in pUSD below which the order is rejected (too small) or position is flagged for sweep. |
| round_strategy | round_down | — | — | Rounding strategy for order sizes: round_down (conservative, never oversize), round_nearest (closest integer), or truncate (same as round_down). |
| auto_sweep_dust | True | — | — | Automatically sweep dust positions (sell or cancel) on the sweep_cron schedule. |
| sweep_cron | 0 4 * * * | — | — | Cron expression defining when the automatic dust sweep runs (default: 04:00 UTC daily). |
7. Detailed Parameter Instructions
min_economic_size_usd
What it means
Minimum order or position size in pUSD below which the order is rejected (too small) or position is flagged for sweep.
Default
{ "min_economic_size_usd": 5 }
Why this default matters
At $5 pUSD minimum, a worst-case transaction cost of ~$0.50 (10% of position) is acceptable. Below $2, fees dominate.
Threshold logic
| Condition | Action |
|---|
| size_usd >= 5 | No intervention |
| 2 <= size_usd < 5 | WARN — DUST_WARN; flag for sweep |
| size_usd < 1 (hard) | HARD_REJECT — DUST_HARD_REJECT; cancel order or sweep position |
Developer check
if size_usd < params.min_economic_size_usd: emit(DUST_WARN)
User-facing English
A portion of your order was too small to keep open and was removed.
round_strategy
What it means
Rounding strategy for order sizes: round_down (conservative, never oversize), round_nearest (closest integer), or truncate (same as round_down).
Default
{ "round_strategy": "round_down" }
Why this default matters
Round-down ensures the final order never exceeds the intent size, which is important for risk constraint compliance.
Threshold logic
| Condition | Action |
|---|
| round_strategy=round_down | floor(size_usd * 1e6) / 1e6 — never exceeds intent |
| round_strategy=round_nearest | round(size_usd * 1e6) / 1e6 |
| round_strategy=truncate | int(size_usd * 1e6) / 1e6 |
Developer check
roundedSize = toUsdcUnits(plan.size_usd, params.round_strategy)
User-facing English
Your order size was rounded to the nearest valid unit.
auto_sweep_dust
What it means
Automatically sweep dust positions (sell or cancel) on the sweep_cron schedule.
Default
{ "auto_sweep_dust": true }
Why this default matters
Automated sweep prevents dust accumulation without requiring manual operator intervention.
Threshold logic
| Condition | Action |
|---|
| auto_sweep_dust=true | Dust positions swept on cron |
| auto_sweep_dust=false | WARN only; operator must sweep manually |
Developer check
if params.auto_sweep_dust: scheduleSweep(params.sweep_cron)
User-facing English
Small leftover positions are automatically cleaned up on a schedule.
sweep_cron
What it means
Cron expression defining when the automatic dust sweep runs (default: 04:00 UTC daily).
Default
{ "sweep_cron": "0 4 * * *" }
Why this default matters
04:00 UTC is typically low-volume on Polymarket; sweeping then minimises market impact of small sell orders.
Threshold logic
| Condition | Action |
|---|
| valid cron expression | Sweep scheduled at specified time |
| invalid cron expression | Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL |
Developer check
if not isValidCron(params.sweep_cron): raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')
User-facing English
Small leftover positions are cleaned up automatically each day.
8. Default Configuration
{
"bot_id": "exec.dustandroundingcleaner",
"version": "0.1.0",
"mode": "shadow_only",
"defaults": {
"min_economic_size_usd": 5,
"round_strategy": "round_down",
"auto_sweep_dust": true,
"sweep_cron": "0 4 * * *"
},
"locked": {
"min_economic_size_usd": {
"min": 1
},
"sweep_cron": {
"requires_approval": true
}
}
}
9. Implementation Flow
- On receipt of ExecutionPlan from SmartRouter: round plan.size_usd to nearest valid integer pUSD unit using round_strategy.
- If rounded_size < min_economic_size_usd: emit DUST_WARN or DUST_HARD_REJECT; cancel plan.
- If remainder from PartialFillHandler is < min_economic_size_usd: cancel remainder; emit DUST_REMAINDER_CANCELLED.
- On sweep_cron trigger: fetch clob_auth /positions; filter positions where value_usd < min_economic_size_usd.
- For each dust position: if market is still open, issue a GTC sell order at mid; else wait for settlement.
- Emit ExecutionReport(DUST_SWEPT) per swept position with builder_code for attribution.
- Log all sweep actions to WAL for 7-year audit retention.
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.
// ── Real-time path: size validation ──
FUNCTION validateSize(plan):
// Round to 6-decimal pUSD units
IF params.round_strategy == 'round_down':
roundedSize = floor(plan.size_usd * 1e6) / 1e6
ELSE:
roundedSize = round(plan.size_usd * 1e6) / 1e6
IF roundedSize < params.hard: // < 1 pUSD
DISCARD plan; reason=DUST_HARD_REJECT
RETURN
IF roundedSize < params.min_economic_size_usd:
EMIT ExecutionReport(DUST_WARN)
// Still proceed (warn only above hard)
IF roundedSize < plan.size_usd:
plan.size_usd = roundedSize
EMIT ExecutionReport(DUST_ROUNDED)
RETURN plan
// ── Cron path: sweep existing dust positions ──
FUNCTION sweepDust():
IF NOT params.auto_sweep_dust:
RETURN
positions = FETCH clob_auth.GET('/positions')
IF positions IS NULL:
EMIT WARN(DUST_SWEEP_POSITIONS_UNAVAILABLE)
RETURN
dustPositions = [p FOR p IN positions IF p.value_usd < params.min_economic_size_usd]
FOR pos IN dustPositions:
book = FETCH clob_public.GET('/book?market=' + pos.market_id)
mid = (book.best_bid + book.best_ask) / 2
sweepOrder = buildOrderTypedData({
market_id: pos.market_id,
side: 'SELL',
price: mid,
size: pos.size,
builder: BUILDER_CODE,
timestamp: now_ms()
})
clob_auth.POST('/order', sign(sweepOrder))
EMIT ExecutionReport(DUST_SWEPT, pos.market_id, pos.value_usd)
SCHEDULE sweepDust AT params.sweep_cron
SDK calls used
clob_auth.GET('/positions')clob_public.GET('/book?market=' + market_id)buildOrderTypedData({side: SELL, price: mid, size, builder_code, timestamp})clob_auth.POST('/order', signed_sweep_order)
Complexity: O(P) where P = number of dust positions (sweep path); O(1) real-time
11. Wire Examples
Input — what arrives on the wire
ExecutionPlan with fractional size — exec.smart_router
{
"market_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"size_usd": 5.73,
"order_type": "GTC",
"collateral": "pUSD",
"builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000"
}
Output — what the bot emits
ExecutionReport — DUST_ROUNDED
{
"report_id": "rep_8b9c0d1e2f3a4b5c",
"bot_id": "exec.dustandroundingcleaner",
"original_size_usd": 5.73,
"rounded_size_usd": 5.0,
"verdict": "DUST_ROUNDED",
"collateral": "pUSD",
"evaluated_at_ms": 1746770600000
}
12. Decision Logic
APPROVE
size_usd >= min_economic_size_usd after rounding; proceed to signing.
RESHAPE_REQUIRED
Size rounded down to nearest integer unit; DUST_ROUNDED emitted.
REJECT
Rounded size < hard threshold (1 pUSD); DUST_HARD_REJECT; order or remainder cancelled.
WARNING_ONLY
Size between warning (2 pUSD) and default (5 pUSD) threshold; DUST_WARN emitted.
13. Standard Decision Output
This bot returns a ExecutionReport object. See ExecutionReport schema.
{
"report_id": "rep_8b9c0d1e2f3a4b5c",
"trace_id": "trc_7a8b9c0d1e2f3a4b",
"bot_id": "exec.dustandroundingcleaner",
"market_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"original_size_usd": 5.73,
"rounded_size_usd": 5.0,
"verdict": "DUST_ROUNDED",
"collateral": "pUSD",
"builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"evaluated_at_ms": 1746770600000
}
14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|
DUST_ROUNDED | RESHAPE | Order size rounded down to nearest valid integer pUSD unit. | Update plan.size_usd; forward to signing. | Your order size was rounded to the nearest valid unit. |
DUST_WARN | WARN | Order size below min_economic_size_usd but above hard threshold. | Emit WARN; proceed with order (size still valid for CLOB). | Your order size is very small and may not be economically optimal. |
DUST_HARD_REJECT | HARD_REJECT | Order size below hard threshold (1 pUSD) after rounding; not viable. | Discard order; emit no submission. | Your order was too small to submit. |
DUST_SWEPT | INFO | Dust position swept via sell order on cron schedule. | Emit ExecutionReport; log to WAL. | A small leftover position was automatically sold. |
DUST_REMAINDER_CANCELLED | INFO | Sub-minimum remainder from PartialFillHandler cancelled. | Cancel remainder; emit ExecutionReport. | The tiny remaining portion of your order was cancelled. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_exec_dustandroundingcleaner_decisions_total | counter | count | verdict | Total decisions by verdict (ROUNDED/WARN/REJECT/SWEPT). |
polytraders_exec_dustandroundingcleaner_dust_positions_swept_total | counter | count | | Total dust positions swept by the cron sweep. |
polytraders_exec_dustandroundingcleaner_dust_value_swept_usd | histogram | pUSD | | Distribution of pUSD values of swept dust positions. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
DRCHighDustRejectRate | rate(polytraders_exec_dustandroundingcleaner_decisions_total{verdict='DUST_HARD_REJECT'}[5m]) > 0.1 | P2 | #runbook-drc-dust-reject |
DRCSweepAccumulation | increase(polytraders_exec_dustandroundingcleaner_dust_positions_swept_total[24h]) > 50 | P3 | #runbook-drc-sweep-accumulation |
16. Developer Reporting
{
"original_size_usd": 5.73,
"rounded_size_usd": 5.0,
"round_strategy": "round_down",
"min_economic_size_usd": 5,
"verdict": "DUST_ROUNDED",
"builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000"
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| Order size rounded | Your order size was rounded to the nearest valid unit before submission. |
| Remainder too small — cancelled | The remaining portion of your order was too small to be worth keeping open and was cancelled. |
| Dust position swept | A small leftover position in your account was automatically sold to clean up your portfolio. |
18. Failure-Mode Block
| main_failure_mode | Sweep order submitted at an unfavourable mid price if market is wide-spread or illiquid at sweep time, resulting in a sweep fill worse than just holding the dust to settlement. |
|---|
| false_positive_risk | Rounding down a legitimate $5.01 order to $5.00 when the minimum order unit is $1.00 is acceptable, but rounding a $1.01 order to $1.00 could trigger the warning threshold. |
|---|
| false_negative_risk | auto_sweep_dust=false: dust accumulates indefinitely if operator does not sweep manually. |
|---|
| safe_fallback | If clob_auth /positions is unavailable during sweep cron, skip the sweep cycle and emit WARN; do not retry aggressively to avoid rate-limit exhaustion on a degraded exchange. |
|---|
| required_dependencies | clob_auth /positions endpoint for sweep, clob_public minimum order size, PartialFillHandler for sub-minimum remainder forwarding |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
CRON_POSITIONS_UNAVAILABLE | Block clob_auth /positions at sweep cron time | | Automatic on next cron trigger |
HARD_REJECT_UNDERSIZED_ORDER | Submit ExecutionPlan with size_usd=0.50 | | Strategy corrects minimum order size |
SWEEP_RATE_LIMIT | Create 100 dust positions to sweep in one cron cycle | | Automatic — all positions swept within sweep window |
20. State & Persistence
Cold-start recovery
Rounding state stateless per order. Cron state read from Redis on restart.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | per-order goroutine (real-time); single-threaded cron worker (sweep) |
| Max in-flight | 50 |
| Idempotency key | order_id + size_usd (real-time); sweep_run_ts_ms (cron) |
| Per-call timeout (ms) | 500 |
| Backpressure strategy | Cron sweep rate-limited to 5 sweep orders/s to avoid CLOB rate limit |
| Locking / mutual exclusion | Redis SETNX for cron to prevent double-sweep if multiple instances run |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|
| exec.partialfillhandler | Source of sub-minimum remainders forwarded for dust handling. | Receives remainder size and order_id; issues cancel. |
| exec.smart_router | Source of ExecutionPlan sizes to validate and round. | Rounded size replaces original in plan before signing. |
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|
| gov.builder_attribution | Every sweep sell order includes builder_code for attribution. | builder_code bytes32 present on all sweep orders. |
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| CLOB V2 auth API | https://clob.polymarket.com | 99.95% / 200ms p99 | If /positions unavailable at cron time, skip sweep; emit WARN. |
| CLOB V2 public API | https://clob.polymarket.com | 99.9% / 200ms p99 | If book unavailable for sweep price, skip that position; retry next cron cycle. |
23. Security Surfaces
Abuse vectors considered
- Triggering the sweep cron repeatedly via schedule manipulation to generate excessive CLOB sell orders
- Injecting a position report with inflated value_usd to prevent legitimate dust positions from being swept
Mitigations
- Cron uses Redis SETNX to prevent double-sweep; sweep rate limited to 5 sell orders/s
- Position list fetched directly from clob_auth; not from a cache that could be tampered with
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 | All sizes rounded to 6-decimal pUSD integer units as required by CTFExchangeV2. Sweep sell orders include builder_code bytes32 for attribution on Polygon. |
API surfaces declared
clob_authclob_publicinternal
Networks supported
polygon
25. Versioning & Migration
| Field | Value |
|---|
| spec | 2.0.0 |
| implementation | 0.1.0 |
| schema | 2 |
| released | None |
| planned_release | Q4-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 |
|---|
| Round-down applied to 5.73 pUSD | size_usd=5.73, round_strategy=round_down | rounded_size_usd=5.0; DUST_ROUNDED emitted |
| HARD_REJECT when rounded size < 1 pUSD | size_usd=0.80 | rounded_size_usd=0; DUST_HARD_REJECT; order cancelled |
| Sweep: identify positions < min_economic_size_usd | position value=3.50 pUSD, min_economic_size=5 | Position flagged for sweep; GTC sell order submitted at mid |
Integration Tests
| Test | Expected result |
|---|
| Cron sweep: fetch positions, filter dust, submit sell orders, emit ExecutionReports | All dust positions swept; DUST_SWEPT emitted per position; WAL entries written |
| Remainder from PartialFillHandler: 2.50 pUSD remainder forwarded and cancelled | DUST_REMAINDER_CANCELLED emitted; no remainder order left on book |
Property Tests
| Property | Required behaviour |
|---|
| rounded_size_usd always <= original_size_usd when round_strategy=round_down | Always true |
| Sweep orders carry builder_code for attribution | Always true |
27. Operational Runbook
DustAndRoundingCleaner incidents are either high DUST_HARD_REJECT rates (strategy generating undersized orders) or sweep accumulation (dust positions building up faster than cron clears them).
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
DRCHighDustRejectRate | Identify strategy generating undersized orders; check minimum size configuration. | | | Strategy pod lead |
DRCSweepAccumulation | Check sweep cron execution log; if cron missed due to clob_auth outage, trigger manual sweep. | | | Exec pod lead |
Manual overrides
polytraders bot trigger-sweep exec.dustandroundingcleaner — Manual sweep needed outside scheduled cron time; e.g. after a period of high partial fills.
Healthcheck
GET /internal/health/dustandroundingcleaner -> 200 if clob_auth reachable, last_sweep < 25h ago, hard_reject_rate < 0.01/min. Red: last_sweep > 25h ago, hard_reject_rate > 0.1/min, cron not 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 |