P2.3 of the autotrade-foundation branch: the MarketData async trait and ResolvedMarket snapshot type, with validated outcome→CLOB-token-id lookup. Provides the engine’s market-resolution seam so sizing and order-building code never works with raw API responses.
What was built
New file: crates/polymarket-fetch/src/pmv2/autotrade/market.rs.
Wired in via pub mod market; in autotrade/mod.rs.
Added async-trait = "0.1" to crates/polymarket-fetch/Cargo.toml.
Types
MarketStatusView — copy struct (derives Clone, Debug):
| field | type | meaning |
|---|---|---|
active | bool | market is active |
accepting_orders | bool | CLOB is open |
resolved | bool | market has settled |
closed | bool | market is closed |
secs_to_resolution | Option<i64> | seconds until end date (None if unknown) |
resolution_proposed | bool | UMA result proposed but market not yet closed (added 2026-06-17, commit 8b3c451; set fail-open by the adapter — 30s Gamma timeout, warn!, default false; consumed by liveness_gate to block entries — see autotrade-resolution-proposed-entry-guard) |
ResolvedMarket — full market snapshot:
| field | type |
|---|---|
condition_id | String |
outcomes | Vec<String> |
clob_token_ids | Vec<String> |
tick_size | f64 |
neg_risk | bool |
status | MarketStatusView |
best_bid | Option<f64> |
best_ask | Option<f64> |
ResolvedMarket::token_for(outcome_index: i32, outcome_label: &str) -> Result<String>
The load-bearing validation function:
- Checks that
clob_token_ids.len() == outcomes.len()(array-length invariant guard) - Checks that
outcome_indexis ≥ 0 and in range - Checks that
outcomes[outcome_index]matchesoutcome_label(case-sensitive) - Returns
clob_token_ids[outcome_index].clone()
MarketData async trait (via #[async_trait]):
#[async_trait]
pub trait MarketData: Send + Sync {
async fn resolve(
&self,
condition_id: &str,
outcome_index: i32,
) -> Result<ResolvedMarket>;
}Single method returning a fully-hydrated ResolvedMarket. The concrete impl (backed by the vendored CLOB client) will live in the engine layer; this trait is the seam that lets tests inject a mock.
Tests (4 passing)
| test | what it verifies |
|---|---|
token_for_validated_outcome_ok | happy path — correct index + label returns the right token |
token_for_label_mismatch_rejected | wrong label for a valid index → Err |
token_for_out_of_range_rejected | index ≥ vec length → Err |
token_for_array_length_mismatch_rejected | clob_token_ids.len() != outcomes.len() → Err |
Gate gotchas
clippy::unwrap_usedfires in test bodiesThe gate runs
cargo clippy --workspace --all-targets -- -D warnings, which includes test code.unwrap()in test bodies triggersclippy::unwrap_used. Fix: add#[allow(clippy::unwrap_used)]on each test function (or the test module).
cargo fmt --checkreorders imports alphabeticallyrustfmt sorts use-items alphabetically —
{Result, bail}must be written{bail, Result}or fmt fails. It also expands long struct literals onto multiple lines.
async-traitmust be in the crate's own Cargo.toml
async-trait = "0.1"was already in the workspace lock (version 0.1.89) but NOT incrates/polymarket-fetch/Cargo.toml. The crate must declare it explicitly or the#[async_trait]macro import fails to resolve.
Related
- autotrade-resolution-proposed-entry-guard — adds the
resolution_proposedflag (set fail-open viaGammaClient::market_resolution_proposed) to block buying already-decided markets - pmv2-autotrade-engine-design
- autotrade-signal-type
- autotrade-authz-module
- ci