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#[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, }
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
96pub 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 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 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 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 if !response.status().is_success() {
157 return Err(Error::StatusCodeError(
158 response.status().as_u16(),
159 response.status().to_string(),
160 ));
161 }
162
163 let result: JsonRpcResponse = response
165 .json()
166 .await
167 .map_err(|e| Error::DeserializationError(e.to_string()))?;
168
169 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 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 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 let out = serde_json::from_value(response)
216 .map_err(|e| Error::DeserializationError(e.to_string()))?;
217
218 Ok(out)
219 }
220}