1use std::collections::HashMap;
2use std::ops::ControlFlow;
3use std::path::PathBuf;
4
5use anyhow::Result;
6use fedimint_core::task::sleep;
7use fedimint_core::util::SafeUrl;
8use reqwest::get;
9use tracing::info;
10use {reqwest, serde_json};
11
12use crate::cmd;
13use crate::envs::{
14 FM_RECURRING_API_ADDRESS_ENV, FM_RECURRING_API_BEARER_TOKEN_ENV, FM_RECURRING_BIND_ADDRESS_ENV,
15 FM_RECURRING_DATA_DIR_ENV,
16};
17use crate::util::{ProcessHandle, ProcessManager, poll};
18
19#[derive(Clone)]
20pub struct Recurringd {
21 pub(crate) process: ProcessHandle,
22 pub addr: String,
23 pub api_url: SafeUrl,
24 pub log_path: PathBuf,
25}
26
27impl Recurringd {
28 pub async fn new(process_mgr: &ProcessManager) -> Result<Self> {
29 let test_dir = &process_mgr.globals.FM_TEST_DIR;
30 let port = process_mgr.globals.FM_PORT_RECURRINGD;
31 let bind_address = format!("127.0.0.1:{port}");
32 let api_url = SafeUrl::parse(&format!("http://{bind_address}/")).expect("Valid URL");
33
34 let bearer_token = "devimint-recurring-token";
36
37 let recurring_env: HashMap<String, String> = HashMap::from_iter([
38 (
39 FM_RECURRING_DATA_DIR_ENV.to_owned(),
40 format!("{}/recurringd", test_dir.display()),
41 ),
42 (
43 FM_RECURRING_BIND_ADDRESS_ENV.to_owned(),
44 bind_address.clone(),
45 ),
46 (FM_RECURRING_API_ADDRESS_ENV.to_owned(), api_url.to_string()),
47 (
48 FM_RECURRING_API_BEARER_TOKEN_ENV.to_owned(),
49 bearer_token.to_string(),
50 ),
51 ]);
52
53 let process = process_mgr
54 .spawn_daemon(
55 "recurringd",
56 cmd!("fedimint-recurringd")
57 .arg(&"--encryption-key")
58 .arg(&"01234567890123456789012345678901")
59 .envs(recurring_env),
60 )
61 .await?;
62
63 let log_path = process_mgr.globals.FM_LOGS_DIR.join("recurringd.log");
64
65 let recurringd = Self {
66 process,
67 addr: bind_address,
68 api_url,
69 log_path,
70 };
71
72 poll("waiting for recurringd to be ready", || async {
74 match get(format!("http://{}/lnv1/federations", recurringd.addr)).await {
75 Ok(response) if response.status().is_success() => Ok(()),
76 _ => {
77 sleep(tokio::time::Duration::from_millis(100)).await;
78 Err(ControlFlow::Continue(anyhow::anyhow!(
79 "recurringd not ready yet"
80 )))
81 }
82 }
83 })
84 .await?;
85
86 info!("Recurringd started at {}", recurringd.addr);
87 Ok(recurringd)
88 }
89
90 pub async fn terminate(self) -> Result<()> {
91 self.process.terminate().await
92 }
93
94 pub async fn add_federation(&self, invite_code: &str) -> Result<String> {
96 let url = format!("http://{}/lnv1/federations", self.addr);
97 let client = reqwest::Client::new();
98 let response = client
99 .put(&url)
100 .header("Authorization", "Bearer devimint-recurring-token")
101 .header("Content-Type", "application/json")
102 .json(&serde_json::json!({ "invite": invite_code }))
103 .send()
104 .await?;
105
106 Ok(response.text().await?)
107 }
108
109 pub async fn list_federations(&self) -> Result<String> {
111 let url = format!("http://{}/lnv1/federations", self.addr);
112 let client = reqwest::Client::new();
113 let response = client
114 .get(&url)
115 .header("Authorization", "Bearer devimint-recurring-token")
116 .send()
117 .await?;
118
119 Ok(response.text().await?)
120 }
121
122 pub fn api_url(&self) -> SafeUrl {
123 self.api_url.clone()
124 }
125}