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

  1. Let the stale intent be marked recoverable/resolved (or delete the row) so reconciliation skips it; it cannot be rebuilt without the pool address.
  2. Backfill pool_address on the intent row from the original trade records if the swap must still be reconciled.
  3. Check the schema_version of the row; rows from older schemas should be migrated or discarded per the adapter's migration path.
  4. 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

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


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)