nautechsystems/nautilus_trader · error · anyhow::Error

Signer not initialized; connect the client first

Error message

Signer not initialized; connect the client first

What it means

transaction_executor (crates/adapters/blockchain/src/execution/client.rs:797) requires an initialized signer; self.signer is populated only during connect(), which loads the private key from config.signer_private_key_env, validates it as hex, and verifies the derived address matches the configured wallet address. disconnect() clears the signer, and connect() itself resets it to None if any post-signer step (execution reconciliation, wallet balance refresh) fails. The error therefore means a signing operation ran outside a fully established connection, and the root cause is usually an earlier connect failure or a missing call.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:797

            );
        }

        Ok(pool)
    }

    /// Builds the shared transaction executor from the connected client state.
    ///
    /// # Errors
    ///
    /// Returns an error if no durable store is configured or the signer is not initialized.
    fn transaction_executor(&self) -> anyhow::Result<TransactionExecutor> {
        let database = self.cache.database.clone().ok_or_else(|| {
            anyhow::anyhow!("No durable store configured; refusing to submit a transaction")
        })?;
        let signer = self
            .signer
            .clone()
            .ok_or_else(|| anyhow::anyhow!("Signer not initialized; connect the client first"))?;

        Ok(TransactionExecutor {
            http_rpc_client: self.http_rpc_client.clone(),
            database,
            signer,
            in_flight: Arc::clone(&self.in_flight),
            wallet_balance: Arc::clone(&self.wallet_balance),
            account_id: self.core.account_id,
            wallet_address: self.wallet_address,
            chain_id: self.chain.chain_id,
            max_fee_per_gas_wei: self.config.max_fee_per_gas_wei,
            base_fee_buffer_bps: self.config.base_fee_buffer_bps,
            gas_limit: self.config.gas_limit,
            gas_buffer_bps: self.config.gas_buffer_bps,
            receipt_timeout: receipt_timeout(self.transaction_limits.receipt_timeout_secs),
            receipt_max_polls: receipt_max_polls(self.transaction_limits.receipt_timeout_secs),
        })
    }

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Await client.connect() and check it returned Ok before issuing any transactional operation.
  2. If connect failed, read the original error it returned - signer-private-key-env not set, invalid hex key, wallet address mismatch, or reconciliation/balance failure are the usual causes.
  3. Set the env var named by config.signer_private_key_env to the hex private key whose address equals the configured wallet address.
  4. Do not submit after disconnect(); reconnect first.

Example fix

// before
let mut client = BlockchainExecutionClient::new(/* ... */);
client.wrap(amount_wei).await?; // error: signer not initialized

// after
let mut client = BlockchainExecutionClient::new(/* ... */);
client.connect().await?;      // loads signer from env, reconciles, refreshes balances
client.wrap(amount_wei).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: only submit on a fully connected client
anyhow::ensure!(
    client.is_connected(),
    "blockchain client not connected; call connect() before submitting"
);
// also confirm the signer env var exists before starting the session
std::env::var(&config.signer_private_key_env)
    .with_context(|| format!("set {} before connect", config.signer_private_key_env))?;

Type guard

fn is_signer_uninitialized(e: &anyhow::Error) -> bool {
    e.to_string().contains("Signer not initialized; connect the client first")
}

Try / catch

match client.wrap(amount_wei).await {
    Ok(hash) => hash,
    Err(e) if is_signer_uninitialized(&e) => {
        // lifecycle bug: reconnect, surfacing the original connect() error if any
        client.connect().await?;
        client.wrap(amount_wei).await
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling wrap/approve/submit_order before connect(); calling them after disconnect(); connect() failed at reconciliation or refresh_wallet_balances (both reset signer to None and return the underlying error) and the caller ignored the failure and proceeded.

Common situations: Fire-and-forget startup that never awaits connect(); the signer env var unset or invalid, or its derived address mismatching wallet_address, causing connect to bail earlier; a disconnect triggered mid-session (stop command) followed by a late order submission.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/0944dd8758ffc988. Report an issue: GitHub.