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

  1. Query both sides (ERC-20 decimals() and the wrapped-fungible parameters) and note the two values printed in the error.
  2. 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.
  3. Point the relayer config at the corrected application/contract addresses.
  4. 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

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


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/019a119adb78f1c8. Report an issue: GitHub.