nautechsystems/nautilus_trader · error

Restored transaction purpose is inconsistent

Error message

Restored transaction purpose is inconsistent

What it means

After a restored transaction finalizes, the client maps the (restored swap plan, purpose) pair to a trace purpose string ('swap_sell', 'swap_buy', 'wrap', 'approve') for finality verification. This bail fires when the combination matches none of the valid shapes — e.g. a Swap purpose with no restored plan, or a Wrap/Approve purpose with a plan — signalling corrupted or inconsistent persisted reconciliation state.

Source

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

            if intent.status == "finalized" {
                InclusionOutcome::Finalized(included)
            } else {
                InclusionOutcome::Reverted(included)
            }
        } else {
            executor.await_finality(&prepared).await?
        };

        match outcome {
            InclusionOutcome::Finalized(mut included) => {
                let trace_purpose = match (&plan, purpose) {
                    (Some(plan), TransactionPurpose::Swap) => match plan.order.order_side() {
                        OrderSide::Sell => "swap_sell",
                        OrderSide::Buy => "swap_buy",
                    },
                    (None, TransactionPurpose::Wrap) => "wrap",
                    (None, TransactionPurpose::Approve) => "approve",
                    _ => anyhow::bail!("Restored transaction purpose is inconsistent"),
                };
                included.finality.decisions.extend(
                    verify_finalized_transaction(
                        &included,
                        &intent,
                        nonce,
                        &prepared.raw_tx,
                        &executor,
                        trace_purpose,
                    )
                    .await?,
                );

                if let Some(plan) = plan {
                    let fill = validate_finalized_swap_fill(&plan, &included)?;
                    let wallet =
                        load_verified_wallet_after_fill(&plan, &included, &executor).await?;
                    if !finality_already_committed {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the execution database rows for the failing intent: confirm the purpose field and whether a swap plan row exists; they must agree (Swap ⇔ plan present, Wrap/Approve ⇔ no plan).
  2. Re-run or repair the swap plan persistence so restore_swap_plan() returns the plan for Swap-purposed intents.
  3. Check for a version mismatch between the code that wrote the intent and the code restoring it; upgrade/migrate the database schema to the current version.
  4. Restore the affected intent rows from a consistent backup if the state is corrupted.
Defensive patterns

Strategy: validation

Validate before calling

// Verify purpose/plan consistency before reconnecting
fn purpose_plan_consistent(purpose: &TransactionPurpose, plan: &Option<SwapPlan>) -> bool {
    match purpose {
        TransactionPurpose::Swap => plan.is_some(),
        _ => plan.is_none(),
    }
}

Type guard

fn restore_shape_is_valid(purpose: &TransactionPurpose, plan: &Option<SwapPlan>) -> bool {
    matches!((plan, purpose),
        (Some(_), TransactionPurpose::Swap)
        | (None, TransactionPurpose::Wrap)
        | (None, TransactionPurpose::Approve))
}

Try / catch

match client.connect().await {
    Err(e) if e.to_string().contains("purpose is inconsistent") => {
        log::error!("corrupt persisted reconciliation state: {e}");
        // halt connect; repair DB rows or restore from backup
    }
    result => result?,
}

Prevention

When it happens

Trigger: connect() → reconcile_unresolved_execution() on a finalized intent where the persisted purpose and the restored plan disagree: purpose == Swap but restore_swap_plan returned no plan, or purpose == Wrap/Approve while a swap plan exists for the intent.

Common situations: Database rows for the intent and its swap plan written by different versions of the software; a swap plan that failed to persist while the intent recorded Swap purpose; manual database edits or partial restores; restore logic returning None for a plan that should exist.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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