use std::fmt::Write;
use std::time::Duration;
use anyhow::{Context, Result, ensure};
use sha2::{Digest, Sha256};
use uuid::Uuid;
pub const ADMIN_API_ENV: &str = "MIDEN_NODE_ADMIN_URL";
const CONNECT_ATTEMPTS: usize = 10;
const CONNECT_RETRY_DELAY: Duration = Duration::from_millis(500);
pub async fn create_invitation_code() -> Result<String> {
let admin_url = std::env::var(ADMIN_API_ENV).with_context(|| {
format!("{ADMIN_API_ENV} is not set; start the node with MIDEN_ACCOUNT_ALLOWLIST=1")
})?;
let code = format!("miden-client-test-invitation-{}", Uuid::new_v4());
let url = format!(
"{}/admin/allowlist/invitations/{}",
admin_url.trim_end_matches('/'),
digest_of(&code)
);
let request = serde_json::json!({ "account_id": null });
let http = reqwest::Client::new();
let mut attempt = 1;
let response = loop {
match http.put(&url).json(&request).send().await {
Ok(response) => break response,
Err(error) if error.is_connect() && attempt < CONNECT_ATTEMPTS => {
tokio::time::sleep(CONNECT_RETRY_DELAY).await;
attempt += 1;
},
Err(error) => {
return Err(anyhow::Error::new(error).context(format!(
"failed to reach the node administration API at {url}; start the node with \
MIDEN_ACCOUNT_ALLOWLIST=1"
)));
},
}
};
let status = response.status();
ensure!(
status.is_success(),
"the node refused to create an invitation code: {status} {}",
response.text().await.unwrap_or_default()
);
Ok(code)
}
fn digest_of(code: &str) -> String {
let mut digest = String::with_capacity(64);
for byte in Sha256::digest(code.as_bytes()) {
write!(digest, "{byte:02x}").expect("writing to a string never fails");
}
digest
}