bittensor_rs/extrinsics/
children.rs

1//! # Children Extrinsics
2//!
3//! Extrinsics for managing child hotkeys on the Bittensor network:
4//! - `set_children`: Set child hotkeys with proportions
5//! - `set_childkey_take`: Set the take rate for child keys
6
7use crate::api::api;
8use crate::error::BittensorError;
9use crate::extrinsics::ExtrinsicResponse;
10use crate::AccountId;
11use subxt::OnlineClient;
12use subxt::PolkadotConfig;
13
14/// A child hotkey with its proportion of stake/rewards
15#[derive(Debug, Clone)]
16pub struct ChildKey {
17    /// Child hotkey proportion (0-u64::MAX, where u64::MAX = 100%)
18    pub proportion: u64,
19    /// Child hotkey account ID
20    pub child: AccountId,
21}
22
23impl ChildKey {
24    /// Create a new child key entry
25    ///
26    /// # Arguments
27    ///
28    /// * `child` - The child hotkey account ID
29    /// * `proportion` - The proportion (0.0 to 1.0 will be converted to u64)
30    ///
31    /// # Example
32    ///
33    /// ```
34    /// use bittensor_rs::extrinsics::ChildKey;
35    /// use subxt::utils::AccountId32;
36    ///
37    /// let child = AccountId32::from([1u8; 32]);
38    /// let child_key = ChildKey::new(child, 0.5); // 50% proportion
39    /// ```
40    pub fn new(child: AccountId, proportion: f64) -> Self {
41        let proportion = (proportion.clamp(0.0, 1.0) * u64::MAX as f64) as u64;
42        Self { proportion, child }
43    }
44
45    /// Create with raw proportion value
46    pub fn new_raw(child: AccountId, proportion: u64) -> Self {
47        Self { proportion, child }
48    }
49}
50
51/// Parameters for setting children
52#[derive(Debug, Clone)]
53pub struct SetChildrenParams {
54    /// The subnet netuid
55    pub netuid: u16,
56    /// List of child hotkeys with their proportions
57    pub children: Vec<ChildKey>,
58}
59
60impl SetChildrenParams {
61    /// Create new set children params
62    pub fn new(netuid: u16) -> Self {
63        Self {
64            netuid,
65            children: Vec::new(),
66        }
67    }
68
69    /// Add a child hotkey
70    pub fn with_child(mut self, child: AccountId, proportion: f64) -> Self {
71        self.children.push(ChildKey::new(child, proportion));
72        self
73    }
74
75    /// Add multiple children at once
76    pub fn with_children(mut self, children: Vec<ChildKey>) -> Self {
77        self.children.extend(children);
78        self
79    }
80}
81
82/// Set child hotkeys for the signing hotkey
83///
84/// Child hotkeys receive a portion of the parent's stake/rewards.
85/// The proportions should sum to <= 1.0 (100%).
86///
87/// # Arguments
88///
89/// * `client` - The subxt client
90/// * `signer` - The signer (parent hotkey's coldkey)
91/// * `hotkey` - The parent hotkey
92/// * `params` - Children configuration
93pub async fn set_children<S>(
94    client: &OnlineClient<PolkadotConfig>,
95    signer: &S,
96    hotkey: AccountId,
97    params: SetChildrenParams,
98) -> Result<ExtrinsicResponse<()>, BittensorError>
99where
100    S: subxt::tx::Signer<PolkadotConfig>,
101{
102    let children: Vec<(u64, AccountId)> = params
103        .children
104        .into_iter()
105        .map(|c| (c.proportion, c.child))
106        .collect();
107
108    let call = api::tx()
109        .subtensor_module()
110        .set_children(hotkey, params.netuid, children);
111
112    let tx_hash = client
113        .tx()
114        .sign_and_submit_default(&call, signer)
115        .await
116        .map_err(|e| BittensorError::TxSubmissionError {
117            message: format!("Failed to set children: {}", e),
118        })?;
119
120    Ok(ExtrinsicResponse::success()
121        .with_message("Children set successfully")
122        .with_extrinsic_hash(&format!("{:?}", tx_hash))
123        .with_data(()))
124}
125
126/// Set the take rate for child keys
127///
128/// This sets the percentage that the parent hotkey takes from child rewards.
129///
130/// # Arguments
131///
132/// * `client` - The subxt client
133/// * `signer` - The signer (hotkey's coldkey)
134/// * `hotkey` - The hotkey setting the take
135/// * `netuid` - The subnet netuid
136/// * `take` - The take rate (0.0 to 1.0)
137pub async fn set_childkey_take<S>(
138    client: &OnlineClient<PolkadotConfig>,
139    signer: &S,
140    hotkey: AccountId,
141    netuid: u16,
142    take: f64,
143) -> Result<ExtrinsicResponse<()>, BittensorError>
144where
145    S: subxt::tx::Signer<PolkadotConfig>,
146{
147    // Convert 0.0-1.0 to u16 (0-65535 where 65535 = 100%)
148    let take_u16 = (take.clamp(0.0, 1.0) * u16::MAX as f64) as u16;
149
150    let call = api::tx()
151        .subtensor_module()
152        .set_childkey_take(hotkey, netuid, take_u16);
153
154    let tx_hash = client
155        .tx()
156        .sign_and_submit_default(&call, signer)
157        .await
158        .map_err(|e| BittensorError::TxSubmissionError {
159            message: format!("Failed to set childkey take: {}", e),
160        })?;
161
162    Ok(ExtrinsicResponse::success()
163        .with_message("Childkey take set successfully")
164        .with_extrinsic_hash(&format!("{:?}", tx_hash))
165        .with_data(()))
166}
167
168/// Revoke all children for a hotkey on a subnet
169///
170/// This is equivalent to setting an empty children list.
171pub async fn revoke_children<S>(
172    client: &OnlineClient<PolkadotConfig>,
173    signer: &S,
174    hotkey: AccountId,
175    netuid: u16,
176) -> Result<ExtrinsicResponse<()>, BittensorError>
177where
178    S: subxt::tx::Signer<PolkadotConfig>,
179{
180    let children: Vec<(u64, AccountId)> = Vec::new();
181
182    let call = api::tx()
183        .subtensor_module()
184        .set_children(hotkey, netuid, children);
185
186    let tx_hash = client
187        .tx()
188        .sign_and_submit_default(&call, signer)
189        .await
190        .map_err(|e| BittensorError::TxSubmissionError {
191            message: format!("Failed to revoke children: {}", e),
192        })?;
193
194    Ok(ExtrinsicResponse::success()
195        .with_message("Children revoked successfully")
196        .with_extrinsic_hash(&format!("{:?}", tx_hash))
197        .with_data(()))
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use subxt::utils::AccountId32;
204
205    #[test]
206    fn test_child_key_new() {
207        let child = AccountId32::from([1u8; 32]);
208        let key = ChildKey::new(child.clone(), 0.5);
209        assert_eq!(key.child, child);
210        // 0.5 * u64::MAX should be approximately half of u64::MAX
211        let third = u64::MAX / 3;
212        let two_thirds = u64::MAX / 3 * 2;
213        assert!(key.proportion > third);
214        assert!(key.proportion < two_thirds);
215    }
216
217    #[test]
218    fn test_child_key_clamping() {
219        let child = AccountId32::from([1u8; 32]);
220
221        // Test negative proportion clamped to 0
222        let key = ChildKey::new(child.clone(), -0.5);
223        assert_eq!(key.proportion, 0);
224
225        // Test proportion > 1 clamped to max
226        let key = ChildKey::new(child, 1.5);
227        assert_eq!(key.proportion, u64::MAX);
228    }
229
230    #[test]
231    fn test_child_key_raw() {
232        let child = AccountId32::from([1u8; 32]);
233        let key = ChildKey::new_raw(child.clone(), 12345);
234        assert_eq!(key.proportion, 12345);
235    }
236
237    #[test]
238    fn test_set_children_params() {
239        let child1 = AccountId32::from([1u8; 32]);
240        let child2 = AccountId32::from([2u8; 32]);
241
242        let params = SetChildrenParams::new(1)
243            .with_child(child1.clone(), 0.3)
244            .with_child(child2.clone(), 0.2);
245
246        assert_eq!(params.netuid, 1);
247        assert_eq!(params.children.len(), 2);
248    }
249
250    #[test]
251    fn test_set_children_params_with_children() {
252        let child1 = AccountId32::from([1u8; 32]);
253        let child2 = AccountId32::from([2u8; 32]);
254
255        let children = vec![ChildKey::new(child1, 0.5), ChildKey::new(child2, 0.5)];
256
257        let params = SetChildrenParams::new(1).with_children(children);
258        assert_eq!(params.children.len(), 2);
259    }
260
261    #[test]
262    fn test_child_key_clone() {
263        let child = AccountId32::from([1u8; 32]);
264        let key = ChildKey::new(child, 0.5);
265        let cloned = key.clone();
266        assert_eq!(key.proportion, cloned.proportion);
267        assert_eq!(key.child, cloned.child);
268    }
269
270    #[test]
271    fn test_child_key_debug() {
272        let child = AccountId32::from([1u8; 32]);
273        let key = ChildKey::new(child, 0.5);
274        let debug = format!("{:?}", key);
275        assert!(debug.contains("ChildKey"));
276    }
277}