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#[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, }
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
80pub 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 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 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 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 if !response.status().is_success() {
147 return Err(Error::StatusCodeError(
148 response.status().as_u16(),
149 response.status().to_string(),
150 ));
151 }
152
153 let result: JsonRpcResponse = response
155 .json()
156 .await
157 .map_err(|e| Error::DeserializationError(e.to_string()))?;
158
159 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 result
171 .result
172 .ok_or_else(|| Error::UnknownError("No result in response".to_string()))
173 }
174}