agentswap 0.12.0

AgentSwap CLI for requesting quotes, inspecting tokens and pools, managing intents, and registering an API key against the AgentSwap service.
// Ethereum owner intent signing on the V7 settler's unchanged EIP-712 version 3 domain.
// Exports: place; verifies order-id and digest parity before releasing any signature.
// Deps: parent intent service, alloy EIP-712 and ABI envelope encoding.

use super::*;
use alloy::sol_types::{eip712_domain, SolValue};

pub(super) async fn place(
    announcer: Announcer<'_>, mut input: PlaceInput, signer: Arc<dyn Signer>,
    allow_trade: bool, config: ChainConfig,
) -> Result<PlaceOutcome> {
    let owner = order_types::parse_address(&input.proxy_owner)?;
    if signer.address() != owner {
        return Err(eyre!("Ethereum intents require the owner signer; agent delegation is unavailable on Ethereum (1)"));
    }
    if input.deadline_secs.is_some() {
        return Err(eyre!("--deadline-secs applies to agent authorization; Ethereum owner intents expire at --duration-secs"));
    }
    input.dry_run |= !allow_trade;
    let provider = evm::read_provider_checked(&evm::rpc_url(config), config.id).await?;
    verify_raw_token_addresses(&provider, config.id, &input).await?;
    let order = build_order(&input, owner, config.id).await?;
    enforce_cap(&order, input.max_amount.as_deref())?;
    let proxy = UserProxyV6::new(proxy_for(&provider, config, owner).await?, provider.clone());
    let settler = IntentSettlerV3::new(config.settler, provider);
    let digest = owner_digest(config, &order);
    if settler.orderHash(order.clone()).call().await? != order_types::order_id(&order)
        || settler.orderDigest(order.clone()).call().await? != digest {
        return Err(eyre!("local owner intent hash or digest does not match the settler"));
    }
    let mut outcome = PlaceOutcome {
        dry_run: input.dry_run, chain_id: config.id, order: order_types::dto_from_order(&order),
        id: format!("{:?}", order_types::order_id(&order)), authorization: None,
        envelope: None, digest: format!("{digest:?}"), signature: None, relay: None, broadcast: None,
        tx_hash: None, tx_status: None, tx_error: None, tx_explorer_url: None,
    };
    if input.dry_run { return Ok(outcome); }
    let signature = signer.sign_hash(digest).await?;
    let envelope = owner_envelope(Bytes::copy_from_slice(&signature.as_bytes()));
    proxy.isIntentAuthorized(order.clone(), envelope.clone()).from(config.settler)
        .call().await.map_err(authorization_error)?;
    submission::announce(announcer, &input, signer, config, &order, &envelope, &mut outcome).await?;
    outcome.signature = Some(format!("0x{}", hex::encode(signature.as_bytes())));
    outcome.envelope = Some(hex_bytes(&envelope));
    Ok(outcome)
}

fn owner_digest(config: ChainConfig, order: &Order) -> B256 {
    order_types::signing_hash(order, &eip712_domain! {
        name: "AgentSwap IntentSettler", version: "3",
        chain_id: config.id, verifying_contract: config.settler,
    })
}

fn owner_envelope(signature: Bytes) -> Bytes {
    let payload: Bytes = signature.abi_encode().into();
    order_types::IntentAbiCodec::encodeEnvelopeCall { kind: 0, payload }
        .abi_encode()[4..].to_vec().into()
}

#[cfg(test)]
mod tests;