nautechsystems/nautilus_trader · critical
Factory resolves an unexpected pool for the swap token pair
Error message
Factory resolves an unexpected pool for the swap token pair and fee
What it means
Raised during pre-trade verification when the Uniswap V3 factory's getPool(token0, token1, fee) returns an address different from plan.pool_address. This confirms the pool recorded in the plan is actually the canonical pool the factory computes for that token pair and fee tier. A mismatch aborts the swap because the plan targets a non-canonical or nonexistent pool.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:5099
}
.abi_encode();
let registered_pool = required_verification(
executor
.verification
.verify_decoded_call(
None,
&plan.factory,
U256::ZERO,
&pool_call,
block,
|result| {
UniswapV3Factory::getPoolCall::abi_decode_returns(result).map_err(Into::into)
},
)
.await,
"swap factory pool",
)?;
anyhow::ensure!(
registered_pool.value == plan.pool_address,
"Factory resolves an unexpected pool for the swap token pair and fee"
);
decisions.push(verification_decision(
®istered_pool,
Some(block),
Some(block),
));
for token in [&plan.pool.token0, &plan.pool.token1] {
let decimals_call = ERC20::decimalsCall.abi_encode();
let decimals = required_verification(
executor
.verification
.verify_decoded_call(
None,
&token.address,
U256::ZERO,View on GitHub (pinned to 18893faf8b)
Solutions
- Rebuild the execution plan and set plan.pool_address from factory.getPool(token0, token1, fee) at plan time
- Verify the pool exists (getPool returned non-zero) for the chosen token pair and fee tier
- Confirm plan.factory matches the factory that actually deployed the pool
- If multiple fee tiers exist, pick the intended tier explicitly and regenerate the plan
Example fix
// before plan.pool_address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"; // hardcoded 0.3% USDC/WETH // after plan.pool_address = factory.get_pool(token0, token1, plan.fee).await?; // canonical pool from factory
Defensive patterns
Strategy: validation
Validate before calling
async fn check_pool_canonical(factory: Address, token0: Address, token1: Address, fee: u32, plan_pool: Address) -> Result<(), String> {
let actual: Address = factory_get_pool(factory, token0, token1, fee).await?;
if actual.is_zero() { return Err("pool does not exist for this pair and fee".into()); }
if actual != plan_pool { return Err(format!("pool mismatch: {actual} != {plan_pool}")); }
Ok(())
} Type guard
fn pool_matches(plan_pool: Address, actual: Address) -> bool { !actual.is_zero() && plan_pool == actual } Prevention
- Always derive pool addresses via factory.getPool, never hardcode
- Verify the pool exists (non-zero address) before planning a swap
- Pin the fee tier explicitly and confirm the canonical pool for it
- Detect token0/token1 ordering (sort by address) before calling getPool
When it happens
Trigger: Executing a swap plan where factory.getPool(tokenIn, tokenOut, fee) != plan.pool_address — e.g. the plan's pool was created via createPool with unusual parameters, or the pool address was computed/hardcoded incorrectly.
Common situations: Hardcoded pool address from a different fee tier, pool deployed by a different factory than plan.factory, plan built on one chain and replayed on another, or a malicious fork pool address entered in the plan.
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 factory
- Router {} reports an unexpected wrapped native contract
- 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/350a74ff3aca05a5.
Report an issue: GitHub.