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
- Re-query balanceOf at both blocks from an independent trusted RPC and compare
- Confirm the weth_address used for verification matches the contract the transaction interacted with
- Check for chain reorgs: verify the inclusion block hash is still canonical at that height
- 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
- Pin weth_address in config and cross-check it against the tx's log addresses
- Detect reorgs by re-checking the inclusion block hash before verification
- Use a single consistent RPC/fork for all verification calls in a session
- Log Deposit event amount from the receipt and compare with the planned wrap amount
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
- {context} verification disagreed
- {context} verification is locally invalid
- Receipt verification is locally invalid for transaction {tx_
- Finalized header verification disagreed
- Finalized header verification is locally invalid
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/92e43d2c4b9ca6cd.
Report an issue: GitHub.