cargo_tangle/command/service/
request.rs

1use color_eyre::Result;
2use dialoguer::console::style;
3use blueprint_clients::tangle::client::OnlineClient;
4use blueprint_crypto::sp_core::SpSr25519;
5use blueprint_crypto::tangle_pair_signer::TanglePairSigner;
6use blueprint_keystore::{Keystore, KeystoreConfig};
7use tangle_subxt::tangle_testnet_runtime::api;
8use tangle_subxt::tangle_testnet_runtime::api::runtime_types::sp_arithmetic::per_things::Percent;
9use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::types::{
10    Asset, AssetSecurityRequirement, MembershipModel,
11};
12use blueprint_core::info;
13use crate::command::jobs::helpers::{load_job_args_from_file, prompt_for_job_params};
14use crate::wait_for_in_block_success;
15use tangle_subxt::subxt::utils::AccountId32;
16use blueprint_keystore::backends::Backend;
17
18/// Requests a service from the Tangle Network.
19///
20/// # Arguments
21///
22/// * `ws_rpc_url` - WebSocket RPC URL for the Tangle Network
23/// * `blueprint_id` - ID of the blueprint to request
24/// * `min_exposure_percent` - Minimum exposure percentage
25/// * `max_exposure_percent` - Maximum exposure percentage
26/// * `target_operators` - List of target operators
27/// * `value` - Value to stake
28/// * `keystore_uri` - URI for the keystore
29///
30/// # Errors
31///
32/// Returns an error if:
33/// * Failed to connect to the Tangle Network
34/// * Failed to sign or submit the transaction
35/// * Transaction failed
36///
37/// # Panics
38///
39/// Panics if:
40/// * Failed to create keystore
41/// * Failed to get keys from keystore
42#[allow(clippy::too_many_arguments)]
43pub async fn request_service(
44    ws_rpc_url: impl AsRef<str>,
45    blueprint_id: u64,
46    min_exposure_percent: u8,
47    max_exposure_percent: u8,
48    target_operators: Vec<AccountId32>,
49    value: u128,
50    keystore_uri: String,
51    params_file: Option<String>,
52    // keystore_password: Option<String>, // TODO: Add keystore password support
53) -> Result<()> {
54    let client = OnlineClient::from_url(ws_rpc_url.as_ref()).await?;
55
56    let config = KeystoreConfig::new().fs_root(keystore_uri.clone());
57    let keystore = Keystore::new(config).expect("Failed to create keystore");
58    let public = keystore.first_local::<SpSr25519>().unwrap();
59    let pair = keystore.get_secret::<SpSr25519>(&public).unwrap();
60    let signer = TanglePairSigner::new(pair.0);
61
62    let blueprint_key = api::storage().services().blueprints(blueprint_id);
63    let maybe_blueprint = client
64        .storage()
65        .at_latest()
66        .await?
67        .fetch(&blueprint_key)
68        .await?;
69    let Some((_blueprint_owner, blueprint)) = maybe_blueprint else {
70        return Err(color_eyre::eyre::eyre!(
71            "Blueprint ID {} not found",
72            blueprint_id
73        ));
74    };
75
76    let min_operators = u32::try_from(target_operators.len())
77        .map_err(|_| color_eyre::eyre::eyre!("Too many operators"))?;
78    let security_requirements = vec![AssetSecurityRequirement {
79        asset: Asset::Custom(0),
80        min_exposure_percent: Percent(min_exposure_percent),
81        max_exposure_percent: Percent(max_exposure_percent),
82    }];
83
84    println!(
85        "{}",
86        style(format!(
87            "Preparing service request for blueprint ID: {}",
88            blueprint_id
89        ))
90        .cyan()
91    );
92    println!(
93        "{}",
94        style(format!(
95            "Target operators: {} (min: {})",
96            target_operators.len(),
97            min_operators
98        ))
99        .dim()
100    );
101    println!(
102        "{}",
103        style(format!(
104            "Exposure range: {}% - {}%",
105            min_exposure_percent, max_exposure_percent
106        ))
107        .dim()
108    );
109
110    // Get request arguments either from file or prompt
111    let request_args = if blueprint.request_params.0.is_empty() {
112        // No request parameters, use default values
113        Vec::new()
114    } else if let Some(file_path) = params_file {
115        info!(
116            "Request params definition: {:?}",
117            blueprint.request_params.0
118        );
119
120        // Load arguments from file based on job definition
121        load_job_args_from_file(&file_path, &blueprint.request_params.0)?
122    } else {
123        info!(
124            "Request params definition: {:?}",
125            blueprint.request_params.0
126        );
127        // Prompt for each parameter based on its type
128        prompt_for_job_params(&blueprint.request_params.0)?
129    };
130
131    let call = api::tx().services().request(
132        None,
133        blueprint_id,
134        Vec::new(),
135        target_operators,
136        request_args,
137        security_requirements,
138        1000,
139        Asset::Custom(0),
140        value,
141        MembershipModel::Fixed { min_operators },
142    );
143
144    println!("{}", style("Submitting Service Request...").cyan());
145    let res = client
146        .tx()
147        .sign_and_submit_then_watch_default(&call, &signer)
148        .await?;
149    let events = wait_for_in_block_success(res).await;
150
151    let service_request_event = events
152        .find_first::<api::services::events::ServiceRequested>()
153        .map_err(|e| color_eyre::eyre::eyre!("Service request failed: {e}"))?
154        .ok_or_else(|| color_eyre::eyre::eyre!("Service request event not found"))?;
155
156    println!(
157        "{}",
158        style("Service Request submitted successfully").green()
159    );
160
161    println!(
162        "{}",
163        style(format!(
164            "Service Request ID: {}",
165            service_request_event.request_id
166        ))
167        .green()
168        .bold()
169    );
170
171    Ok(())
172}