tx3_sdk/trp/
mod.rs

1use reqwest::header;
2use serde::{Deserialize, Serialize};
3use serde_json::{json, Value};
4use std::collections::HashMap;
5use uuid::Uuid;
6
7pub mod args;
8
9pub use args::ArgValue;
10
11use crate::trp::args::BytesEnvelope;
12
13// Custom error type for TRP operations
14#[derive(Debug, thiserror::Error)]
15pub enum Error {
16    #[error("network error: {0}")]
17    NetworkError(#[from] reqwest::Error),
18
19    #[error("HTTP error {0}: {1}")]
20    StatusCodeError(u16, String),
21
22    #[error("Failed to deserialize response: {0}")]
23    DeserializationError(String),
24
25    #[error("JSON-RPC error: {1}")]
26    JsonRpcError(String, String),
27
28    #[error("Unknown error: {0}")]
29    UnknownError(String),
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct TirInfo {
34    pub version: String,
35    pub bytecode: String,
36    pub encoding: String, // "base64" | "hex" | other
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct VKeyWitness {
41    pub key: args::BytesEnvelope,
42    pub signature: args::BytesEnvelope,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(tag = "type")]
47pub enum SubmitWitness {
48    #[serde(rename = "vkey")]
49    VKey(VKeyWitness),
50}
51
52#[derive(Deserialize, Debug, Serialize)]
53pub struct SubmitParams {
54    pub tx: args::BytesEnvelope,
55    pub witnesses: Vec<SubmitWitness>,
56}
57
58#[derive(Deserialize, Debug, Serialize)]
59pub struct SubmitResponse {
60    pub hash: String,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct TxEnvelope {
65    pub tx: String,
66    pub hash: String,
67}
68
69#[derive(Debug, Clone)]
70pub struct ClientOptions {
71    pub endpoint: String,
72    pub headers: Option<HashMap<String, String>>,
73    pub env_args: Option<HashMap<String, ArgValue>>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct JsonRpcRequest {
78    pub jsonrpc: String,
79    pub method: String,
80    pub params: serde_json::Value,
81    pub id: String,
82}
83
84#[derive(Debug, Deserialize)]
85struct JsonRpcResponse {
86    result: Option<serde_json::Value>,
87    error: Option<JsonRpcError>,
88}
89
90#[derive(Debug, Deserialize)]
91struct JsonRpcError {
92    message: String,
93    data: Option<Value>,
94}
95
96/// Client for the Transaction Resolve Protocol (TRP)
97pub struct Client {
98    options: ClientOptions,
99    client: reqwest::Client,
100}
101
102pub struct ProtoTxRequest {
103    pub tir: TirInfo,
104    pub args: HashMap<String, ArgValue>,
105}
106
107impl Client {
108    pub fn new(options: ClientOptions) -> Self {
109        Self {
110            options,
111            client: reqwest::Client::new(),
112        }
113    }
114
115    pub async fn call(
116        &self,
117        method: &str,
118        params: serde_json::Value,
119    ) -> Result<serde_json::Value, Error> {
120        // Prepare headers
121        let mut headers = header::HeaderMap::new();
122        headers.insert(
123            header::CONTENT_TYPE,
124            header::HeaderValue::from_static("application/json"),
125        );
126
127        if let Some(user_headers) = &self.options.headers {
128            for (key, value) in user_headers {
129                if let Ok(header_name) = header::HeaderName::from_bytes(key.as_bytes()) {
130                    if let Ok(header_value) = header::HeaderValue::from_str(value) {
131                        headers.insert(header_name, header_value);
132                    }
133                }
134            }
135        }
136
137        // Prepare request body with FlattenedArgs for proper serialization
138        let body = JsonRpcRequest {
139            jsonrpc: "2.0".to_string(),
140            method: method.to_string(),
141            params,
142            id: Uuid::new_v4().to_string(),
143        };
144
145        // Send request
146        let response = self
147            .client
148            .post(&self.options.endpoint)
149            .headers(headers)
150            .json(&serde_json::to_value(body).unwrap())
151            .send()
152            .await
153            .map_err(Error::from)?;
154
155        // Check if response is successful
156        if !response.status().is_success() {
157            return Err(Error::StatusCodeError(
158                response.status().as_u16(),
159                response.status().to_string(),
160            ));
161        }
162
163        // Parse response
164        let result: JsonRpcResponse = response
165            .json()
166            .await
167            .map_err(|e| Error::DeserializationError(e.to_string()))?;
168
169        // Handle possible error
170        if let Some(error) = result.error {
171            return Err(Error::JsonRpcError(
172                error.message,
173                error
174                    .data
175                    .map_or_else(|| "No data".to_string(), |v| v.to_string()),
176            ));
177        }
178
179        // Return result
180        result
181            .result
182            .ok_or_else(|| Error::UnknownError("No result in response".to_string()))
183    }
184
185    pub async fn resolve(&self, proto_tx: ProtoTxRequest) -> Result<TxEnvelope, Error> {
186        let params = json!({
187            "tir": proto_tx.tir,
188            "args": HashMap::<String, serde_json::Value>::from_iter(proto_tx.args.into_iter().map(|(k, v)| (k, args::to_json(v)))),
189            "env": self.options.env_args,
190        });
191
192        let response = self.call("trp.resolve", params).await?;
193
194        // Return result
195        let out = serde_json::from_value(response)
196            .map_err(|e| Error::DeserializationError(e.to_string()))?;
197
198        Ok(out)
199    }
200
201    pub async fn submit(
202        &self,
203        tx: TxEnvelope,
204        witnesses: Vec<SubmitWitness>,
205    ) -> Result<SubmitResponse, Error> {
206        let params = serde_json::to_value(SubmitParams {
207            tx: BytesEnvelope::from_hex(&tx.tx).unwrap(),
208            witnesses,
209        })
210        .unwrap();
211
212        let response = self.call("trp.submit", params).await?;
213
214        // Return result
215        let out = serde_json::from_value(response)
216            .map_err(|e| Error::DeserializationError(e.to_string()))?;
217
218        Ok(out)
219    }
220}