linera-io/linera-protocol · warning
invalid hex
Error message
invalid hex
What it means
The is_deposit_processed GraphQL query handler hex-decodes the submitted hash string and expects success. The panic means the query argument was not valid hex — odd-length string, non-hex characters, or a 0x-prefixed string with junk after the prefix (the optional 0x is stripped, everything else must decode). A panic in a query handler surfaces as a GraphQL error response rather than a boolean result.
Source
Thrown at linera-bridge/contracts/evm-bridge/src/service.rs:195
/// The ERC-20 token address on the source EVM chain (hex-encoded).
async fn token_address(&self) -> String {
let params: BridgeParameters = self.runtime.application_parameters();
format!("0x{}", hex::encode(params.token_address))
}
/// The configured EVM JSON-RPC endpoint, or empty if finality verification
/// is disabled.
async fn rpc_endpoint(&self) -> String {
self.state.rpc_endpoint.get().clone()
}
/// Whether a deposit with the given hash has been processed.
///
/// The hash is the hex-encoded keccak-256 of the deposit key
/// (see [`evm_bridge::DepositKey::hash`]).
async fn is_deposit_processed(&self, hash: String) -> bool {
let bytes: [u8; 32] = hex::decode(hash.strip_prefix("0x").unwrap_or(&hash))
.expect("invalid hex")
.try_into()
.expect("hash must be 32 bytes");
self.state
.processed_deposits
.contains(&bytes)
.await
.expect("failed to check processed deposits")
}
/// Verifies that the given EVM block hash is finalized on the source chain.
///
/// Makes the EVM JSON-RPC calls in the service runtime so that the contract
/// sees a single deterministic oracle response (the boolean result) instead
/// of multiple raw HTTP responses with non-deterministic headers.
async fn is_block_hash_finalized(&self, block_hash: String) -> bool {
let bytes: [u8; 32] = hex::decode(block_hash.strip_prefix("0x").unwrap_or(&block_hash))
.expect("invalid hex")
.try_into()View on GitHub (pinned to 6c226ddcb3)
Solutions
- Send the hash exactly as hex::encode produced it: 0x + 64 lowercase hex chars
- Validate client-side with a regex like /^0x[0-9a-fA-F]{64}$/ before issuing the query
- If you control the service, replace the expect with a GraphQL error (Result-based handler) for a friendlier message
- Check for stray whitespace, quotes, or truncation in the query template
Example fix
# before
query { isDepositProcessed(hash: "0x9f2b1c" ) } # truncated / odd-length hex -> GraphQL error
# after
# Always send keccak-256 output verbatim: 0x + 64 hex chars
query { isDepositProcessed(hash: "0x3fa85164e8b7c0c2d4a6b9e0f5d2c8a1b7e6d4f3c2a1908e7d6c5b4a3928f1e0d") } Defensive patterns
Strategy: validation
Validate before calling
// Validate before querying:
fn is_query_hash(s: &str) -> bool {
let h = s.strip_prefix("0x").unwrap_or(s);
h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())
}
assert!(is_query_hash(&hash));
let processed = service.is_deposit_processed(hash).await; Type guard
fn is_deposit_hash_query_arg(s: &str) -> bool {
let h = s.strip_prefix("0x").unwrap_or(s);
h.len() % 2 == 0 && h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())
} Try / catch
// GraphQL surfaces the panic as an errors[] entry; match on the message:
match resp.errors.first() {
Some(e) if e.message.contains("invalid hex") => fix_client_encoding(),
_ => use_result(resp.data),
} Prevention
- Always derive the hash via hex::encode of the 32-byte keccak output
- Reject user-pasted hashes that fail /^0x[0-9a-fA-F]{64}$/ at the input boundary
- Prefer GraphQL variables over string interpolation
When it happens
Trigger: Querying isDepositProcessed(hash: ...) with a base64 string, an odd-length hex string, a hash with a '0X' uppercase prefix containing invalid chars, or an empty string after prefix stripping; passing the raw DepositKey bytes instead of their hex encoding.
Common situations: Frontends that render hashes with mixed case or ellipsis truncation before querying; scripts piping arbitrary identifiers into the query; confusion between keccak hex output and multihash/base58 encodings.
Related errors
- hash must be 32 bytes
- invalid {label} hex: {e}
- RegisterFungibleBridge requires an authenticated signer
- SetRpcEndpoint requires an authenticated signer
- failed to query chain ID from RPC endpoint
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/c733d09dcdd7069b.
Report an issue: GitHub.