linera_faucet_client/
lib.rs1use std::collections::BTreeMap;
9
10use linera_base::{
11 crypto::{CryptoHash, ValidatorPublicKey},
12 data_types::{Amount, ArithmeticError, ChainDescription, Timestamp},
13 identifiers::ChainId,
14};
15use linera_client::config::GenesisConfig;
16use linera_execution::{committee::ValidatorState, Committee, ResourceControlPolicy};
17use linera_version::VersionInfo;
18use thiserror_context::Context;
19
20#[derive(Debug, thiserror::Error)]
21#[non_exhaustive]
22pub enum ErrorInner {
23 #[error("JSON parsing error: {0:?}")]
24 Json(#[from] serde_json::Error),
25 #[error("GraphQL error: {0:?}")]
26 GraphQl(Vec<serde_json::Value>),
27 #[error("HTTP error: {0:?}")]
28 Http(#[from] reqwest::Error),
29 #[error(transparent)]
30 ArithmeticError(#[from] ArithmeticError),
31}
32
33thiserror_context::impl_context!(Error(ErrorInner));
34
35#[derive(Clone, Debug, serde::Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct ClaimOutcome {
39 pub chain_id: ChainId,
41 pub certificate_hash: CryptoHash,
43 pub amount: Amount,
45}
46
47#[derive(Clone, Debug, serde::Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct InitialClaim {
51 pub chain_id: ChainId,
53 pub timestamp: Timestamp,
55}
56
57#[derive(Debug, Clone)]
59pub struct Faucet {
60 url: String,
61}
62
63impl Faucet {
64 pub fn new(url: String) -> Self {
65 Self { url }
66 }
67
68 pub fn url(&self) -> &str {
69 &self.url
70 }
71
72 async fn query<Response: serde::de::DeserializeOwned>(
73 &self,
74 query: impl AsRef<str>,
75 ) -> Result<Response, Error> {
76 let query = query.as_ref();
77
78 #[derive(serde::Deserialize)]
79 struct GraphQlResponse<T> {
80 data: Option<T>,
81 errors: Option<Vec<serde_json::Value>>,
82 }
83
84 let builder = reqwest::ClientBuilder::new();
85
86 #[cfg(not(target_arch = "wasm32"))]
87 let builder = builder.timeout(linera_base::time::Duration::from_secs(30));
88
89 let response: GraphQlResponse<Response> = builder
90 .build()
91 .unwrap()
92 .post(&self.url)
93 .json(&serde_json::json!({
94 "query": query,
95 }))
96 .send()
97 .await
98 .with_context(|| format!("executing query {query:?}"))?
99 .error_for_status()?
100 .json()
101 .await?;
102
103 if let Some(errors) = response.errors {
104 let messages = errors
106 .iter()
107 .filter_map(|error| {
108 error
109 .get("message")
110 .and_then(|msg| msg.as_str())
111 .map(|s| s.to_string())
112 })
113 .collect::<Vec<_>>();
114
115 if messages.is_empty() {
116 Err(ErrorInner::GraphQl(errors).into())
117 } else {
118 Err(
119 ErrorInner::GraphQl(vec![serde_json::Value::String(messages.join("; "))])
120 .into(),
121 )
122 }
123 } else {
124 Ok(response
125 .data
126 .expect("no errors present but no data returned"))
127 }
128 }
129
130 pub async fn genesis_config(&self) -> Result<GenesisConfig, Error> {
131 #[derive(serde::Deserialize)]
132 #[serde(rename_all = "camelCase")]
133 struct Response {
134 genesis_config: GenesisConfig,
135 }
136
137 Ok(self
138 .query::<Response>("query { genesisConfig }")
139 .await?
140 .genesis_config)
141 }
142
143 pub async fn version_info(&self) -> Result<VersionInfo, Error> {
144 #[derive(serde::Deserialize)]
145 struct Response {
146 version: VersionInfo,
147 }
148
149 Ok(self.query::<Response>("query { version }").await?.version)
150 }
151
152 pub async fn claim(
153 &self,
154 owner: &linera_base::identifiers::AccountOwner,
155 ) -> Result<ChainDescription, Error> {
156 #[derive(serde::Deserialize)]
157 struct Response {
158 claim: ChainDescription,
159 }
160 Ok(self
161 .query::<Response>(format!("mutation {{ claim(owner: \"{owner}\") }}"))
162 .await?
163 .claim)
164 }
165
166 pub async fn daily_claim(
170 &self,
171 owner: &linera_base::identifiers::AccountOwner,
172 ) -> Result<ClaimOutcome, Error> {
173 #[derive(serde::Deserialize)]
174 #[serde(rename_all = "camelCase")]
175 struct Response {
176 daily_claim: ClaimOutcome,
177 }
178
179 Ok(self
180 .query::<Response>(format!("mutation {{ dailyClaim(owner: \"{owner}\") }}"))
181 .await?
182 .daily_claim)
183 }
184
185 pub async fn initial_claim(
187 &self,
188 owner: &linera_base::identifiers::AccountOwner,
189 ) -> Result<Option<InitialClaim>, Error> {
190 #[derive(serde::Deserialize)]
191 #[serde(rename_all = "camelCase")]
192 struct Response {
193 initial_claim: Option<InitialClaim>,
194 }
195
196 Ok(self
197 .query::<Response>(format!(
198 "query {{ initialClaim(owner: \"{owner}\") {{ chainId timestamp }} }}"
199 ))
200 .await?
201 .initial_claim)
202 }
203
204 pub async fn next_daily_claim(
208 &self,
209 owner: &linera_base::identifiers::AccountOwner,
210 ) -> Result<Option<Timestamp>, Error> {
211 #[derive(serde::Deserialize)]
212 #[serde(rename_all = "camelCase")]
213 struct Response {
214 next_daily_claim: Option<Timestamp>,
215 }
216
217 Ok(self
218 .query::<Response>(format!("query {{ nextDailyClaim(owner: \"{owner}\") }}"))
219 .await?
220 .next_daily_claim)
221 }
222
223 pub async fn current_validators(&self) -> Result<Vec<(ValidatorPublicKey, String)>, Error> {
224 #[derive(serde::Deserialize)]
225 #[serde(rename_all = "camelCase")]
226 struct Validator {
227 public_key: ValidatorPublicKey,
228 network_address: String,
229 }
230
231 #[derive(serde::Deserialize)]
232 #[serde(rename_all = "camelCase")]
233 struct Response {
234 current_validators: Vec<Validator>,
235 }
236
237 Ok(self
238 .query::<Response>("query { currentValidators { publicKey networkAddress } }")
239 .await?
240 .current_validators
241 .into_iter()
242 .map(|validator| (validator.public_key, validator.network_address))
243 .collect())
244 }
245
246 pub async fn current_committee(&self) -> Result<Committee, Error> {
247 #[derive(serde::Deserialize)]
248 struct CommitteeResponse {
249 validators: BTreeMap<ValidatorPublicKey, ValidatorState>,
250 policy: ResourceControlPolicy,
251 }
252
253 #[derive(serde::Deserialize)]
254 #[serde(rename_all = "camelCase")]
255 struct Response {
256 current_committee: CommitteeResponse,
257 }
258
259 let response = self
260 .query::<Response>(
261 "query { currentCommittee { \
262 validators \
263 policy \
264 } }",
265 )
266 .await?;
267
268 let committee_response = response.current_committee;
269
270 Ok(Committee::new(
271 committee_response.validators,
272 committee_response.policy,
273 )?)
274 }
275}