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