linera-io/linera-protocol · critical
token decimals mismatch between Linera wrapped-fungible ({li
Error message
token decimals mismatch between Linera wrapped-fungible ({linera_decimals}) and EVM ERC-20 ({evm_decimals}); the bridge would silently misinterpret amounts. Reconfigure both sides with matching `decimals` before starting the relayer. What it means
A hard startup guard in the relayer's serve loop: the Linera wrapped-fungible application and the EVM ERC-20 report different `decimals` values. Because the bridge relays raw integer amounts, a mismatch (e.g. 6 vs 18) would silently scale deposits and withdrawals by orders of magnitude, so the relayer aborts instead of serving.
Source
Thrown at linera-bridge/src/relay/mod.rs:355
.await
.context("committee catch-up failed")?;
let bridge_app_id: ApplicationId = linera_bridge_address
.parse()
.context("invalid --linera-bridge-address")?;
let fungible_app_id: ApplicationId = linera_fungible_address
.parse()
.context("invalid --linera-fungible-address")?;
// ── Verify the configured Linera and EVM contracts agree on token decimals ──
let linera_decimals = monitor::query_wrapped_fungible_decimals(&chain_client, fungible_app_id)
.await
.context("failed to query wrapped-fungible decimals from Linera")?;
let evm_decimals = evm_client
.token_decimals()
.await
.context("failed to query ERC-20 decimals from EVM")?;
anyhow::ensure!(
linera_decimals == evm_decimals,
"token decimals mismatch between Linera wrapped-fungible ({linera_decimals}) and \
EVM ERC-20 ({evm_decimals}); the bridge would silently misinterpret amounts. \
Reconfigure both sides with matching `decimals` before starting the relayer."
);
tracing::info!(
decimals = linera_decimals,
"Verified Linera ↔ EVM token decimals match"
);
let (op_tx, mut op_rx) = mpsc::channel::<linera::ChainOperation>(16);
let linera_client = Arc::new(linera::LineraClient::new(
chain_client.clone(),
op_tx,
bridge_app_id,
fungible_app_id,
));
View on GitHub (pinned to 6c226ddcb3)
Solutions
- Query both sides (ERC-20 decimals() and the wrapped-fungible parameters) and note the two values printed in the error.
- Re-register/create the Linera wrapped-fungible application with decimals equal to the ERC-20's value, or redeploy the ERC-20 with the Linera side's value — pick one source of truth.
- Point the relayer config at the corrected application/contract addresses.
- Restart the relayer and confirm it passes the guard before transferring funds.
Example fix
// before: wrapped-fungible registered with decimals = 6, ERC-20 deployed with 18
// relayer aborts: "token decimals mismatch ... (6) ... (18)"
// after: align both sides, then restart
// Linera side: create wrapped-fungible with parameters { decimals: 18, ... }
// EVM side: ERC-20 constructor decimals = 18
// verify: erc20.decimals() == wrapped_fungible.decimals() Defensive patterns
Strategy: validation
Validate before calling
// Deployment-time preflight: compare decimals before any transfers.
let evm_decimals: u8 = erc20.decimals().call().await?._0;
ensure!(
evm_decimals == wrapped_fungible_params.decimals,
"decimals mismatch: ERC-20 {evm_decimals} vs wrapped-fungible {}",
wrapped_fungible_params.decimals
); Prevention
- Treat the ERC-20's decimals() as the source of truth and configure the wrapped-fungible app to match at registration time.
- Add the decimals equality assertion to CI/deployment scripts for every environment.
- Never bridge funds in an environment that has ever run with mismatched decimals.
When it happens
Trigger: serve_loop queries wrapped-fungible decimals on Linera and token_decimals() on the ERC-20; any deployment where the fungible application was registered with a decimals parameter different from the ERC-20 constructor's (typical: USDC-style 6 vs default 18).
Common situations: Bridging an existing 6-decimal stablecoin while the wrapped-fungible app was created with default 18 decimals; testnets redeploying one side with new parameters; changing decimals on one contract and not the other during iteration.
Related errors
- invalid tx hash: {}
- no admin chain (Root(0)) in genesis config
- transaction receipt not found for {tx_hash}
- receipt missing block_hash (pending tx?)
- receipt missing transaction_index
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/019a119adb78f1c8.
Report an issue: GitHub.