nautechsystems/nautilus_trader · critical

Router {} reports an unexpected wrapped native contract

Error message

Router {} reports an unexpected wrapped native contract

What it means

Raised during pre-trade router verification when the router's WETH9() call returns a wrapped-native token address different from plan.weth. This ensures the router's wrapped native token matches the plan before quoting or swapping. A mismatch means the plan's wrapped-native assumption is wrong for this deployment.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:5066

    let weth_call = UniswapV3RouterState::WETH9Call.abi_encode();
    let router_weth = required_verification(
        executor
            .verification
            .verify_decoded_call(
                None,
                &plan.router,
                U256::ZERO,
                &weth_call,
                block,
                |result| {
                    UniswapV3RouterState::WETH9Call::abi_decode_returns(result).map_err(Into::into)
                },
            )
            .await,
        "swap router wrapped native",
    )?;
    anyhow::ensure!(
        router_weth.value == plan.weth,
        "Router {} reports an unexpected wrapped native contract",
        plan.router,
    );
    decisions.push(verification_decision(
        &router_weth,
        Some(block),
        Some(block),
    ));

    let pool_call = UniswapV3Factory::getPoolCall {
        tokenA: plan.token_in,
        tokenB: plan.token_out,
        fee: plan.fee,
    }
    .abi_encode();
    let registered_pool = required_verification(
        executor

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Rebuild the execution plan so plan.weth matches the router's current WETH9() return value
  2. Verify plan.weth is the wrapped-native contract for the target chain (e.g. WETH on mainnet, WETH on Arbitrum)
  3. Check the plan was generated for the same chain ID the trade is executing on
  4. Use canonical per-chain wrapped-native addresses from the deployment manifest

Example fix

// before
plan.weth = "0x4200000000000000000000000000000000000006"; // base WETH
// after
plan.weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; // mainnet WETH matching router.WETH9()
Defensive patterns

Strategy: validation

Validate before calling

async fn check_router_weth(router: Address, plan_weth: Address) -> Result<(), String> {
    let actual: Address = call_router_weth9(router).await?;
    if actual != plan_weth { return Err(format!("router {router} weth mismatch: {actual} != {plan_weth}")); }
    Ok(())
}

Type guard

fn weth_matches(plan_weth: Address, actual: Address) -> bool { plan_weth == actual }

Prevention

When it happens

Trigger: Executing a swap plan where on-chain router.WETH9() != plan.weth, typically when the plan references a WETH/WBNB/WMATIC address from a different chain or deployment.

Common situations: Plan built on Ethereum mainnet and replayed on an L2 or sidechain with different wrapped-native addresses, weth address typo'd in the plan config, or stale plan after network migration.

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/876fa7d5a1696104. Report an issue: GitHub.