agentswap 0.11.0

AgentSwap CLI for requesting quotes, inspecting tokens and pools, managing intents, and registering an API key against the AgentSwap service.
// CLI executable regressions for unsigned dry-run JSON and human-readable previews.
// Exports: ignored E2E tests, run with AGENTSWAP_E2E_BIN on an authorized remote host.
// Deps: shared quote/RPC fixture, std process/filesystem, serde_json.

use super::dry_run_fixture::Fixture;
use alloy::primitives::Address;
use alloy::sol_types::SolCall;
use crate::order_types::{IntentSettlerV3, UserProxyV6};
use std::process::Command;

fn check_cli(is_intent: bool, requested: bool) {
    let fixture = Fixture::start();
    let directory = std::env::temp_dir().join(format!("agentswap-e2e-{}-{is_intent}-{requested}", std::process::id()));
    std::fs::create_dir(&directory).unwrap();
    let key = directory.join("test-key");
    std::fs::write(&key, "01".repeat(32)).unwrap();
    for json in [true, false] {
        let mut command = Command::new(std::env::var("AGENTSWAP_E2E_BIN").expect("remote CLI executable"));
        command.env_clear().env("AGENTSWAP_RPC_URL_8453", &fixture.url).current_dir(&directory);
        command.args(["--url", &fixture.url, "--api-key", "test", "--key-file"]).arg(&key);
        if json { command.arg("--json"); }
        if requested { command.arg("--allow-trade"); }
        if is_intent {
            command.args(["intent", "place", "--proxy-owner", &format!("{:?}", Address::repeat_byte(4)),
                "--start-out", "2", "--end-out", "1", "--relay"]);
        } else {
            command.args(["trade", "--proxy", &format!("{:?}", Address::repeat_byte(2)), "--self-submit"]);
        }
        command.args(["--chainid", "8453", "--from", "USDC", "--to", "WETH", "--amount", "1"]);
        if requested { command.arg("--dry-run"); }
        let output = command.output().expect("CLI process");
        assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
        assert_preview(&String::from_utf8(output.stdout).unwrap(), json, is_intent);
    }
    std::fs::remove_dir_all(directory).unwrap();
    let calls = fixture.calls.lock().unwrap();
    let hash = if is_intent { UserProxyV6::hashIntentAuthorizationCall::SELECTOR } else { UserProxyV6::hashAgentOrderCall::SELECTOR };
    assert!(calls.contains(&hex::encode(hash)), "digest parity RPC missing");
    if is_intent { assert!(calls.contains(&hex::encode(IntentSettlerV3::orderHashCall::SELECTOR))); }
    assert!(!calls.contains(&crate::routes::INTENT_ANNOUNCE.to_string()));
    assert!(!calls.contains(&hex::encode(UserProxyV6::isIntentAuthorizedCall::SELECTOR)));
}

fn assert_preview(output: &str, json: bool, is_intent: bool) {
    if json {
        let value: serde_json::Value = serde_json::from_str(output).expect("CLI JSON");
        assert_eq!(value["dry_run"], true);
        for field in ["signature", "envelope", "self_submit"] {
            assert!(value.get(field).is_none(), "CLI dry-run released {field}");
        }
        assert!(value["order"].is_object());
        assert_eq!(value["digest"].as_str().unwrap().len(), 66);
        if is_intent { assert!(value["authorization"].is_object()); }
        else { assert_eq!(value["quote"]["output"], "1000"); }
        return;
    }
    assert!(output.contains("Digest"));
    assert!(output.contains("true"));
    for label in ["Signature", "Envelope", "Self Submit Calldata"] {
        assert!(!output.contains(label), "CLI dry-run displayed {label}");
    }
}

#[test]
#[ignore = "requires remote AGENTSWAP_E2E_BIN"]
fn requested_trade_cli_dry_run() { check_cli(false, true); }

#[test]
#[ignore = "requires remote AGENTSWAP_E2E_BIN"]
fn forced_trade_cli_dry_run() { check_cli(false, false); }

#[test]
#[ignore = "requires remote AGENTSWAP_E2E_BIN"]
fn requested_intent_cli_dry_run() { check_cli(true, true); }

#[test]
#[ignore = "requires remote AGENTSWAP_E2E_BIN"]
fn forced_intent_cli_dry_run() { check_cli(true, false); }

#[test]
#[ignore = "requires a CLI binary built on an authorized remote host"]
fn arc_testnet_cli_log_windows_preserve_other_v6_chains() {
    let executable = std::env::current_exe().unwrap().parent().unwrap().parent().unwrap().join("agentswap");
    for alias in ["arc", "arc-mainnet", "5042", "arc-testnet", "arct", "5042002", "base", "arb", "bsc", "rh"] {
        let chain_id = crate::tokens::chain_name_to_id(alias).unwrap();
        for policy in [false, true] {
            let fixture = Fixture::start();
            let mut command = Command::new(&executable);
            command.env_clear().env(format!("AGENTSWAP_RPC_URL_{chain_id}"), &fixture.url);
            command.arg("--json");
            if policy { command.arg("policy"); } else { command.args(["intent", "list"]); }
            command.args(["--chainid", alias, "--owner", &format!("{:?}", Address::repeat_byte(4))]);
            if policy { command.args(["--agent", &format!("{:?}", Address::repeat_byte(5))]); }
            let output = command.output().expect("CLI process");
            assert!(output.status.success(), "{alias}: {}", String::from_utf8_lossy(&output.stderr));
            let value: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
            if policy { assert_eq!(value["tokens"], serde_json::json!([])); }
            else { assert_eq!(value, serde_json::json!([])); }
            let calls = fixture.calls.lock().unwrap();
            let queries: Vec<serde_json::Value> = calls.iter()
                .filter_map(|call| serde_json::from_str(call).ok()).collect();
            assert_eq!(queries.len(), 41, "{alias}: default lookback changed");
            let mut next_block = 50_000;
            for query in queries {
                let filter = &query["params"][0];
                let from = u64::from_str_radix(filter["fromBlock"].as_str().unwrap().trim_start_matches("0x"), 16).unwrap();
                let to = u64::from_str_radix(filter["toBlock"].as_str().unwrap().trim_start_matches("0x"), 16).unwrap();
                assert_eq!(from, next_block, "{alias}: gap or overlap");
                assert!(to >= from && to - from < 5_000, "{alias}: oversized log request");
                next_block = to + 1;
            }
            assert_eq!(next_block, 250_001, "{alias}: incomplete lookback");
        }
    }
}