nautechsystems/nautilus_trader · error · anyhow::Error

WETH balance after transaction {} did not increase by {amoun

Error message

WETH balance after transaction {} did not increase by {amount_wei}: expected {expected_balance}, was {}

What it means

After a wrap transaction finalizes, the adapter re-verifies on-chain state: WETH balance at the inclusion block must equal the prior-block balance plus exactly the wrap amount. This ensure! fires when the observed post-transaction balance differs — the deposit did not credit the expected amount (or the balance moved unexpectedly between the two sampled blocks). It guards against verifying a transaction whose on-chain effect does not match the intended wrap.

Source

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

        "wrapped balance after finality",
    )
    .with_context(|| {
        format!(
            "failed to verify WETH balance after included transaction {} at block {}",
            included.tx_hash, included.block_number
        )
    })?;
    let expected_balance = balance_before
        .value
        .checked_add(amount_wei)
        .ok_or_else(|| {
            anyhow::anyhow!(
                "WETH balance overflow for included transaction {} at block {}",
                included.tx_hash,
                included.block_number
            )
        })?;
    anyhow::ensure!(
        balance_after.value == expected_balance,
        "WETH balance after transaction {} did not increase by {amount_wei}: expected {expected_balance}, was {}",
        included.tx_hash,
        balance_after.value
    );

    Ok(vec![
        verification_decision(&balance_before, Some(previous_block), Some(previous_block)),
        verification_decision(
            &balance_after,
            Some(included.block_number),
            Some(included.block_number),
        ),
    ])
}

async fn verify_approve_allowance(
    executor: &TransactionExecutor,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-query balanceOf at both blocks from an independent trusted RPC and compare
  2. Confirm the weth_address used for verification matches the contract the transaction interacted with
  3. Check for chain reorgs: verify the inclusion block hash is still canonical at that height
  4. Inspect the actual transaction's logs (Deposit event) to see what was really executed

Example fix

// before
let weth = Address::from_str(&config.weth)?;
// after
let weth = Address::from_str(&config.weth)?;
anyhow::ensure!(weth == plan.expected_weth_address, "WETH address mismatch in verification config");
Defensive patterns

Strategy: try-catch

Validate before calling

async fn precheck_weth(rpc: &dyn Provider, weth: Address, wallet: Address, block: u64, expected: U256) -> anyhow::Result<()> {
    let bal = ERC20::balanceOfCall { account: wallet }.abi_encode();
    let v = rpc.call(None, weth, bal, Some(block.into())).await?;
    let decoded = ERC20::balanceOfCall::abi_decode_returns(&v, false)?;
    anyhow::ensure!(decoded == expected, "precheck balance mismatch");
    Ok(())
}

Type guard

fn balances_match(balance_after: U256, balance_before: U256, amount: U256) -> bool {
    balance_before.checked_add(amount) == Some(balance_after)
}

Try / catch

match verify_wrap_balance_increase(&executor, &weth, amount, &included).await {
    Ok(d) => apply(d),
    Err(e) if e.to_string().contains("did not increase by") => {
        tracing::warn!("balance divergence on wrap {} — checking reorg", included.tx_hash);
        if chain_reorged(included.block_number).await? { rebuild_inclusion(included).await?; }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `balance_after.value != balance_before.value + amount_wei` at `included.block_number` — e.g. the wrap tx actually sent ETH somewhere else, the WETH contract is not the expected one, or concurrent balance changes occurred between the previous block and the inclusion block.

Common situations: Verifying against a forked/reorged chain where balances differ from the canonical history, querying a different WETH address than the transaction used, an RPC serving stale or divergent state, or the included transaction being a different operation than the wrap that was intended.

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/92e43d2c4b9ca6cd. Report an issue: GitHub.