{"record":{"id":"019a119adb78f1c8","repo":"linera-io/linera-protocol","slug":"token-decimals-mismatch-between-linera-wrapped-fun","errorCode":null,"errorMessage":"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.","messagePattern":"token decimals mismatch between Linera wrapped-fungible \\((.+?)\\) and EVM ERC-20 \\((.+?)\\); the bridge would silently misinterpret amounts\\. Reconfigure both sides with matching `decimals` before starting the relayer\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"linera-bridge/src/relay/mod.rs","lineNumber":355,"sourceCode":"    .await\n    .context(\"committee catch-up failed\")?;\n\n    let bridge_app_id: ApplicationId = linera_bridge_address\n        .parse()\n        .context(\"invalid --linera-bridge-address\")?;\n    let fungible_app_id: ApplicationId = linera_fungible_address\n        .parse()\n        .context(\"invalid --linera-fungible-address\")?;\n\n    // ── Verify the configured Linera and EVM contracts agree on token decimals ──\n    let linera_decimals = monitor::query_wrapped_fungible_decimals(&chain_client, fungible_app_id)\n        .await\n        .context(\"failed to query wrapped-fungible decimals from Linera\")?;\n    let evm_decimals = evm_client\n        .token_decimals()\n        .await\n        .context(\"failed to query ERC-20 decimals from EVM\")?;\n    anyhow::ensure!(\n        linera_decimals == evm_decimals,\n        \"token decimals mismatch between Linera wrapped-fungible ({linera_decimals}) and \\\n         EVM ERC-20 ({evm_decimals}); the bridge would silently misinterpret amounts. \\\n         Reconfigure both sides with matching `decimals` before starting the relayer.\"\n    );\n    tracing::info!(\n        decimals = linera_decimals,\n        \"Verified Linera ↔ EVM token decimals match\"\n    );\n\n    let (op_tx, mut op_rx) = mpsc::channel::<linera::ChainOperation>(16);\n    let linera_client = Arc::new(linera::LineraClient::new(\n        chain_client.clone(),\n        op_tx,\n        bridge_app_id,\n        fungible_app_id,\n    ));\n","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/relay/mod.rs#L337-L373","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: wrapped-fungible registered with decimals = 6, ERC-20 deployed with 18\n// relayer aborts: \"token decimals mismatch ... (6) ... (18)\"\n\n// after: align both sides, then restart\n// Linera side: create wrapped-fungible with parameters { decimals: 18, ... }\n// EVM side: ERC-20 constructor decimals = 18\n// verify: erc20.decimals() == wrapped_fungible.decimals()","handlingStrategy":"validation","validationCode":"// Deployment-time preflight: compare decimals before any transfers.\nlet evm_decimals: u8 = erc20.decimals().call().await?._0;\nensure!(\n    evm_decimals == wrapped_fungible_params.decimals,\n    \"decimals mismatch: ERC-20 {evm_decimals} vs wrapped-fungible {}\",\n    wrapped_fungible_params.decimals\n);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["token","decimals","erc20","fungible","bridge"],"backgroundTag":"token-decimals-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}