nautechsystems/nautilus_trader · error

Lighter API secret must be a 40-byte hex private key

Error message

Lighter API secret must be a 40-byte hex private key

What it means

The Lighter private key decoder requires the API secret to decode from hex into exactly 40 bytes (SCALAR_BYTES). The value may optionally have a 0x/0X prefix; after stripping, `hex::decode` must succeed (else a different 'valid hex' error) and the byte length must equal 40. This error means the hex decoded fine but was not 40 bytes.

Source

Thrown at crates/adapters/lighter/src/common/credential.rs:327

        .trim()
        .parse::<u8>()
        .with_context(|| format!("{env_var} must be an API key index in 0..=254"))?;
    ensure_api_key_index(index)
}

fn ensure_api_key_index(value: u8) -> anyhow::Result<u8> {
    anyhow::ensure!(value <= 254, "Lighter API key index must be in 0..=254");
    Ok(value)
}

fn decode_private_key_hex(value: &str) -> anyhow::Result<Vec<u8>> {
    let value = value.trim();
    let hex = value
        .strip_prefix("0x")
        .or_else(|| value.strip_prefix("0X"))
        .unwrap_or(value);
    let bytes = hex::decode(hex).context("Lighter API secret must be valid hex")?;
    anyhow::ensure!(
        bytes.len() == SCALAR_BYTES,
        "Lighter API secret must be a 40-byte hex private key"
    );
    Ok(bytes)
}

#[cfg(test)]
mod tests {
    use rstest::rstest;

    use super::*;

    const PRIVATE_KEY_HEX: &str =
        "0b8e0f63c24d8baacd9d29ad4e9a4b73c4a8d2bb8b16dc4fa9d7c2e1d3a8b1f0e8d3a4c5b6e7f001";

    #[rstest]
    fn test_credential_env_vars_mainnet() {
        assert_eq!(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the API secret is the full 40-byte (80 hex character) private key from your Lighter account.
  2. Re-copy the key carefully, without truncation, optionally with a 0x prefix.
  3. Check `bytes.len()` from the hex string: 80 hex chars (excluding 0x) is required.
  4. If you have a different-length key, obtain the correct Lighter API secret rather than padding the key.

Example fix

// before
LIGHTER_API_SECRET=abc123  // decodes to 3 bytes
// after
LIGHTER_API_SECRET=0x<80 hex characters representing 40 bytes>
Defensive patterns

Strategy: validation

Validate before calling

let hex_str = secret.trim().trim_start_matches("0x").trim_start_matches("0X");
assert_eq!(hex_str.len(), 80, "Lighter API secret must be 80 hex chars (40 bytes)");

Type guard

fn is_40_byte_hex(s: &str) -> bool {
    let h = s.trim().trim_start_matches("0x").trim_start_matches("0X");
    h.len() == 80 && h.chars().all(|c| c.is_ascii_hexdigit())
}

Try / catch

match decode_private_key_hex(&secret) {
    Ok(key) => use_key(key),
    Err(e) => eprintln!("Invalid LIGHTER_API_SECRET: {e:#}; expected 80 hex chars"),
}

Prevention

When it happens

Trigger: Setting the Lighter API secret env var to a hex string that decodes to fewer or more than 40 bytes — e.g. a truncated key, a 32-byte key from another system, or an ed25519 key of unexpected length.

Common situations: Copying an incomplete key (clipboard truncation); using an Ethereum 32-byte private key instead of the Lighter 40-byte API secret; including stray whitespace is handled by trim but extra characters change length.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/49f3c89ff6a9f432. Report an issue: GitHub.