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 (crates/adapters/blockchain/src/execution/client.rs:874) parses the persisted swap intent's amount_in column as U256; a NULL value aborts reconciliation. The input amount is required to reconstruct the SwapPlan (it feeds min_amount_out checks and emitted fill quantities) and swap intents always persist it, so NULL indicates incomplete durable state - a legacy row, torn write, or manual edit - rather than any valid record.

Source

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

        );

        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 = pool.get_base_token().address;
        let token_out = quote_token.address;

        Ok(SwapPlan {
            order,

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Inspect the intent row: SELECT id, status, amount_in FROM execution_intent WHERE id = <id>.
  2. Verify the on-chain swap outcome via the intent's transaction hash, then mark the intent resolved so restarts stop failing.
  3. Ensure the deployment writing intents is current so amount_in is always persisted.
  4. Backfill amount_in from on-chain swap event data if you need history intact.
Defensive patterns

Strategy: try-catch

Validate before calling

-- Pre-flight: active swap intents missing the input amount
SELECT id, status
FROM execution_intent
WHERE status IN ('prepared', 'signed', 'submitted')
  AND purpose = 'swap'
  AND amount_in IS NULL;

Type guard

fn is_missing_amount_in(e: &anyhow::Error) -> bool {
    e.to_string().contains("Persisted swap intent has no input amount")
}

Try / catch

if let Err(e) = client.connect().await {
    if is_missing_amount_in(&e) {
        // incomplete durable row: backfill from the on-chain swap event or resolve the intent
        log::error!("swap intent lacks amount_in: {e}");
    }
    return Err(e);
}

Prevention

When it happens

Trigger: An active swap intent row with NULL amount_in hit during connect()-time reconciliation; rows written by an older writer that did not persist the amount; direct SQL manipulation.

Common situations: Restoring backups across versions; ETL scripts that dropped 'unused' columns; partial writes during a database outage.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/d5f1be825f654eea. Report an issue: GitHub.