zescrow_core/
interface.rs

1#[cfg(feature = "json")]
2use std::fs::File;
3#[cfg(feature = "json")]
4use std::path::Path;
5
6#[cfg(feature = "json")]
7use anyhow::Context;
8use bincode::{Decode, Encode};
9#[cfg(feature = "json")]
10use serde::de::DeserializeOwned;
11#[cfg(feature = "json")]
12use serde::{Deserialize, Serialize};
13
14use crate::{Asset, EscrowError, Party};
15
16/// Default path to escrow params template.
17pub const ESCROW_PARAMS_PATH: &str = concat!(
18    env!("CARGO_MANIFEST_DIR"),
19    "/../templates/escrow_params.json"
20);
21
22/// Default path to on-chain escrow metadata.
23pub const ESCROW_METADATA_PATH: &str = concat!(
24    env!("CARGO_MANIFEST_DIR"),
25    "/../templates/escrow_metadata.json"
26);
27
28/// Default path to escrow conditions template.
29pub const ESCROW_CONDITIONS_PATH: &str = concat!(
30    env!("CARGO_MANIFEST_DIR"),
31    "/../templates/escrow_conditions.json"
32);
33
34/// Default path to proof data.
35pub const PROOF_DATA_PATH: &str =
36    concat!(env!("CARGO_MANIFEST_DIR"), "/../templates/proof_data.json");
37
38/// Reads a JSON-encoded file from the given `path` and deserializes into type `T`.
39///
40/// # Errors
41///
42/// Returns an `anyhow::Error` if the file cannot be opened, read, or parsed.
43///
44/// # Examples
45///
46/// ```ignore
47/// # use zescrow_core::interface::load_escrow_data;
48///
49/// #[derive(Deserialize)]
50/// struct MyParams { /* fields matching JSON */ }
51///
52/// let _params: MyParams = load_escrow_data(./my_params.json).unwrap();
53/// ```
54#[cfg(feature = "json")]
55pub fn load_escrow_data<P, T>(path: P) -> anyhow::Result<T>
56where
57    P: AsRef<Path>,
58    T: DeserializeOwned,
59{
60    let path = path.as_ref();
61    let content =
62        std::fs::read_to_string(path).with_context(|| format!("loading escrow data: {path:?}"))?;
63    serde_json::from_str(&content).with_context(|| format!("parsing JSON from {path:?}"))
64}
65
66/// Writes `data` (serializable) as pretty-printed JSON to the given `path`.
67///
68/// # Errors
69///
70/// Returns an `anyhow::Error` if the file cannot be created or data cannot be serialized.
71///
72/// # Examples
73///
74/// ```ignore
75/// # use zescrow_core::interface::save_escrow_data;
76/// # use serde::Serialize;
77///
78/// #[derive(Serialize)]
79/// struct MyMetadata { /* fields */ }
80///
81/// let metadata = MyMetadata { /* ... */ };
82/// save_escrow_data("./metadata.json", &metadata).unwrap();
83/// ```
84#[cfg(feature = "json")]
85pub fn save_escrow_data<P, T>(path: P, data: &T) -> anyhow::Result<()>
86where
87    P: AsRef<Path>,
88    T: Serialize,
89{
90    let path = path.as_ref();
91    let file = File::create(path).with_context(|| format!("creating file {path:?}"))?;
92    serde_json::to_writer_pretty(file, data)
93        .with_context(|| format!("serializing to JSON to {path:?}"))
94}
95
96/// State of escrow execution in the `client`.
97#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
98#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq, Eq)]
99pub enum ExecutionState {
100    /// Escrow object created.
101    Initialized,
102
103    /// Funds have been deposited; awaiting release or cancellation.
104    Funded,
105
106    /// Conditions (if any) have been fulfilled;
107    /// funds will be released to the recipient if the proof verifies on-chain.
108    ConditionsMet,
109}
110
111/// Result of escrow execution in the `client`.
112#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
113#[derive(Debug, Clone, Encode, Decode)]
114pub enum ExecutionResult {
115    /// Happy path; no errors in execution.
116    Ok(ExecutionState),
117    /// Unsuccessful escrow execution, with the error message.
118    Err(String),
119}
120
121/// Metadata returned from on-chain escrow creation.
122#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
123#[derive(Debug, Clone, Encode, Decode)]
124pub struct EscrowMetadata {
125    /// The parameters that were specified during escrow creation.
126    pub params: EscrowParams,
127    /// State of escrow execution in the `client`.
128    pub state: ExecutionState,
129    /// Unique identifier for the created escrow.
130    pub escrow_id: Option<u64>,
131}
132
133/// Parameters required to create an escrow on-chain.
134#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
135#[derive(Debug, Clone, Encode, Decode)]
136pub struct EscrowParams {
137    /// Chain-specific network configuration.
138    pub chain_config: ChainConfig,
139
140    /// Exactly which asset to lock (native, token, NFT, pool-share, etc).
141    pub asset: Asset,
142
143    /// Who is funding the escrow.
144    pub sender: Party,
145
146    /// Who will receive the funds once conditions pass.
147    pub recipient: Party,
148
149    /// Optional block height or slot after which "release" is allowed.
150    /// Must be `None` or less than `cancel_after` if both are set.
151    pub finish_after: Option<u64>,
152
153    /// Optional block height or slot after which "cancel" is allowed.
154    /// Must be `None` or greater than `finish_after` if both are set.
155    pub cancel_after: Option<u64>,
156
157    /// Denotes whether this escrow is subject to cryptographic conditions.
158    pub has_conditions: bool,
159}
160
161/// Chain-specific network configuration for creating or querying escrows.
162#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
163#[derive(Debug, Clone, Encode, Decode)]
164pub struct ChainConfig {
165    /// Network identifier.
166    pub chain: Chain,
167    /// JSON-RPC endpoint URL.
168    pub rpc_url: String,
169    /// Sender's private key and/or keypair path.
170    ///
171    /// For Ethereum, a wallet import format (WIF) or hex is expected.
172    /// For Solana, a path to a keypair file (e.g., `~/.config/solana/id.json`).
173    pub sender_private_id: String,
174    /// On-chain escrow program ID (Solana) or smart contract address (Ethereum).
175    pub agent_id: String,
176}
177
178/// Supported blockchain networks.
179#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
180#[cfg_attr(feature = "json", serde(rename_all = "lowercase"))]
181#[derive(Debug, Copy, Clone, Encode, Decode)]
182pub enum Chain {
183    /// Ethereum and other EVM-compatible chains.
184    Ethereum,
185    /// Solana
186    Solana,
187}
188
189impl AsRef<str> for Chain {
190    fn as_ref(&self) -> &str {
191        match self {
192            Chain::Ethereum => "ethereum",
193            Chain::Solana => "solana",
194        }
195    }
196}
197
198impl std::str::FromStr for Chain {
199    type Err = EscrowError;
200
201    /// Parses a string ID into a `Chain` enum (case-insensitive).
202    ///
203    /// # Errors
204    ///
205    /// Returns `EscrowError::UnsupportedChain` on unrecognized input.
206    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
207        match s.to_lowercase().as_str() {
208            "ethereum" | "eth" => Ok(Self::Ethereum),
209            "solana" | "sol" => Ok(Self::Solana),
210            _ => Err(EscrowError::UnsupportedChain),
211        }
212    }
213}