nautechsystems/nautilus_trader · error · anyhow::Error

Failed to encode params: {e}

Error message

Failed to encode params: {e}

What it means

sign_spot builds the URL-encoded POST parameter string (nonce plus request params) with serde_urlencoded. This error wraps serialization failure of the params map — e.g. a key/value pair that cannot be percent-encoded into the request body.

Source

Thrown at crates/adapters/kraken/src/common/credential.rs:181

    ///
    /// Note: "nonce + POST data" means the nonce value string is prepended
    /// to the URL-encoded POST body, e.g., "1234567890nonce=1234567890&param=value".
    pub fn sign_spot(
        &self,
        path: &str,
        nonce: u64,
        params: &HashMap<String, String>,
    ) -> anyhow::Result<(String, String)> {
        let secret = STANDARD
            .decode(&self.api_secret)
            .map_err(|e| anyhow::anyhow!("Failed to decode API secret: {e}"))?;

        let nonce_str = nonce.to_string();
        let mut post_data = format!("nonce={nonce_str}");

        if !params.is_empty() {
            let encoded = serde_urlencoded::to_string(params)
                .map_err(|e| anyhow::anyhow!("Failed to encode params: {e}"))?;
            post_data.push('&');
            post_data.push_str(&encoded);
        }

        let sha_input = format!("{nonce_str}{post_data}");
        let hash = digest::digest(&digest::SHA256, sha_input.as_bytes());
        let mut message = path.as_bytes().to_vec();
        message.extend_from_slice(hash.as_ref());
        let key = hmac::Key::new(hmac::HMAC_SHA512, &secret);
        let signature = hmac::sign(&key, &message);

        Ok((STANDARD.encode(signature.as_ref()), post_data))
    }

    /// Sign a JSON request for Kraken Spot API (used for CancelOrderBatch, AddOrderBatch).
    ///
    /// These endpoints use JSON body instead of form-encoded.
    /// Signature: HMAC-SHA512(path + SHA256(nonce + json_body))

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Sanitize params to valid UTF-8 strings and strip control characters before signing
  2. Log/inspect the params map in the wrapped {e} to find the offending entry
  3. Ensure values come from validated sources (ASCII symbol names, plain numerics)
  4. Upgrade serde_urlencoded if a serialization bug is suspected

Example fix

// before
params.insert("pair", weird_user_input.to_string());
let sig = creds.sign_spot(path, nonce, &params)?;
// after
let pair: String = weird_user_input.chars().filter(|c| c.is_ascii_graphic()).collect();
params.insert("pair", pair);
let sig = creds.sign_spot(path, nonce, &params)?;
Defensive patterns

Strategy: validation

Validate before calling

for (k, v) in &params {
    assert!(k.chars().all(|c| c.is_ascii_graphic() || c == ' '));
    assert!(v.chars().all(|c| c.is_ascii_graphic() || c == ' '));
}

Try / catch

match creds.sign_spot(path, nonce, &params) {
    Ok(s) => s,
    Err(e) if e.to_string().contains("encode params") => {
        eprintln!("unencodable param in request: {e}");
        return Err(SignError::BadParams);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling sign_spot with params containing characters/values serde_urlencoded cannot serialize (non-UTF-8 strings, control characters introduced via env/config) — serde_urlencoded::to_string returns an Err which is wrapped.

Common situations: Non-UTF-8 or control characters in order parameters read from config/environment; params built from user input with invalid encoding; extremely unusual symbol strings.

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/3617c6f6cd17a063. Report an issue: GitHub.