nautechsystems/nautilus_trader · error · anyhow::Error

Persisted swap trader does not match restored order

Error message

Persisted swap trader does not match restored order

What it means

During swap-plan restoration after a restart, the blockchain execution client verifies that the trader_id persisted with the execution intent row matches the trader_id of the restored order from the cache. A mismatch means the durable intent and the in-memory order came from different trader identities, so reconciliation is aborted to avoid attributing a persisted swap to the wrong trader. This is a safety invariant: replaying an intent against an order with a different owner could submit trades under incorrect identity.

Solutions

  1. Make sure the trader_id configured for this node matches the trader_id of the orders and intents in the durable store (revert the config change or use the original trader_id).
  2. Point the cache database at the durable store belonging to this trader (separate DB file/namespace per trader_id) instead of a shared one.
  3. If the intent row is corrupt or stale, clear/resolve the active execution intent for this wallet/chain so reconciliation skips it.
  4. Ensure orders are restored from the same cache snapshot that produced the intents; do not mix cache snapshots across traders.

Example fix

// before (config changed between runs)
trader_id = "TRADER-002"
// after (match the trader that persisted the intents)
trader_id = "TRADER-001"
Defensive patterns

Strategy: validation

Validate before calling

// Before starting reconciliation, confirm trader identity matches the persisted intents
let intent_trader: Option<&str> = intent.trader_id.as_deref();
let order_trader = order.trader_id().as_str();
if intent_trader != Some(order_trader) {
    eprintln!("intent trader {intent_trader:?} != order trader {order_trader}; fix trader_id config before connecting");
}

Type guard

fn trader_matches(intent: &ExecutionIntentRow, order: &OrderAny) -> bool {
    intent.trader_id.as_deref() == Some(order.trader_id().as_str())
}

Prevention

When it happens

Trigger: restore_swap_plan (called from reconcile_unresolved_execution or build_execution_verification_migration) loads an ExecutionIntentRow whose intent.trader_id is None or differs from order.trader_id() for the order identified by intent.client_order_id. Happens when the cache is restored from a different instance/trader_id than the one that persisted the intent (e.g. trader_id changed in config between runs), or the intent row was written by another trader sharing the same database.

Common situations: Changing the trader_id in the node configuration after intents were persisted; pointing multiple trader instances at one shared durable store; restoring a cache snapshot from a different deployment; a NULL trader_id column on an old or hand-edited intent row.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/18721d225c480b12. Report an issue: GitHub.

Appendix: source

Thrown at crates/adapters/blockchain/src/execution/client.rs:1049

            .try_order_owned(&client_order_id)
            .with_context(|| {
                format!(
                    "Cannot reconcile swap intent {} because order {client_order_id} is not restored",
                    intent.id
                )
            })?;
        let instrument_id = InstrumentId::from_str(
            intent
                .instrument_id
                .as_deref()
                .ok_or_else(|| anyhow::anyhow!("Persisted swap intent has no instrument ID"))?,
        )?;
        anyhow::ensure!(
            order.instrument_id() == instrument_id,
            "Persisted swap instrument {instrument_id} does not match restored order instrument {}",
            order.instrument_id()
        );
        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"))?,

View on GitHub (pinned to 18893faf8b)