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
- Inspect {e} for the SQLSTATE: 23505 unique violation, 23503 foreign key violation, 23514 check violation, 23502 not null
- Complete or release the existing active intent for that signer before reserving again
- Reuse the original client_order_id on retries so the conflict is recognized as the same order, not a duplicate
- 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
- Use one wallet per executor process so the active-signer unique index never conflicts
- Derive client_order_id deterministically so retries collide with the original reservation instead of creating duplicates
- Register chains in the cache before enabling execution on them
- Validate swap intents carry all order metadata before reserving
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
- Failed to start execution intent reservation: {e}
- Failed to record prepared execution intent: {e}
- Failed to commit execution intent reservation: {e}
- Execution intent {intent_id} was not found
- Invalid execution transition for intent {intent_id}: {curren
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/057df0b81b7c1c7d.
Report an issue: GitHub.