mod commands;
mod client;
mod cli;
mod credentials;
mod display;
mod evm;
mod mcp;
mod order_types;
mod redact;
mod routes;
mod service;
mod signer;
mod tokens;
mod x402;
use clap::Parser;
use cli::{Cli, Commands, IntentCommands};
use eyre::Result;
mod startup;
use startup::{exit_code, signer_from_file};
#[tokio::main]
async fn main() {
let _ = client::Client::status;
let cli = Cli::parse();
let result = run_cli(cli).await;
if let Err(e) = result {
let msg = redact::urls(&format!("{e}"));
eprintln!("Error: {msg}");
if msg.contains("HTTP 401") {
eprintln!();
eprintln!("This endpoint needs a valid API key. To get one:");
eprintln!(" agentswap register --address <OWNER_WALLET> --key-file <KEY_FILE>");
eprintln!(" (or set SR_API_KEY if you already have a key)");
}
std::process::exit(exit_code(&e));
}
}
async fn run_cli(cli: Cli) -> Result<()> {
match &cli.command {
Commands::BatchSell { command } => return commands::batch_sell::dispatch(&cli, command).await,
Commands::Portfolio(input) => return commands::holdings::portfolio(&cli, input.clone()).await,
Commands::GrantLink(input) => return commands::holdings::grant_link(&cli, input.clone()).await,
_ => {}
}
let _ = (routes::X402_VERIFY, routes::X402_SETTLE, routes::X402_DISCOVERY);
if let Some(max_amount) = &cli.trade_max_amount {
order_types::parse_raw_amount("trade max-amount", max_amount)?;
}
order_types::parse_raw_amount("x402 payment cap", &cli.x402_max_amount)?;
let api_key = cli
.api_key
.clone()
.filter(|k| !k.is_empty())
.or_else(credentials::load_api_key);
let signer = signer_from_file(cli.key_file.as_deref())?;
let x402_signer = signer_from_file(cli.x402_key_file.as_deref().or(cli.key_file.as_deref()))?;
let client = client::Client::new(&cli.url, api_key.clone()).with_x402(
x402::Config {
enabled: cli.x402,
prefer_x402: cli.prefer_x402,
chain_id: cli.x402_chain_id,
max_amount: cli.x402_max_amount.clone(),
asset: cli.x402_asset.clone(),
},
x402_signer.clone(),
);
let intent_relay_client = client::Client::new(crate::routes::app_origin(), api_key).with_x402(
x402::Config {
enabled: cli.x402,
prefer_x402: cli.prefer_x402,
chain_id: cli.x402_chain_id,
max_amount: cli.x402_max_amount.clone(),
asset: cli.x402_asset.clone(),
},
x402_signer,
);
match cli.command {
Commands::BatchSell { .. } | Commands::Portfolio(_) | Commands::GrantLink(_) => unreachable!("read-only commands dispatched above"),
Commands::BatchQuote {
chain_id,
pairs,
taker,
amount,
} => {
commands::batch_quote::run(
&client,
commands::batch_quote::Args {
chain_id,
pairs,
taker,
amount,
json: cli.json,
},
)
.await
}
Commands::Chains => commands::chains::run(&client, cli.json).await,
Commands::BuyQuota {
chain_id,
token,
amount,
} => {
commands::buy_quota::run(
&client,
commands::buy_quota::Args {
chain_id,
token,
amount,
json: cli.json,
},
)
.await
}
Commands::Quote {
chain_id,
from,
to,
amount,
slippage,
verify,
taker,
} => {
commands::quote::run(
&client,
commands::quote::Args {
chain_id,
from,
to,
amount,
slippage,
verify,
taker,
json: cli.json,
},
)
.await
}
Commands::Health => commands::health::run(&client, cli.json).await,
Commands::KeyInfo => commands::key_info::run(&client, cli.json).await,
Commands::Tokens { chain_id } => {
commands::tokens::run(&client, cli.json, chain_id.as_deref()).await
}
Commands::Pools { chain_id, address } => {
commands::pools::run(
&client,
commands::pools::Args {
chain_id,
address,
json: cli.json,
},
)
.await
}
Commands::Register {
address,
private_key,
key_file,
} => {
commands::register::run(
&client,
commands::register::Args {
address,
private_key,
key_file,
json: cli.json,
},
)
.await
}
Commands::Pricing => commands::pricing::run(&client, cli.json).await,
Commands::QuotaClaim { chain_id, tx_hash } => {
commands::quota_claim::run(
&client,
commands::quota_claim::Args {
chain_id,
tx_hash,
json: cli.json,
},
)
.await
}
Commands::RouteExplain { hash } => {
commands::route_explain::run(
&client,
commands::route_explain::Args {
hash,
json: cli.json,
},
)
.await
}
Commands::Mcp => {
mcp::serve_stdio(mcp::Config {
client,
intent_client: intent_relay_client,
signer,
allow_trade: cli.allow_trade,
trade_max_amount: cli.trade_max_amount.clone(),
})
.await
}
Commands::Intent { command } => match command {
IntentCommands::Place {
chain_id, proxy_owner, from, to, amount, start_out, end_out,
decay_secs, duration_secs, deadline_secs, relay, self_submit, dry_run,
} => {
let signer = signer.ok_or_else(|| eyre::eyre!("intent place requires --key-file or AGENTSWAP_KEY_FILE"))?;
commands::intent::run_place(
&intent_relay_client,
service::intent::PlaceInput {
chain_id, proxy_owner, from, to, amount, start_out, end_out,
decay_secs, duration_secs, deadline_secs, relay, self_submit, dry_run,
max_amount: cli.trade_max_amount.clone(),
}, signer, cli.allow_trade, cli.json,
).await
}
IntentCommands::List { chain_id, owner, agent, lookback_blocks } => {
commands::intent::run_list(service::intent::ListInput { chain_id, owner, agent, lookback_blocks }, cli.json).await
}
IntentCommands::Status { chain_id, id, lookback_blocks } => {
commands::intent::run_status(service::intent::StatusInput { chain_id, id, lookback_blocks }, cli.json).await
}
},
Commands::Policy { chain_id, owner, agent, lookback_blocks, tokens } => {
commands::intent::run_policy(service::intent::PolicyInput { chain_id, owner, agent, lookback_blocks, tokens }, cli.json).await
}
Commands::Trade {
chain_id,
from,
to,
amount,
slippage,
min_out,
mode,
proxy,
nonce,
deadline_secs,
dry_run,
self_submit,
key_file,
} => {
let signer = signer_from_file(key_file.as_deref())?
.or(signer)
.ok_or_else(|| eyre::eyre!("trade requires --key-file or AGENTSWAP_KEY_FILE"))?;
commands::trade::run(
&client,
signer,
commands::trade::Args {
chain_id,
from,
to,
amount,
slippage,
min_out,
mode,
proxy,
nonce,
deadline_secs: Some(deadline_secs),
dry_run: dry_run || !cli.allow_trade,
self_submit,
json: cli.json,
max_amount: cli.trade_max_amount.clone(),
},
cli.allow_trade,
)
.await
}
}
}
#[cfg(test)]
#[path = "main_tests.rs"]
mod tests;
#[cfg(test)]
mod holdings_tests;
#[cfg(test)]
mod fix_tests;
#[cfg(test)]
mod batch_sell_tests;
#[cfg(test)]
mod batch_sell_fix_tests;
#[cfg(test)]
mod batch_sell_fork_tests;