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