nautechsystems/nautilus_trader · error · anyhow::Error
Persisted swap intent has no pool address
Error message
Persisted swap intent has no pool address
What it means
When rebuilding a SwapPlan from a persisted execution intent, the pool_address column is required to parse the Uniswap V3 pool contract address. If the intent row has no pool_address (NULL/missing), restore_swap_plan cannot reconstruct the swap target and fails. The library throws this because a swap cannot be safely re-executed without knowing the exact pool it was prepared against.
Solutions
- Let the stale intent be marked recoverable/resolved (or delete the row) so reconciliation skips it; it cannot be rebuilt without the pool address.
- Backfill pool_address on the intent row from the original trade records if the swap must still be reconciled.
- Check the schema_version of the row; rows from older schemas should be migrated or discarded per the adapter's migration path.
- Avoid hand-editing the durable store; always persist intents through the adapter so required fields are set.
Defensive patterns
Strategy: validation
Validate before calling
if intent.pool_address.is_none() {
eprintln!("intent {} lacks pool_address; it cannot be restored - resolve/clear it before reconnect", intent.id);
} Type guard
fn has_pool_address(intent: &ExecutionIntentRow) -> bool {
intent.pool_address.as_deref().map(|s| !s.is_empty()).unwrap_or(false)
} Try / catch
// Wrap reconciliation per-intent and treat missing-column rows as unrecoverable
match restore_swap_plan(&intent) {
Err(e) if e.to_string().contains("no pool address") => mark_intent_recoverable(&intent),
Err(e) => return Err(e),
Ok(plan) => drive(plan),
} Prevention
- Never hand-edit or partially insert rows in the execution intent table
- Run schema migrations before starting the adapter against an old database
- Confirm intent.schema_version matches the adapter's EXECUTION_SCHEMA_VERSION
- Persist intents only through the adapter API
When it happens
Trigger: reconcile_unresolved_execution (via connect) or build_execution_verification_migration loads an active ExecutionIntentRow whose intent.pool_address is None and calls restore_swap_plan. Occurs with rows written by older schema versions, partially-written rows, or manual database edits.
Common situations: Upgrading the adapter from a version that did not persist pool_address while old intent rows remain in the durable store; an interrupted/partial insert; cleaning or migrating the DB by hand and dropping the column value.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Persisted swap intent has no input amount
- Persisted pool does not match restored pool
- Restored pool has no fee
- Cannot persist position event with no position_id
- Contract address should be set in logs
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/be1ffd3bec1243d4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/adapters/blockchain/src/execution/client.rs:1067
anyhow::ensure!(
intent.trader_id.as_deref() == Some(order.trader_id().as_str()),
"Persisted swap trader does not match restored order"
);
anyhow::ensure!(
intent.strategy_id.as_deref() == Some(order.strategy_id().as_str()),
"Persisted swap strategy does not match restored order"
);
anyhow::ensure!(
intent.account_id.as_deref() == Some(self.core.account_id.as_str()),
"Persisted swap account does not match execution client account"
);
let pool = self.resolve_pool(&instrument_id)?;
let pool_address = Address::from_str(
intent
.pool_address
.as_deref()
.ok_or_else(|| anyhow::anyhow!("Persisted swap intent has no pool address"))?,
)?;
anyhow::ensure!(
pool.address == pool_address,
"Persisted pool {pool_address} does not match restored pool {}",
pool.address
);
let amount_in = U256::from_str(
intent
.amount_in
.as_deref()
.ok_or_else(|| anyhow::anyhow!("Persisted swap intent has no input amount"))?,
)?;
let fee = U24::try_from(
pool.fee
.ok_or_else(|| anyhow::anyhow!("Restored pool {instrument_id} has no fee"))?,
)?;
let quote_token = pool.get_quote_token();
let quote_currency = Currency::new_checked(View on GitHub (pinned to 18893faf8b)