nautechsystems/nautilus_trader · critical
Router {} reports an unexpected factory
Error message
Router {} reports an unexpected factory What it means
Raised during pre-trade router verification when the swap router's factory() call returns an address different from the plan.factory recorded in the execution plan. This guards against the router not being the expected Uniswap V3 router or pointing to a cloned/malicious factory. Verification fails before any swap is submitted.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:5038
let factory_call = UniswapV3RouterState::factoryCall.abi_encode();
let router_factory = required_verification(
executor
.verification
.verify_decoded_call(
None,
&plan.router,
U256::ZERO,
&factory_call,
block,
|result| {
UniswapV3RouterState::factoryCall::abi_decode_returns(result)
.map_err(Into::into)
},
)
.await,
"swap router factory",
)?;
anyhow::ensure!(
router_factory.value == plan.factory,
"Router {} reports an unexpected factory",
plan.router,
);
decisions.push(verification_decision(
&router_factory,
Some(block),
Some(block),
));
let weth_call = UniswapV3RouterState::WETH9Call.abi_encode();
let router_weth = required_verification(
executor
.verification
.verify_decoded_call(
None,
&plan.router,
U256::ZERO,View on GitHub (pinned to 18893faf8b)
Solutions
- Rebuild the execution plan so plan.factory matches the factory reported by the router currently deployed at plan.router
- Confirm plan.router and plan.factory are from the same network/deployment (chain ID check)
- Re-fetch canonical Uniswap V3 router and factory addresses from official deployments rather than manual entry
- If the router is behind a proxy, verify the proxy admin/upgrades did not change factory()
Example fix
// before plan.router = "0xE592427A0AEce92De3Edee1F18E0157C05861564"; // plan stale after redeploy // after let plan = build_execution_plan(chain_id).await?; // re-derive router/factory/weth for current deployment
Defensive patterns
Strategy: validation
Validate before calling
async fn check_router_factory(router: Address, plan_factory: Address) -> Result<(), String> {
let actual: Address = call_router_factory(router).await?;
if actual != plan_factory { return Err(format!("router {router} factory mismatch: {actual} != {plan_factory}")); }
Ok(())
} Type guard
fn factory_matches(plan_factory: Address, actual: Address) -> bool { plan_factory == actual } Prevention
- Always build plans from a trusted deployment registry for the exact chain ID
- Never reuse execution plans across chains or after contract redeploys
- Cache canonical router/factory/WETH addresses with chain-id keys
- Treat any factory mismatch as a potential malicious router and halt trading
When it happens
Trigger: Executing a swap plan where on-chain router.factory() != plan.factory, e.g. the plan was built against a different router deployment or the router contract was upgraded/replaced at plan.router.
Common situations: Stale execution plan reused after a router redeploy, plan built on one network and replayed on another (different contract addresses), router address typo'd in the plan, or a malicious/proxy router.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- Router {} reports an unexpected wrapped native contract
- Factory resolves an unexpected pool for the swap token pair
- Finalized transaction {} emitted {} Swap logs; expected exac
- Missing tickLower in topic2 when parsing burn event
- Missing tickUpper in topic3 when parsing burn event
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/f60abd07972130eb.
Report an issue: GitHub.