nautechsystems/nautilus_trader · error
ERC-20 approve returned false for token {token}
Error message
ERC-20 approve returned false for token {token} What it means
Before signing an ERC-20 approve, the client simulates the call and checks its boolean return value. ERC-20 approve returns false on failure in some token implementations; if the simulation succeeds but returns false, the approval will not take effect, so the client bails instead of signing a no-op approval.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:2665
&approve_call,
block,
|result| {
if result.is_empty() {
Ok(true)
} else {
ERC20::approveCall::abi_decode_returns_validate(result)
.map_err(Into::into)
}
},
)
.await,
"pre-sign approval simulation",
)?;
match &simulation.value {
VerifiedSimulation::Succeeded(true) => {}
VerifiedSimulation::Succeeded(false) => {
anyhow::bail!("ERC-20 approve returned false for token {token}")
}
VerifiedSimulation::Denied => {
anyhow::bail!("ERC-20 approve simulation reverted for token {token}")
}
}
Ok(vec![
verification_decision(&allowance, Some(block), Some(block)),
verification_decision(&simulation, Some(block), Some(block)),
])
}
}
}
async fn verify_swap_anchors_before_sign(
&self,
anchors: &SwapQuoteAnchors,
) -> anyhow::Result<Vec<ExecutionVerificationDecision>> {
let checkpoint = required_verification(View on GitHub (pinned to 18893faf8b)
Solutions
- Check the token's implementation: paused, custom approve logic, or blacklist conditions
- Verify token state (owner balance, current allowance, pause status) before approving
- Use a standard, audited token or upgrade the token contract if you control it
- Try approving a smaller/standard amount (e.g. type(uint256).max) if the token imposes limits
Example fix
// before let ok: bool = token.approve(spender, amount).call().await?; // after let paused = token.paused().call().await?; anyhow::ensure!(!paused, "token paused; approve would return false"); let ok: bool = token.approve(spender, amount).call().await?; anyhow::ensure!(ok, "approve returned false for token");
Defensive patterns
Strategy: validation
Validate before calling
let paused: bool = token.paused().call().await?; anyhow::ensure!(!paused, "token paused; approve would return false"); let owner_balance: U256 = token.balance_of(owner).call().await?; anyhow::ensure!(owner_balance > U256::zero(), "owner has no token balance");
Type guard
fn approve_will_apply(ret: &VerifiedSimulation) -> bool {
matches!(ret, VerifiedSimulation::Succeeded(true))
} Prevention
- Audit non-standard tokens for approve returning false or custom conditions
- Check token pause/blacklist status before approval flows
- Keep pre-sign simulations enabled for every approval
- Prefer standard audited ERC-20 implementations
When it happens
Trigger: Pre-sign approval simulation of approve(spender, amount) returns VerifiedSimulation::Succeeded(false) — the call executes without reverting but the token contract returns false, indicating the approval did not apply.
Common situations: Non-standard ERC-20 tokens with balance/allowance conditions in approve; paused token contracts that return false instead of reverting; approving on a fork/simulation backend with stale token state; amount exceeding some token-specific cap.
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
- ERC-20 approve simulation reverted for token {token}
- Router allowance {} is below the swap amount {} for input to
- Input token {} balance {} is below the swap amount {}
- Router allowance after transaction {} does not equal the req
- Token {} reports unexpected decimals
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/728c2bfd5c821256.
Report an issue: GitHub.