nautechsystems/nautilus_trader · error
Signed transaction creates a contract instead of calling a d
Error message
Signed transaction creates a contract instead of calling a destination
What it means
After decoding the EIP-1559 transaction, the library requires tx.to to be TxKind::Call (a call to an existing address). If the transaction's `to` is TxKind::Create, it is a contract-creation transaction, which this code path does not support — identity and RPC matching checks assume a destination address exists.
Source
Thrown at crates/adapters/blockchain/src/execution/transaction.rs:217
) -> anyhow::Result<DecodedSignedTransaction> {
let envelope = TxEnvelope::decode_2718_exact(raw_transaction).map_err(|_| {
anyhow::anyhow!("Persisted signed transaction is not a complete EIP-2718 envelope")
})?;
let TxEnvelope::Eip1559(signed) = envelope else {
anyhow::bail!("Persisted signed transaction is not EIP-1559");
};
anyhow::ensure!(
signed.signature().normalize_s().is_none(),
"Persisted transaction signature is not EIP-2 normalized"
);
let signer = signed
.signature()
.recover_address_from_prehash(&signed.signature_hash())
.context("failed to recover persisted transaction signer")?;
let hash = *signed.hash();
let tx = signed.tx();
let TxKind::Call(to) = tx.to else {
anyhow::bail!("Signed transaction creates a contract instead of calling a destination");
};
anyhow::ensure!(
tx.access_list.is_empty(),
"Signed transaction access list is not empty"
);
Ok(DecodedSignedTransaction {
hash,
signer,
chain_id: tx.chain_id,
nonce: tx.nonce,
to,
value: tx.value,
input: tx.input.clone(),
gas_limit: tx.gas_limit,
max_fee_per_gas: tx.max_fee_per_gas,
max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
})View on GitHub (pinned to 18893faf8b)
Solutions
- Set the `to` destination address on the transaction request before signing so the signed tx is TxKind::Call.
- Confirm you are not accidentally feeding a contract-deployment transaction into this call-oriented validation path.
- Trace where the transaction request is constructed and log the `to` field prior to signing to catch it being dropped/defaulted.
- If contract creation is intended, use a deployment flow instead of decode_signed_transaction/validate_signed_transaction.
Example fix
// before
let tx = TxRequest { input: calldata, ..Default::default() }; // to = None -> CREATE
// after
let tx = TxRequest { to: Address::from_str(contract)?, input: calldata, ..Default::default() }; Defensive patterns
Strategy: validation
Validate before calling
fn is_contract_call(tx: &TxEip1559) -> bool {
matches!(tx.to, TxKind::Call(_))
}
// reject requests with to = None before signing Type guard
fn destination(tx: &TxEip1559) -> Option<Address> {
match tx.to { TxKind::Call(addr) => Some(addr), TxKind::Create => None }
} Try / catch
match decode_signed_transaction(raw) {
Err(e) if e.to_string().contains("creates a contract") => /* abort: deployment tx sent through call path */,
Err(e) => return Err(e),
Ok(decoded) => decoded,
} Prevention
- Always set `to` explicitly when building transaction requests; never rely on defaults
- Assert the tx kind is Call in the request-builder before signing
- Separate contract-deployment flows from call flows so they never share the validation path
- Log the destination address at request-construction time
When it happens
Trigger: decode_signed_transaction encounters a signed EIP-1559 transaction whose `to` field is None/CREATE — i.e. the payload was built without a destination address, deploying bytecode instead of calling a contract.
Common situations: Building the transaction request without setting `to` (e.g. an empty or default-built TxRequest) which the signer serializes as contract creation; intentionally deploying a contract and then passing it through the adapter's call-validation path; a field-mapping bug dropping the destination address before signing.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Failed to load active execution intent: {e}
- Failed to start replacement transaction persistence: {e}
- Failed to lock active execution intent {intent_id}: {e}
- Active execution intent {intent_id} was not found
- Invalid execution transition for intent {intent_id}: {curren
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/687c14a6256ed3fe.
Report an issue: GitHub.