cargo_tangle/command/
register.rs

1use blueprint_clients::tangle::client::OnlineClient;
2use blueprint_core::{debug, info};
3use blueprint_crypto::sp_core::SpSr25519;
4use blueprint_crypto::tangle_pair_signer::TanglePairSigner;
5use blueprint_keystore::backends::Backend;
6use blueprint_keystore::{Keystore, KeystoreConfig};
7use blueprint_runner::tangle::config::decompress_pubkey;
8use blueprint_tangle_extra::serde::new_bounded_string;
9use color_eyre::Result;
10use dialoguer::console::style;
11use tangle_subxt::subxt;
12use tangle_subxt::subxt::error::DispatchError;
13use tangle_subxt::subxt::tx::Signer;
14use tangle_subxt::tangle_testnet_runtime::api;
15use tangle_subxt::tangle_testnet_runtime::api::runtime_types::pallet_multi_asset_delegation as mad;
16
17/// Registers a blueprint.
18///
19/// # Arguments
20///
21/// * `ws_rpc_url` - WebSocket RPC URL for the Tangle Network
22/// * `blueprint_id` - ID of the blueprint to register
23/// * `keystore_uri` - URI for the keystore
24///
25/// # Errors
26///
27/// Returns an error if:
28/// * Failed to connect to the Tangle Network
29/// * Failed to sign or submit the transaction
30/// * Transaction failed
31/// * Missing ECDSA key
32///
33/// # Panics
34///
35/// Panics if:
36/// * Failed to create keystore
37/// * Failed to get keys from keystore
38pub async fn register(
39    ws_rpc_url: impl AsRef<str>,
40    blueprint_id: u64,
41    keystore_uri: String,
42    rpc_address: impl AsRef<str>,
43    // keystore_password: Option<String>, // TODO: Add keystore password support
44) -> Result<()> {
45    let client = OnlineClient::from_url(ws_rpc_url.as_ref()).await?;
46
47    let config = KeystoreConfig::new().fs_root(keystore_uri.clone());
48    let keystore = Keystore::new(config).expect("Failed to create keystore");
49    let public = keystore
50        .first_local::<SpSr25519>()
51        .map_err(|e| color_eyre::eyre::eyre!("Failed to get public key: {}", e))?;
52    let pair = keystore
53        .get_secret::<SpSr25519>(&public)
54        .map_err(|e| color_eyre::eyre::eyre!("Failed to get secret key: {}", e))?;
55    let signer = TanglePairSigner::new(pair.0);
56
57    // Get the account ID from the signer for display
58    let account_id = signer.account_id();
59    println!(
60        "{}",
61        style(format!(
62            "Starting registration process for Operator ID: {}",
63            account_id
64        ))
65        .cyan()
66    );
67
68    let ecdsa_public = keystore
69        .first_local::<blueprint_crypto::sp_core::SpEcdsa>()
70        .map_err(|e| color_eyre::eyre::eyre!("Missing ECDSA key: {}", e))?;
71
72    let preferences =
73        tangle_subxt::tangle_testnet_runtime::api::services::calls::types::register::Preferences {
74            key: decompress_pubkey(&ecdsa_public.0.0).unwrap(),
75            rpc_address: new_bounded_string(rpc_address.as_ref()),
76        };
77
78    info!("Joining operators...");
79    let join_call = api::tx()
80        .multi_asset_delegation()
81        .join_operators(1_000_000_000_000_000);
82    let join_res = client
83        .tx()
84        .sign_and_submit_then_watch_default(&join_call, &signer)
85        .await?;
86
87    // Wait for finalization instead of just in-block
88    match join_res.wait_for_finalized_success().await {
89        Ok(events) => {
90            info!("Successfully joined operators with events: {:?}", events);
91        }
92        Err(e) => {
93            match e {
94                subxt::Error::Runtime(DispatchError::Module(module))
95                    if module.as_root_error::<api::Error>().is_ok_and(|e| {
96                        matches!(
97                            e,
98                            api::Error::MultiAssetDelegation(mad::pallet::Error::AlreadyOperator)
99                        )
100                    }) =>
101                {
102                    println!(
103                        "{}",
104                        style("Account is already an operator, skipping join step...").yellow()
105                    );
106                    info!(
107                        "Account {} is already an operator, continuing with registration",
108                        account_id
109                    );
110                }
111
112                _ => {
113                    // Re-throw any other error
114                    return Err(e.into());
115                }
116            }
117        }
118    }
119
120    println!(
121        "{}",
122        style(format!(
123            "PreRegistering for blueprint with ID: {}...",
124            blueprint_id
125        ))
126        .cyan()
127    );
128
129    let preregister_call = api::tx().services().pre_register(blueprint_id);
130    let preregister_res = client
131        .tx()
132        .sign_and_submit_then_watch_default(&preregister_call, &signer)
133        .await?;
134
135    // Wait for finalization instead of just in-block
136    let events = preregister_res.wait_for_finalized_success().await?;
137    info!(
138        "Successfully preregistered for blueprint with ID: {} with events: {:?}",
139        blueprint_id, events
140    );
141
142    println!(
143        "{}",
144        style(format!("Registering for blueprint ID: {}...", blueprint_id)).cyan()
145    );
146    let registration_args = tangle_subxt::tangle_testnet_runtime::api::services::calls::types::register::RegistrationArgs::new();
147    let register_call =
148        api::tx()
149            .services()
150            .register(blueprint_id, preferences, registration_args, 0);
151    let register_res = client
152        .tx()
153        .sign_and_submit_then_watch_default(&register_call, &signer)
154        .await?;
155
156    // Wait for finalization instead of just in-block
157    let events = register_res.wait_for_finalized_success().await?;
158    info!(
159        "Successfully registered for blueprint with ID: {} with events: {:?}",
160        blueprint_id, events
161    );
162
163    // Verify registration by querying the latest block
164    println!("{}", style("Verifying registration...").cyan());
165    let latest_block = client.blocks().at_latest().await?;
166    let latest_block_hash = latest_block.hash();
167    debug!("Latest block: {:?}", latest_block.number());
168
169    // Create a TangleServicesClient to query operator blueprints
170    let services_client =
171        blueprint_clients::tangle::services::TangleServicesClient::new(client.clone());
172
173    debug!("Querying blueprints for account: {:?}", account_id);
174
175    // Query operator blueprints at the latest block
176    let block_hash = latest_block_hash.0;
177    let blueprints = services_client
178        .query_operator_blueprints(block_hash, account_id.clone())
179        .await?;
180
181    info!("Found {} blueprints for operator", blueprints.len());
182    for (i, blueprint) in blueprints.iter().enumerate() {
183        info!("Blueprint {}: {:?}", i, blueprint);
184    }
185
186    println!("{}", style("Registration process completed").green());
187    Ok(())
188}