miden_node_rpc/
funding.rs1use std::num::NonZeroU64;
2use std::time::Duration;
3
4use anyhow::Context;
5use miden_node_tracing::miden_instrument;
6use miden_protocol::account::AccountId;
7use reqwest::Client;
8use serde::Serialize;
9use url::Url;
10
11use crate::COMPONENT;
12
13#[derive(Clone)]
15pub struct FundingClient {
16 http: Client,
17 url: Url,
18 amount: NonZeroU64,
19}
20
21impl FundingClient {
22 pub fn new(mut url: Url, amount: NonZeroU64) -> anyhow::Result<Self> {
23 anyhow::ensure!(
24 matches!(url.scheme(), "http" | "https") && url.host_str().is_some(),
25 "funding service URL must use HTTP or HTTPS"
26 );
27 anyhow::ensure!(
28 url.query().is_none() && url.fragment().is_none(),
29 "funding service URL must not contain a query or fragment"
30 );
31 url.path_segments_mut()
32 .map_err(|()| anyhow::anyhow!("funding service URL must be a base URL"))?
33 .pop_if_empty()
34 .push("request-funds");
35 let http = Client::builder()
36 .connect_timeout(Duration::from_secs(5))
37 .timeout(Duration::from_secs(120))
38 .build()
39 .context("failed to create funding service client")?;
40 Ok(Self { http, url, amount })
41 }
42
43 #[miden_instrument(
46 target = COMPONENT,
47 name = "account.funding",
48 fields(account.id = account_id, asset.amount = self.amount.get()),
49 err,
50 )]
51 pub async fn fund(&self, account_id: AccountId) -> anyhow::Result<()> {
52 self.http
53 .post(self.url.clone())
54 .json(&FundingRequest {
55 account_id: account_id.to_hex(),
56 amount: self.amount.get(),
57 })
58 .send()
59 .await
60 .context("failed to send account funding request")?
61 .error_for_status()
62 .context("funding service rejected account funding request")?
63 .bytes()
65 .await
66 .context("failed to read account funding response")?;
67 Ok(())
68 }
69}
70
71#[derive(Serialize)]
72struct FundingRequest {
73 account_id: String,
74 amount: u64,
75}