1use crate::api::api;
9use crate::error::BittensorError;
10use crate::extrinsics::ExtrinsicResponse;
11use subxt::OnlineClient;
12use subxt::PolkadotConfig;
13use tracing::{debug, warn};
14
15#[derive(Debug, Clone, Default)]
17pub struct SubnetIdentity {
18 pub name: String,
20 pub github_repo: String,
22 pub contact: String,
24 pub description: String,
26 pub url: String,
28 pub discord: String,
30 pub logo_url: String,
32 pub additional: String,
34}
35
36impl SubnetIdentity {
37 pub fn new(name: impl Into<String>) -> Self {
48 Self {
49 name: name.into(),
50 ..Default::default()
51 }
52 }
53
54 pub fn with_github(mut self, repo: impl Into<String>) -> Self {
56 self.github_repo = repo.into();
57 self
58 }
59
60 pub fn with_contact(mut self, contact: impl Into<String>) -> Self {
62 self.contact = contact.into();
63 self
64 }
65
66 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
68 self.description = desc.into();
69 self
70 }
71
72 pub fn with_url(mut self, url: impl Into<String>) -> Self {
74 self.url = url.into();
75 self
76 }
77
78 pub fn with_discord(mut self, discord: impl Into<String>) -> Self {
80 self.discord = discord.into();
81 self
82 }
83
84 pub fn with_logo(mut self, logo: impl Into<String>) -> Self {
86 self.logo_url = logo.into();
87 self
88 }
89}
90
91pub async fn register_network<S>(
112 client: &OnlineClient<PolkadotConfig>,
113 signer: &S,
114) -> Result<ExtrinsicResponse<u16>, BittensorError>
115where
116 S: subxt::tx::Signer<PolkadotConfig>,
117{
118 let call = api::tx()
119 .subtensor_module()
120 .register_network(signer.account_id());
121
122 debug!("Submitting register_network transaction");
123
124 let tx_progress = client
126 .tx()
127 .sign_and_submit_then_watch_default(&call, signer)
128 .await
129 .map_err(|e| BittensorError::TxSubmissionError {
130 message: format!("Failed to submit register_network: {}", e),
131 })?;
132
133 let tx_hash = tx_progress.extrinsic_hash();
134 debug!("Transaction submitted with hash: {:?}", tx_hash);
135
136 let tx_events = tx_progress
138 .wait_for_finalized_success()
139 .await
140 .map_err(|e| {
141 warn!("Transaction finalization failed: {}", e);
142 BittensorError::TxFinalizationError {
143 reason: format!("register_network transaction failed: {}", e),
144 }
145 })?;
146
147 debug!("Transaction finalized successfully");
148
149 let network_added_event = tx_events
152 .find_first::<api::subtensor_module::events::NetworkAdded>()
153 .map_err(|e| {
154 warn!("Failed to decode NetworkAdded event: {}", e);
155 BittensorError::ChainError {
156 message: format!("Failed to decode NetworkAdded event: {}", e),
157 }
158 })?;
159
160 match network_added_event {
161 Some(event) => {
162 let netuid = event.0;
163 debug!(
164 "NetworkAdded event found: netuid={}, modality={}",
165 netuid, event.1
166 );
167 Ok(ExtrinsicResponse::success()
168 .with_message("Network registered successfully")
169 .with_extrinsic_hash(&format!("{:?}", tx_hash))
170 .with_data(netuid))
171 }
172 None => {
173 warn!("NetworkAdded event not found in transaction events");
174 for event in tx_events.iter().flatten() {
176 debug!(
177 "Event found: {}::{}",
178 event.pallet_name(),
179 event.variant_name()
180 );
181 }
182 Err(BittensorError::ChainError {
183 message: "NetworkAdded event not found - network may not have been registered"
184 .to_string(),
185 })
186 }
187 }
188}
189
190pub async fn register_network_with_identity<S>(
212 client: &OnlineClient<PolkadotConfig>,
213 signer: &S,
214 identity: SubnetIdentity,
215) -> Result<ExtrinsicResponse<u16>, BittensorError>
216where
217 S: subxt::tx::Signer<PolkadotConfig>,
218{
219 let subnet_name = identity.name.clone();
221
222 let api_identity = api::runtime_types::pallet_subtensor::pallet::SubnetIdentityV3 {
224 subnet_name: identity.name.into_bytes(),
225 github_repo: identity.github_repo.into_bytes(),
226 subnet_contact: identity.contact.into_bytes(),
227 subnet_url: identity.url.into_bytes(),
228 discord: identity.discord.into_bytes(),
229 description: identity.description.into_bytes(),
230 logo_url: identity.logo_url.into_bytes(),
231 additional: identity.additional.into_bytes(),
232 };
233
234 let call = api::tx()
235 .subtensor_module()
236 .register_network_with_identity(signer.account_id(), Some(api_identity));
237
238 debug!(
239 "Submitting register_network_with_identity transaction for '{}'",
240 subnet_name
241 );
242
243 let tx_progress = client
245 .tx()
246 .sign_and_submit_then_watch_default(&call, signer)
247 .await
248 .map_err(|e| BittensorError::TxSubmissionError {
249 message: format!("Failed to submit register_network_with_identity: {}", e),
250 })?;
251
252 let tx_hash = tx_progress.extrinsic_hash();
253 debug!("Transaction submitted with hash: {:?}", tx_hash);
254
255 let tx_events = tx_progress
257 .wait_for_finalized_success()
258 .await
259 .map_err(|e| {
260 warn!("Transaction finalization failed: {}", e);
261 BittensorError::TxFinalizationError {
262 reason: format!("register_network_with_identity transaction failed: {}", e),
263 }
264 })?;
265
266 debug!("Transaction finalized successfully");
267
268 let network_added_event = tx_events
270 .find_first::<api::subtensor_module::events::NetworkAdded>()
271 .map_err(|e| {
272 warn!("Failed to decode NetworkAdded event: {}", e);
273 BittensorError::ChainError {
274 message: format!("Failed to decode NetworkAdded event: {}", e),
275 }
276 })?;
277
278 match network_added_event {
279 Some(event) => {
280 let netuid = event.0;
281 debug!(
282 "NetworkAdded event found: netuid={}, modality={}",
283 netuid, event.1
284 );
285 Ok(ExtrinsicResponse::success()
286 .with_message("Network registered with identity successfully")
287 .with_extrinsic_hash(&format!("{:?}", tx_hash))
288 .with_data(netuid))
289 }
290 None => {
291 warn!("NetworkAdded event not found in transaction events");
292 for event in tx_events.iter().flatten() {
294 debug!(
295 "Event found: {}::{}",
296 event.pallet_name(),
297 event.variant_name()
298 );
299 }
300 Err(BittensorError::ChainError {
301 message: "NetworkAdded event not found - network may not have been registered"
302 .to_string(),
303 })
304 }
305 }
306}
307
308pub async fn set_subnet_identity<S>(
317 client: &OnlineClient<PolkadotConfig>,
318 signer: &S,
319 netuid: u16,
320 identity: SubnetIdentity,
321) -> Result<ExtrinsicResponse<()>, BittensorError>
322where
323 S: subxt::tx::Signer<PolkadotConfig>,
324{
325 let call = api::tx().subtensor_module().set_subnet_identity(
326 netuid,
327 identity.name.into_bytes(),
328 identity.github_repo.into_bytes(),
329 identity.contact.into_bytes(),
330 identity.url.into_bytes(),
331 identity.discord.into_bytes(),
332 identity.description.into_bytes(),
333 identity.logo_url.into_bytes(),
334 identity.additional.into_bytes(),
335 );
336
337 let tx_hash = client
338 .tx()
339 .sign_and_submit_default(&call, signer)
340 .await
341 .map_err(|e| BittensorError::TxSubmissionError {
342 message: format!("Failed to set subnet identity: {}", e),
343 })?;
344
345 Ok(ExtrinsicResponse::success()
346 .with_message("Subnet identity updated")
347 .with_extrinsic_hash(&format!("{:?}", tx_hash))
348 .with_data(()))
349}
350
351pub async fn root_register<S>(
361 client: &OnlineClient<PolkadotConfig>,
362 signer: &S,
363 hotkey: crate::AccountId,
364) -> Result<ExtrinsicResponse<()>, BittensorError>
365where
366 S: subxt::tx::Signer<PolkadotConfig>,
367{
368 let call = api::tx().subtensor_module().root_register(hotkey);
369
370 let tx_hash = client
371 .tx()
372 .sign_and_submit_default(&call, signer)
373 .await
374 .map_err(|e| BittensorError::TxSubmissionError {
375 message: format!("Failed to root register: {}", e),
376 })?;
377
378 Ok(ExtrinsicResponse::success()
379 .with_message("Root registration successful")
380 .with_extrinsic_hash(&format!("{:?}", tx_hash))
381 .with_data(()))
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn test_subnet_identity_new() {
390 let identity = SubnetIdentity::new("Test Subnet");
391 assert_eq!(identity.name, "Test Subnet");
392 assert!(identity.github_repo.is_empty());
393 }
394
395 #[test]
396 fn test_subnet_identity_builder() {
397 let identity = SubnetIdentity::new("My Subnet")
398 .with_github("https://github.com/example/subnet")
399 .with_contact("admin@example.com")
400 .with_description("A test subnet")
401 .with_url("https://example.com")
402 .with_discord("abc123")
403 .with_logo("https://example.com/logo.png");
404
405 assert_eq!(identity.name, "My Subnet");
406 assert_eq!(identity.github_repo, "https://github.com/example/subnet");
407 assert_eq!(identity.contact, "admin@example.com");
408 assert_eq!(identity.description, "A test subnet");
409 assert_eq!(identity.url, "https://example.com");
410 assert_eq!(identity.discord, "abc123");
411 assert_eq!(identity.logo_url, "https://example.com/logo.png");
412 }
413
414 #[test]
415 fn test_subnet_identity_default() {
416 let identity = SubnetIdentity::default();
417 assert!(identity.name.is_empty());
418 assert!(identity.github_repo.is_empty());
419 }
420
421 #[test]
422 fn test_subnet_identity_clone() {
423 let identity = SubnetIdentity::new("Test").with_github("https://github.com/test");
424 let cloned = identity.clone();
425 assert_eq!(identity.name, cloned.name);
426 assert_eq!(identity.github_repo, cloned.github_repo);
427 }
428
429 #[test]
430 fn test_subnet_identity_debug() {
431 let identity = SubnetIdentity::new("Test");
432 let debug = format!("{:?}", identity);
433 assert!(debug.contains("SubnetIdentity"));
434 assert!(debug.contains("Test"));
435 }
436}