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(
        &registered_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

  1. Rebuild the execution plan and set plan.pool_address from factory.getPool(token0, token1, fee) at plan time
  2. Verify the pool exists (getPool returned non-zero) for the chosen token pair and fee tier
  3. Confirm plan.factory matches the factory that actually deployed the pool
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/350a74ff3aca05a5. Report an issue: GitHub.