nautechsystems/nautilus_trader · error · anyhow::Error

Failed to reserve execution intent for signer {} on chain {}

Error message

Failed to reserve execution intent for signer {} on chain {}: {e}

What it means

Thrown when the INSERT INTO execution_intent inside reserve_execution_intent fails. The v2 unique partial indexes make ownership conflicts the common cause: another active intent for the same (chain_id, wallet_address) via execution_intent_active_signer_key, or the same client_order_id via execution_intent_client_order_key. It also fires on FK violation when chain_id is absent from the chain table, or CHECK violation when purpose 'swap' lacks client_order_id/trader_id/strategy_id/account_id/instrument_id/pool_address/amount_in.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:3361

        .bind(EXECUTION_SCHEMA_VERSION)
        .bind(chain_id)
        .bind(&intent.wallet_address)
        .bind(&intent.purpose)
        .bind(&intent.client_order_id)
        .bind(&intent.trader_id)
        .bind(&intent.strategy_id)
        .bind(&intent.account_id)
        .bind(&intent.instrument_id)
        .bind(&intent.pool_address)
        .bind(&intent.transaction_to)
        .bind(&intent.transaction_input)
        .bind(&intent.transaction_value)
        .bind(&intent.amount_in)
        .bind(created_block)
        .fetch_one(&mut *transaction)
        .await
        .map_err(|e| {
            anyhow::anyhow!(
                "Failed to reserve execution intent for signer {} on chain {}: {e}",
                intent.wallet_address,
                intent.chain_id
            )
        })?;

        sqlx::query(
            "
            INSERT INTO execution_transaction_transition (
                intent_id, transition_key, to_status, block_number
            ) VALUES ($1, 'prepared', 'prepared', $2)
            ",
        )
        .bind(row.id)
        .bind(created_block)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to record prepared execution intent: {e}"))?;

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Inspect {e} for the SQLSTATE: 23505 unique violation, 23503 foreign key violation, 23514 check violation, 23502 not null
  2. Complete or release the existing active intent for that signer before reserving again
  3. Reuse the original client_order_id on retries so the conflict is recognized as the same order, not a duplicate
  4. Register the chain row and populate every swap order field (trader/strategy/account/instrument/pool/amount_in) before reserving

Example fix

// before: reserve blindly on retry
let row = db.reserve_execution_intent(&intent).await?;

// after: resume when the signer slot is already owned
let active = sqlx::query_scalar::<_, i64>(
    "SELECT id FROM execution_intent WHERE chain_id = $1 AND wallet_address = $2 AND active",
)
.bind(chain_id)
.bind(&intent.wallet_address)
.fetch_optional(&pool)
.await?;
let row = match active {
    Some(_) => resume_existing_intent(&db, &intent).await?,
    None => db.reserve_execution_intent(&intent).await?,
};
Defensive patterns

Strategy: validation

Validate before calling

// Check signer-slot and client-order ownership before reserving
let conflict: Option<i64> = sqlx::query_scalar(
    "SELECT id FROM execution_intent WHERE chain_id = $1 AND wallet_address = $2 AND active",
)
.bind(chain_id)
.bind(wallet_address)
.fetch_optional(&pool)
.await?;
let chain_registered: bool = sqlx::query_scalar(
    "SELECT EXISTS (SELECT 1 FROM chain WHERE chain_id = $1)",
)
.bind(chain_id)
.fetch_one(&pool)
.await?;
if conflict.is_none() && chain_registered { /* safe to reserve */ }

Type guard

fn swap_intent_complete(intent: &ExecutionIntentInsert) -> bool {
    intent.purpose != "swap"
        || intent.client_order_id.is_some()
            && intent.trader_id.is_some()
            && intent.strategy_id.is_some()
            && intent.account_id.is_some()
            && intent.instrument_id.is_some()
            && intent.pool_address.is_some()
            && intent.amount_in.is_some()
}

Try / catch

match db.reserve_execution_intent(&intent).await {
    Err(e) => {
        let msg = e.to_string();
        if msg.contains("duplicate key value") && msg.contains("execution_intent_active_signer_key") {
            // signer slot owned: load and resume the existing active intent
        } else if msg.contains("duplicate key value") && msg.contains("execution_intent_client_order_key") {
            // same client order already reserved: treat as duplicate submission, resume it
        } else if msg.contains("violates foreign key constraint") {
            // register the chain row first
        } else {
            return Err(e);
        }
    }
    Ok(row) => { /* proceed to nonce assignment */ }
}

Prevention

When it happens

Trigger: A second reserve for the same signer while its first intent is still active; retrying a submission with a new client_order_id instead of resuming the original; a swap intent built without full order metadata; a chain_id never registered in the chain table.

Common situations: Two executors configured with the same wallet; crash-recovery code that re-reserves rather than resuming; test chains not seeded into the cache.

Related errors


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