agentswap 0.11.2

AgentSwap CLI for requesting quotes, inspecting tokens and pools, managing intents, and registering an API key against the AgentSwap service.
// Trade command for quote-to-AgentOrder signing and optional self-submit.
// Exports: Args, run.
// Deps: crate::{client, service::trade, signer}.

use crate::client::Client;
use crate::service::submit::Wait;
use crate::service::trade::{self, TradeInput};
use crate::signer::Signer;
use comfy_table::{presets::UTF8_FULL_CONDENSED, Table};
use eyre::Result;
use std::sync::Arc;

pub struct Args {
    pub chain_id: String,
    pub from: String,
    pub to: String,
    pub amount: String,
    pub slippage: Option<u16>,
    pub min_out: Option<String>,
    pub max_amount: Option<String>,
    pub mode: String,
    pub proxy: String,
    pub nonce: Option<String>,
    pub deadline_secs: Option<u64>,
    pub dry_run: bool,
    pub self_submit: bool,
    pub json: bool,
}

pub async fn run(
    client: &Client,
    signer: Arc<dyn Signer>,
    args: Args,
    allow_trade: bool,
) -> Result<()> {
    let json = args.json;
    crate::tokens::chain_name_to_id(&args.chain_id)
        .ok_or_else(|| eyre::eyre!("{}", crate::tokens::unknown_chain_id(&args.chain_id)))?;
    let outcome = trade::execute_trade(client, signer, input(args), allow_trade, Wait::CLI).await?;
    if outcome.dry_run || outcome.self_submit.is_some() {
        print_outcome(&outcome, json)?;
    }
    // Printed first, so a reverted or unknown broadcast still leaves its hash on stdout.
    outcome.not_confirmed().map_or(Ok(()), |failure| Err(failure.into()))
}

fn input(args: Args) -> TradeInput {
    TradeInput {
        chain_id: args.chain_id,
        from: args.from,
        to: args.to,
        amount: args.amount,
        slippage: args.slippage,
        min_out: args.min_out,
        max_amount: args.max_amount,
        mode: args.mode,
        proxy: args.proxy,
        nonce: args.nonce,
        deadline_secs: args.deadline_secs,
        dry_run: args.dry_run,
        self_submit: args.self_submit,
        verify_quote: true,
    }
}

#[test]
fn cli_trade_keeps_quote_verification_enabled() {
    let trade = input(Args {
        chain_id: "base".into(), from: "USDC".into(), to: "WETH".into(), amount: "1".into(),
        slippage: None, min_out: None, max_amount: None, mode: "agent-order".into(),
        proxy: alloy::primitives::Address::repeat_byte(2).to_string(), nonce: None,
        deadline_secs: None, dry_run: true, self_submit: false, json: false,
    });
    assert!(trade.verify_quote);
}

fn print_outcome(outcome: &trade::TradeOutcome, json: bool) -> Result<()> {
    if json {
        println!("{}", serde_json::to_string_pretty(outcome)?);
        return Ok(());
    }
    let mut table = Table::new();
    table.load_preset(UTF8_FULL_CONDENSED);
    table.set_header(vec!["AgentSwap Trade", ""]);
    table.add_row(vec!["Mode", outcome.mode.as_str()]);
    table.add_row(vec!["Dry Run", &outcome.dry_run.to_string()]);
    table.add_row(vec!["Digest", outcome.digest.as_str()]);
    table.add_row(vec!["Agent", outcome.order.agent.as_str()]);
    table.add_row(vec!["Router", outcome.order.router.as_str()]);
    table.add_row(vec!["Token In", outcome.order.token_in.as_str()]);
    table.add_row(vec!["Amount In", outcome.order.amount_in.as_str()]);
    table.add_row(vec!["Token Out", outcome.order.token_out.as_str()]);
    table.add_row(vec!["Min Out", outcome.order.min_out.as_str()]);
    if let Some(preview) = &outcome.self_submit {
        table.add_row(vec!["Self Submit To", preview.to.as_str()]);
        table.add_row(vec!["Self Submit Calldata", preview.calldata.as_str()]);
        if let Some(hash) = &preview.tx_hash {
            table.add_row(vec!["Self Submit Tx", hash.as_str()]);
            crate::display::add_tx_status_rows(&mut table, preview.tx_status, preview.tx_error.as_deref());
            if let Some(url) = &preview.tx_explorer_url {
                table.add_row(vec!["Explorer", url.as_str()]);
            }
        }
    }
    println!("{table}");
    Ok(())
}