linera-io/linera-protocol · error · async_graphql::Error

invalid {label} hex: {e}

Error message

invalid {label} hex: {e}

What it means

decode_hex is the shared helper the EVM bridge service uses to hex-decode GraphQL arguments (with optional 0x prefix) for decode_hex_array and process_deposit. It fails whenever hex::decode rejects the input: a non-hex character, an odd number of hex digits, or stray characters such as whitespace, quotes, or underscores. The message names which label (argument) failed and echoes the hex::decode cause.

Source

Thrown at linera-bridge/contracts/evm-bridge/src/service.rs:72

    }

    async fn handle_query(&self, request: Request) -> Response {
        let schema = Schema::build(
            self.clone(),
            MutationRoot {
                runtime: self.runtime.clone(),
            },
            EmptySubscription,
        )
        .finish();
        schema.execute(request).await
    }
}

/// Decodes a hex string (optional `0x` prefix) into bytes.
fn decode_hex(label: &str, s: &str) -> async_graphql::Result<Vec<u8>> {
    hex::decode(s.strip_prefix("0x").unwrap_or(s))
        .map_err(|e| async_graphql::Error::new(format!("invalid {label} hex: {e}")))
}

/// Decodes a hex string into a fixed-size byte array.
fn decode_hex_array<const N: usize>(label: &str, s: &str) -> async_graphql::Result<[u8; N]> {
    decode_hex(label, s)?.as_slice().try_into().map_err(|_| {
        async_graphql::Error::new(format!(
            "{label} must be exactly {N} bytes ({} hex chars)",
            N * 2
        ))
    })
}

/// GraphQL mutation root: schedules bridge operations submitted by a client (the
/// demo UI, an admin tool, or a custom relayer). The application bytecode — and
/// thus this interface — cannot change after deployment, so every
/// [`BridgeOperation`] is exposed; on-chain authorization still gates each.
/// Byte-array arguments are hex-encoded strings (with or without `0x`).
pub struct MutationRoot {

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Send only even-length hex digits [0-9a-f] (case-insensitive) with at most one leading 0x prefix; re-copy the value from its source (transaction receipt, event log).
  2. Strip whitespace/newlines and any duplicate 0x prefix before sending; check for shell quoting issues ("$…" expansion, smart quotes).
  3. If the value is a decimal or other encoding, convert it to hex first (e.g. `cast --to-hex`, `toHex`), keeping the required byte length in mind.

Example fix

// before
process_deposit(depositId: "0x12g4")  // 'g' is not a hex digit -> invalid depositId hex

// after
process_deposit(depositId: "0x12f4")  // even-length [0-9a-f] digits only
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_hex_arg(s: &str) -> bool {
    let body = s.strip_prefix("0x").unwrap_or(s);
    !body.is_empty() && body.len() % 2 == 0 && body.bytes().all(|b| b.is_ascii_hexdigit())
}

Type guard

fn is_valid_hex(s: &str) -> bool {
    let body = s.strip_prefix("0x").unwrap_or(s).trim();
    !body.is_empty() && body.len() % 2 == 0 && body.bytes().all(|b| b.is_ascii_hexdigit())
}

Prevention

When it happens

Trigger: Sending a GraphQL query/mutation to the evm-bridge service (e.g. process_deposit, or any arg decoded through decode_hex_array) where the argument contains characters outside [0-9a-fA-F] (after an optional 0x prefix), has odd length, or includes whitespace/newlines/quotes from shell or JSON copy-paste.

Common situations: Pasting an ENS name, decimal amount, or base58 value where a hex hash is expected; a truncated or double-prefixed hash ("0x0x…"); values copied from logs that include quotes or trailing whitespace; sending the same string to both keccak-decoded and hex-decoded fields and only one being valid hex.

Related errors


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