nautechsystems/nautilus_trader · error · anyhow::Error

Cannot create sign doc: {e}

Error message

Cannot create sign doc: {e}

What it means

TxBuilder::build_transaction wraps the cosmos-sdk SignDoc::new failure with this message. SignDoc construction serializes the tx body, auth info, chain id, and account number into the canonical bytes that will be signed; it fails if those values cannot be encoded consistently. The anyhow context preserves the underlying prost/encoding error.

Source

Thrown at crates/adapters/dydx/src/grpc/builder.rs:164

            let ext = TxExtension {
                selected_authenticators: auth_ids.to_vec(),
            };
            builder.non_critical_extension_option(ext.to_any());
        }

        let tx_body = builder.finish();

        let fee = fee.unwrap_or_else(|| {
            self.calculate_fee(None)
                .unwrap_or_else(|_| Self::default_fee())
        });

        let auth_info =
            SignerInfo::single_direct(Some(account.public_key()), account.sequence_number)
                .auth_info(fee);

        let sign_doc = SignDoc::new(&tx_body, &auth_info, &self.chain_id, account.account_number)
            .map_err(|e| anyhow::anyhow!("Cannot create sign doc: {e}"))?;

        account.sign(sign_doc)
    }

    /// Build and simulate a transaction to estimate gas.
    ///
    /// Returns the raw transaction bytes suitable for simulation.
    ///
    /// # Errors
    ///
    /// Returns an error if transaction building fails.
    pub fn build_for_simulation(
        &self,
        account: &Account,
        msgs: impl IntoIterator<Item = Any>,
    ) -> Result<Vec<u8>, anyhow::Error> {
        let tx_raw = self.build_transaction(account, msgs, None, None)?;
        tx_raw.to_bytes().map_err(|e| anyhow::anyhow!("{e}"))

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Log/print the inner `{e}` to see the underlying protobuf encoding error and fix the offending message in msgs.
  2. Verify the chain_id matches the target network (dydx-mainnet-1 vs dydx-dymension-testnet-3 etc.) and the account_number/sequence come from a fresh query_account.
  3. Reduce message size or split messages into multiple transactions if the tx body exceeds size limits.
  4. Check that prost/ibc-proto dependency versions align so the tx body and auth_info types serialize with the same schema.

Example fix

// before
let sign_doc = SignDoc::new(&tx_body, &auth_info, &self.chain_id, account.account_number)
    .map_err(|e| anyhow::anyhow!("Cannot create sign doc: {e}"))?;
// after: validate inputs before signing
if msgs.is_empty() { anyhow::bail!("No messages to include in tx"); }
let sign_doc = SignDoc::new(&tx_body, &auth_info, &self.chain_id, account.account_number)
    .map_err(|e| anyhow::anyhow!("Cannot create sign doc (chain_id={chain_id}): {e}"))?;
Defensive patterns

Strategy: try-catch

Validate before calling

if msgs.is_empty() { return Err(anyhow!("no messages provided")); }

Try / catch

match builder.build_transaction(&account, msgs, fee, memo) {
    Ok(tx) => tx,
    Err(e) if e.to_string().starts_with("Cannot create sign doc") => {
        // refresh account state and retry once
        let (num, seq) = client.query_address(addr).await?;
        builder.build_transaction(&refreshed_account, msgs, fee, memo)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling build_transaction (directly or via build_for_simulation) when SignDoc::new fails to serialize the tx body/auth_info for the given chain_id and account_number — typically malformed tx messages, oversized payloads, or a protobuf encoding failure.

Common situations: A message list containing a proto message whose serialization exceeds limits; mismatched or malformed chain-id string; account sequence/number values that break encoding after a node reset or mismatch between local account state and chain state.

Related errors


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