agentswap 0.13.0

AgentSwap CLI for requesting quotes, inspecting tokens and pools, managing intents, and registering an API key against the AgentSwap service.
// Request and response models for the intent service, shared by the CLI and MCP.
// Exports: PlaceInput, PlaceOutcome, PublishUnknown, ListInput, StatusInput, PolicyInput,
// IntentRecord, PolicyOutput, TokenPolicy.
// Deps: serde, schemars, crate::order_types DTOs, crate::tokens for the chain-selector copy.

use crate::order_types;
use crate::service::submit::{NotConfirmed, Submission, TxStatus};
use crate::tokens::CHAIN_ID_HELP;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PlaceInput {
    #[schemars(description = CHAIN_ID_HELP)]
    pub chain_id: String,
    /// Owner wallet; Ethereum requires this owner as signer and a deployed V7 proxy.
    pub proxy_owner: String,
    pub from: String,
    pub to: String,
    /// Unsigned decimal input amount in the token's smallest unit.
    pub amount: String,
    /// Unsigned decimal starting output in the token's smallest unit.
    pub start_out: String,
    /// Unsigned decimal ending output in the token's smallest unit.
    pub end_out: String,
    #[serde(default)]
    /// Seconds over which the output decays from start_out to end_out; defaults to the window
    /// and is capped at it. Must be greater than zero.
    pub decay_secs: Option<u64>,
    #[serde(default)]
    /// Intent window in seconds from now; default 600.
    pub duration_secs: Option<u64>,
    #[serde(default)]
    /// Seconds from now for the agent authorization deadline. Defaults to the end of the intent
    /// window; a value that lands before the window closes is refused. Unavailable on Ethereum.
    pub deadline_secs: Option<u64>,
    #[serde(default)]
    pub relay: bool,
    #[serde(default)]
    pub self_submit: bool,
    #[serde(default)]
    /// Publish off chain under agentswap-v6; exclusive with relay and self_submit.
    pub broadcast: bool,
    #[serde(default)]
    /// Return an unsigned intent and digest with on-chain parity checks; V6 includes authorization.
    /// Forced without --allow-trade; produces no signature or authorization envelope.
    pub dry_run: bool,
    #[serde(default)]
    /// Optional unsigned decimal cap in raw input units.
    pub max_amount: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PlaceOutcome {
    pub dry_run: bool,
    pub chain_id: u64,
    pub order: order_types::OrderDto,
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub authorization: Option<order_types::IntentAuthorizationDto>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub envelope: Option<String>,
    pub digest: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signature: Option<String>,
    pub relay: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub broadcast: Option<serde_json::Value>,
    /// Hash of the self-submitted announce transaction, set as soon as it was sent.
    pub tx_hash: Option<String>,
    /// Receipt outcome of the self-submitted announce: confirmed, reverted, or unknown when no
    /// receipt was read.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tx_status: Option<TxStatus>,
    /// Why tx_status is unknown.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tx_error: Option<String>,
    /// Explorer page of the announce transaction, on chains with a known explorer.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tx_explorer_url: Option<String>,
}

/// The relay answered without ruling out placement, so the signed intent may be live: HTTP 504 (its
/// stream publish timed out) or HTTP 502 (its announce broadcast failed, possibly after a node
/// accepted it). Carries the signed outcome so the caller can poll it by id; the intent is never
/// signed again.
#[derive(Debug)]
pub struct PublishUnknown {
    pub outcome: Box<PlaceOutcome>,
    error: String,
}

impl PublishUnknown {
    /// `error` as PublishUnknown when it is a relay HTTP 504 or 502, else unchanged.
    pub(super) fn classify(error: eyre::Report, relay: bool, outcome: &PlaceOutcome) -> eyre::Report {
        let text = error.to_string();
        if !relay || !(text.starts_with("HTTP 504") || text.starts_with("HTTP 502")) { return error; }
        Self { outcome: Box::new(outcome.clone()), error: text }.into()
    }
}

impl std::fmt::Display for PublishUnknown {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}; intent {} may be live", self.error, self.outcome.id)
    }
}

impl std::error::Error for PublishUnknown {}

impl PlaceOutcome {
    pub(super) fn record(&mut self, submission: Submission) {
        let hash = submission.hash_hex();
        self.tx_explorer_url = crate::display::tx_url(self.chain_id, &hash);
        self.tx_hash = Some(hash);
        self.tx_status = Some(submission.status);
        self.tx_error = submission.error;
    }

    /// The error to report after printing this outcome when its announce did not confirm.
    pub fn not_confirmed(&self) -> Option<NotConfirmed> {
        NotConfirmed::check(self.tx_hash.as_deref(), self.tx_status, self.tx_error.as_deref())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ListInput {
    #[schemars(description = CHAIN_ID_HELP)]
    pub chain_id: String,
    /// Owner wallet whose intents to list; required unless agent is given.
    pub owner: Option<String>,
    /// Agent wallet that placed the intents; required unless owner is given.
    pub agent: Option<String>,
    #[serde(default)]
    pub lookback_blocks: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct StatusInput {
    #[schemars(description = CHAIN_ID_HELP)]
    pub chain_id: String,
    /// Intent id (bytes32) as announced.
    pub id: String,
    #[serde(default)]
    pub lookback_blocks: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PolicyInput {
    #[schemars(description = CHAIN_ID_HELP)]
    pub chain_id: String,
    /// Owner wallet whose proxy holds the policy.
    pub owner: String,
    /// Agent wallet the policy authorizes.
    pub agent: String,
    #[serde(default)]
    pub lookback_blocks: Option<u64>,
    #[serde(default)]
    /// Token addresses to read budgets for; use them when the cap events are older than the
    /// lookback.
    pub tokens: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct IntentRecord {
    pub id: String,
    pub placed_by: String,
    pub owner: String,
    pub agent: Option<String>,
    pub pair: String,
    pub amount_in: String,
    pub start_out: String,
    pub end_out: String,
    pub window: String,
    pub exclusive_window: bool,
    pub floor_now: String,
    pub fee_now: String,
    pub required_now: String,
    pub floor_for_outsider: String,
    pub required_for_outsider: String,
    pub status: String,
    pub reason: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PolicyOutput {
    pub owner: String,
    pub agent: String,
    pub proxy: String,
    pub expiry: String,
    pub epoch_len: String,
    pub action_mask: String,
    pub generation: String,
    pub tokens: Vec<TokenPolicy>,
    pub note: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct TokenPolicy {
    pub token: String,
    pub allowed: bool,
    pub cap: String,
    pub used: String,
    pub epoch_start: String,
}