nautechsystems/nautilus_trader · error
signature_hex called before sign
Error message
signature_hex called before sign
What it means
`SignedAction::signature_hex` formats the ECDSA/EIP-712 signature as a `0x`-prefixed hex string, but the signature bytes are only populated by calling `sign` first. The struct stores `signature: Option<[u8; 65]>`, and reading it before signing panics. This is a documented API-ordering requirement (the doc comment states the panic condition).
Source
Thrown at crates/adapters/derive/src/signing/eip712.rs:235
signer
.sign_hash_sync(&typed_data_hash)
.map_err(|e| TypedDataError::SigningFailed {
message: e.to_string(),
})?;
let bytes = signature.as_bytes();
self.signature = Some(bytes);
Ok(bytes)
}
/// Returns the signature as a `0x`-prefixed 130-character hex string.
/// Panics if [`SignedAction::sign`] has not yet been called.
///
/// # Panics
///
/// Panics if [`SignedAction::sign`] has not been called.
#[must_use]
pub fn signature_hex(&self) -> String {
let bytes = self.signature.expect("signature_hex called before sign");
format!("0x{}", alloy_primitives::hex::encode(bytes))
}
/// Returns the signed action's subaccount id.
#[must_use]
pub const fn subaccount_id(&self) -> u64 {
self.ctx.subaccount_id
}
/// Returns the signed action's nonce.
#[must_use]
pub const fn nonce(&self) -> u64 {
self.ctx.nonce
}
/// Returns the signed action's session-key signer address.
#[must_use]
pub const fn signer_address(&self) -> Address {View on GitHub (pinned to 18893faf8b)
Solutions
- Always call `sign(&private_key)` on the SignedAction before `signature_hex()`.
- Refactor to a typestate or a `sign` that returns a `SignedActionWithSignature` so unsigned actions cannot expose a signature.
- If only the action payload is needed (hashing/serialization), use methods that don't require the signature.
- Add an assertion or debug check before use to catch the ordering mistake early.
Example fix
// before
let action = SignedAction { action, signature: None, .. };
let sig = action.signature_hex(); // panics
// after
let mut action = SignedAction { action, signature: None, .. };
action.sign(&private_key)?;
let sig = action.signature_hex(); Defensive patterns
Strategy: validation
Validate before calling
debug_assert!(action.signature.is_some(), "call sign() before signature_hex()");
if action.signature.is_none() {
return Err(/* signature not yet computed */);
} Prevention
- Always call sign(&private_key) before reading the signature
- Order code so sign happens immediately after action construction
- Consider a typestate API that separates SignedAction from SignedActionWithSignature
When it happens
Trigger: Building a `SignedAction` (e.g. via the action-construction helpers) and calling `signature_hex()` before invoking `sign(private_key)`; also when `sign` fails silently or is skipped on a code path (e.g. early return, conditional signing).
Common situations: Developers wiring up Derive authentication for the first time forget the sign step; test harnesses constructing actions for serialization checks call signature_hex on unsigned actions; refactors move the sign call after logging code that reads the signature.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- InstrumentState channel requires kind and currency parameter
- Time went backwards
- in-flight mutex poisoned
- wallet balance mutex poisoned
- instrument update lock poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/fd8102976d18a9e8.
Report an issue: GitHub.