nautechsystems/nautilus_trader · error · anyhow::Error

Persisted swap intent has no input amount

Error message

Persisted swap intent has no input amount

What it means

restore_swap_plan parses the persisted amount_in (as a U256 decimal string) to reconstruct the swap's input amount. If the intent row has no amount_in value, the swap plan cannot be rebuilt and reconciliation fails. The library requires this field because re-driving the swap needs the exact input amount that was originally prepared.

Solutions

  1. Mark the intent recoverable or remove it so reconciliation skips it; the plan cannot be restored without the amount.
  2. Backfill amount_in on the row from original trade/swap records if reconciliation of that swap is required.
  3. Confirm intent.schema_version matches EXECUTION_SCHEMA_VERSION and run any required migration before reuse.
  4. Always create intents through the adapter so all required columns are populated atomically.
Defensive patterns

Strategy: validation

Validate before calling

if intent.amount_in.is_none() {
    eprintln!("intent {} lacks amount_in; swap cannot be rebuilt - backfill or mark recoverable", intent.id);
}

Type guard

fn has_amount_in(intent: &ExecutionIntentRow) -> bool {
    intent.amount_in.as_deref().map(|s| !s.is_empty()).unwrap_or(false)
}

Try / catch

// Treat a missing amount as unrecoverable and quarantine the row
match restore_swap_plan(&intent) {
    Err(e) if e.to_string().contains("no input amount") => mark_intent_recoverable(&intent),
    Err(e) => return Err(e),
    Ok(plan) => drive(plan),
}

Prevention

When it happens

Trigger: reconcile_unresolved_execution (from connect) or build_execution_verification_migration encounters an active ExecutionIntentRow with intent.amount_in = None when calling restore_swap_plan. Occurs with rows from older schema versions, partial writes, or manual DB manipulation.

Common situations: Adapter upgrade from a schema that did not persist amount_in while old rows remain; an interrupted insert when the intent was created; manual database cleanup dropping the value; running a mismatched adapter version against an old database.

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/d5f1be825f654eea. Report an issue: GitHub.

Appendix: source

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

        );

        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(
            &quote_token.symbol,
            quote_token.decimals,
            0,
            &quote_token.name,
            CurrencyType::Crypto,
        )?;
        let (token_in, token_out) = swap_token_pair(
            order.order_side(),
            pool.get_base_token().address,
            quote_token.address,
        )?;

View on GitHub (pinned to 18893faf8b)