identity_iota_core/rebased/
utils.rs1use std::process::Output;
5
6use anyhow::Context as _;
7use iota_interaction::types::base_types::ObjectID;
8use iota_sdk::IotaClient;
9use iota_sdk::IotaClientBuilder;
10use serde::Deserialize;
11#[cfg(not(target_arch = "wasm32"))]
12use tokio::process::Command;
13
14use crate::rebased::Error;
15use iota_interaction::types::base_types::IotaAddress;
16
17const FUND_WITH_ACTIVE_ADDRESS_FUNDING_TX_BUDGET: u64 = 5_000_000;
18const FUND_WITH_ACTIVE_ADDRESS_FUNDING_VALUE: u64 = 500_000_000;
19
20#[derive(Deserialize, Debug)]
21#[serde(rename_all = "camelCase")]
22struct CoinOutput {
23 gas_coin_id: ObjectID,
24 nanos_balance: u64,
25}
26
27pub async fn get_client(network: &str) -> Result<IotaClient, Error> {
29 let client = IotaClientBuilder::default()
30 .build(network)
31 .await
32 .map_err(|err| Error::Network(format!("failed to connect to {network}"), err))?;
33
34 Ok(client)
35}
36
37fn unpack_command_output(output: &Output, task: &str) -> anyhow::Result<String> {
38 let stdout = std::str::from_utf8(&output.stdout)?;
39 if !output.status.success() {
40 let stderr = std::str::from_utf8(&output.stderr)?;
41 anyhow::bail!("Failed to {task}: {stdout}, {stderr}");
42 }
43
44 Ok(stdout.to_string())
45}
46
47#[cfg(not(target_arch = "wasm32"))]
54pub async fn request_funds(address: &IotaAddress) -> anyhow::Result<()> {
55 let fund_with_active_address = std::env::var("IOTA_IDENTITY_FUND_WITH_ACTIVE_ADDRESS")
56 .map(|v| !v.is_empty() && v.to_lowercase() == "true")
57 .unwrap_or(false);
58
59 if !fund_with_active_address {
60 let output = Command::new("iota")
61 .arg("client")
62 .arg("faucet")
63 .arg("--address")
64 .arg(address.to_string())
65 .arg("--json")
66 .output()
67 .await
68 .context("Failed to execute command")?;
69 unpack_command_output(&output, "request funds from faucet")?;
70 } else {
71 let output = Command::new("iota")
72 .arg("client")
73 .arg("gas")
74 .arg("--json")
75 .output()
76 .await
77 .context("Failed to execute command")?;
78 let output_str = unpack_command_output(&output, "fetch active account's gas coins")?;
79
80 let parsed: Vec<CoinOutput> = serde_json::from_str(&output_str)?;
81 let min_balance = FUND_WITH_ACTIVE_ADDRESS_FUNDING_VALUE + FUND_WITH_ACTIVE_ADDRESS_FUNDING_TX_BUDGET;
82 let matching = parsed.into_iter().find(|coin| coin.nanos_balance >= min_balance);
83 let Some(coin_to_use) = matching else {
84 anyhow::bail!("Failed to find coin object with enough funds to transfer to test account");
85 };
86
87 let address_string = address.to_string();
88 let output = Command::new("iota")
89 .arg("client")
90 .arg("pay-iota")
91 .arg("--recipients")
92 .arg(&address_string)
93 .arg("--input-coins")
94 .arg(coin_to_use.gas_coin_id.to_string())
95 .arg("--amounts")
96 .arg(FUND_WITH_ACTIVE_ADDRESS_FUNDING_VALUE.to_string())
97 .arg("--gas-budget")
98 .arg(FUND_WITH_ACTIVE_ADDRESS_FUNDING_TX_BUDGET.to_string())
99 .arg("--json")
100 .output()
101 .await
102 .context("Failed to execute command")?;
103 unpack_command_output(&output, &format!("send funds from active account to {address_string}"))?;
104 }
105
106 Ok(())
107}