ai-agent-bitcoin-escrow 0.1.0

A Rust library for AI agents to create, manage, and execute Bitcoin escrow contracts using multisig
Documentation
//! Common types for the AI Agent Bitcoin Escrow Library.

use bitcoin::{Address, Network, OutPoint, PublicKey, ScriptBuf, Transaction, Txid, XOnlyPublicKey};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;

/// Unique identifier for an escrow contract.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EscrowId(pub String);

impl EscrowId {
    /// Create a new unique escrow ID.
    pub fn new() -> Self {
        Self(uuid::Uuid::new_v4().to_string())
    }

    /// Create an escrow ID from a string.
    pub fn from_string(s: String) -> Self {
        Self(s)
    }
}

impl Default for EscrowId {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for EscrowId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Represents an agent identifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AgentId(pub String);

impl AgentId {
    /// Create a new agent ID.
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }
}

impl fmt::Display for AgentId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Represents an oracle identifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OracleId(pub String);

impl OracleId {
    /// Create a new oracle ID.
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }
}

impl fmt::Display for OracleId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Role of a participant in an escrow contract.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd)]
#[serde(rename_all = "snake_case")]
pub enum EscrowRole {
    /// The party funding the escrow.
    Buyer,
    /// The party receiving funds upon successful completion.
    Seller,
    /// The arbitrator/mediator for dispute resolution.
    Arbiter,
    /// An AI agent acting autonomously.
    AIAgent,
}

impl fmt::Display for EscrowRole {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            EscrowRole::Buyer => write!(f, "buyer"),
            EscrowRole::Seller => write!(f, "seller"),
            EscrowRole::Arbiter => write!(f, "arbiter"),
            EscrowRole::AIAgent => write!(f, "ai_agent"),
        }
    }
}

/// Participant in an escrow contract.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EscrowParticipant {
    /// Unique identifier for the participant.
    pub id: String,
    /// Role in the escrow.
    pub role: EscrowRole,
    /// Public key for multisig (if available).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub public_key: Option<PublicKey>,
    /// X-only public key for Taproot (if available).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub xonly_public_key: Option<XOnlyPublicKey>,
    /// Bitcoin address for receiving funds (serialized as string).
    #[serde(skip_serializing_if = "Option::is_none", serialize_with = "serialize_address_opt", deserialize_with = "deserialize_address_opt")]
    pub address: Option<Address>,
    /// Whether this participant has signed.
    pub signed: bool,
    /// Timestamp of when the participant joined.
    pub joined_at: DateTime<Utc>,
}

fn serialize_address_opt<S>(addr: &Option<Address>, s: S) -> std::result::Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    match addr {
        Some(a) => s.serialize_str(&a.to_string()),
        None => s.serialize_none(),
    }
}

fn deserialize_address_opt<'de, D>(d: D) -> std::result::Result<Option<Address>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    use serde::de::Error;
    let opt: Option<String> = Option::deserialize(d)?;
    match opt {
        Some(s) => {
            let addr = Address::from_str(&s).map_err(|e| D::Error::custom(e.to_string()))?;
            Ok(Some(addr.assume_checked()))
        }
        None => Ok(None),
    }
}

/// Status of an escrow contract.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EscrowStatus {
    /// Contract is created but not yet funded.
    Created,
    /// Contract is funded and waiting for conditions.
    Funded,
    /// Conditions are being evaluated.
    Evaluating,
    /// Ready for release, waiting for signatures.
    PendingRelease,
    /// Funds have been released.
    Released,
    /// Contract was cancelled/refunded.
    Cancelled,
    /// Dispute in progress.
    Disputed,
    /// Contract has expired.
    Expired,
}

impl fmt::Display for EscrowStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            EscrowStatus::Created => write!(f, "created"),
            EscrowStatus::Funded => write!(f, "funded"),
            EscrowStatus::Evaluating => write!(f, "evaluating"),
            EscrowStatus::PendingRelease => write!(f, "pending_release"),
            EscrowStatus::Released => write!(f, "released"),
            EscrowStatus::Cancelled => write!(f, "cancelled"),
            EscrowStatus::Disputed => write!(f, "disputed"),
            EscrowStatus::Expired => write!(f, "expired"),
        }
    }
}

/// Represents an escrow output (UTXO).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EscrowOutput {
    /// The outpoint (txid + vout).
    pub outpoint: OutPoint,
    /// The amount in satoshis.
    pub amount: u64,
    /// The scriptPubKey.
    pub script_pubkey: ScriptBuf,
}

impl EscrowOutput {
    /// Create a new escrow output.
    pub fn new(outpoint: OutPoint, amount: u64, script_pubkey: ScriptBuf) -> Self {
        Self {
            outpoint,
            amount,
            script_pubkey,
        }
    }
}

/// Represents a signed transaction ready for broadcast.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedTransaction {
    /// The signed transaction.
    pub transaction: Transaction,
    /// Transaction ID.
    pub txid: Txid,
    /// Timestamp of signing.
    pub signed_at: DateTime<Utc>,
    /// Who signed this transaction.
    pub signed_by: Vec<String>,
}

/// Configuration for creating an escrow contract.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EscrowConfig {
    /// Network to use (mainnet, testnet, signet, regtest).
    pub network: Network,
    /// Threshold for multisig (e.g., 2 for 2-of-3).
    pub threshold: usize,
    /// Total number of participants.
    pub total_participants: usize,
    /// Optional expiry time for the escrow.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expires_at: Option<DateTime<Utc>>,
    /// Description/purpose of the escrow.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// Fee rate in sat/vbyte.
    pub fee_rate: f32,
}

impl Default for EscrowConfig {
    fn default() -> Self {
        Self {
            network: Network::Testnet,
            threshold: 2,
            total_participants: 3,
            expires_at: None,
            description: None,
            fee_rate: 1.0,
        }
    }
}

/// Types of release conditions.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ReleaseCondition {
    /// AI agent decision trigger.
    AIDecision {
        agent_id: AgentId,
        decision_hash: String,
    },
    /// Oracle-based trigger.
    OracleTrigger {
        oracle_id: OracleId,
        query: String,
        expected_value: String,
    },
    /// Time-based trigger.
    TimeLock {
        unlock_after: DateTime<Utc>,
    },
    /// Combination of conditions (AND).
    AllOf {
        conditions: Vec<ReleaseCondition>,
    },
    /// Any of conditions (OR).
    AnyOf {
        conditions: Vec<ReleaseCondition>,
    },
    /// Manual approval from specified roles.
    ManualApproval {
        required_roles: Vec<EscrowRole>,
    },
}

/// Outcome of a condition evaluation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionResult {
    /// Whether the condition is satisfied.
    pub satisfied: bool,
    /// Human-readable reason.
    pub reason: String,
    /// Timestamp of evaluation.
    pub evaluated_at: DateTime<Utc>,
    /// Supporting data (e.g., oracle response).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
}