near_api/
stake.rs

1use std::collections::BTreeMap;
2
3use near_api_types::{
4    stake::{RewardFeeFraction, StakingPoolInfo, UserStakeBalance},
5    AccountId, Data, EpochReference, NearGas, NearToken, Reference,
6};
7use near_openapi_client::types::{RpcQueryError, RpcQueryResponse};
8
9use crate::{
10    advanced::{
11        query_request::QueryRequest, query_rpc::SimpleQueryRpc, validator_rpc::SimpleValidatorRpc,
12        ResponseHandler, RpcBuilder,
13    },
14    common::{
15        query::{
16            CallResultHandler, MultiQueryHandler, MultiRequestBuilder, PostprocessHandler,
17            RequestBuilder, RpcType, RpcValidatorHandler, ViewStateHandler,
18        },
19        utils::{from_base64, near_data_to_near_token, to_base64},
20    },
21    config::RetryResponse,
22    contract::Contract,
23    errors::{QueryCreationError, QueryError, SendRequestError},
24    transactions::ConstructTransaction,
25    NetworkConfig,
26};
27
28/// A wrapper struct that simplifies interactions with the [Staking Pool](https://github.com/near/core-contracts/tree/master/staking-pool) standard on behalf of the account.
29///
30/// Delegation is a wrapper that provides the functionality to manage user account stake in
31/// the staking pool.
32#[derive(Clone, Debug)]
33pub struct Delegation(pub AccountId);
34
35impl Delegation {
36    /// Returns the underlying account ID for this delegation.
37    ///
38    /// # Example
39    /// ```rust,no_run
40    /// use near_api::*;
41    ///
42    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
43    /// let delegation = Staking::delegation("alice.testnet".parse()?);
44    /// let account_id = delegation.account_id();
45    /// println!("Account ID: {}", account_id);
46    /// # Ok(())
47    /// # }
48    /// ```
49    pub const fn account_id(&self) -> &AccountId {
50        &self.0
51    }
52
53    /// Converts this delegation to an Account for account-related operations.
54    ///
55    /// # Example
56    /// ```rust,no_run
57    /// use near_api::*;
58    ///
59    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
60    /// let delegation = Staking::delegation("alice.testnet".parse()?);
61    /// let account = delegation.as_account();
62    /// let account_info = account.view().fetch_from_testnet().await?;
63    /// println!("Account balance: {}", account_info.data.amount);
64    /// # Ok(())
65    /// # }
66    /// ```
67    pub fn as_account(&self) -> crate::account::Account {
68        crate::account::Account(self.0.clone())
69    }
70
71    /// Prepares a new contract query (`get_account_staked_balance`) for fetching the staked balance ([NearToken]) of the account on the staking pool.
72    ///
73    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
74    ///
75    /// ## Example
76    /// ```rust,no_run
77    /// use near_api::*;
78    ///
79    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
80    /// let balance = Staking::delegation("alice.testnet".parse()?)
81    ///     .view_staked_balance("pool.testnet".parse()?)
82    ///     .fetch_from_testnet()
83    ///     .await?;
84    /// println!("Staked balance: {:?}", balance);
85    /// # Ok(())
86    /// # }
87    /// ```
88    pub fn view_staked_balance(
89        &self,
90        pool: AccountId,
91    ) -> RequestBuilder<PostprocessHandler<NearToken, CallResultHandler<u128>>> {
92        Contract(pool)
93            .call_function(
94                "get_account_staked_balance",
95                serde_json::json!({
96                    "account_id": self.0.clone(),
97                }),
98            )
99            .read_only()
100            .map(near_data_to_near_token)
101    }
102
103    /// Prepares a new contract query (`get_account_unstaked_balance`) for fetching the unstaked(free, not used for staking) balance ([NearToken]) of the account on the staking pool.
104    ///
105    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
106    ///
107    /// ## Example
108    /// ```rust,no_run
109    /// use near_api::*;
110    ///
111    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
112    /// let balance = Staking::delegation("alice.testnet".parse()?)
113    ///     .view_unstaked_balance("pool.testnet".parse()?)
114    ///     .fetch_from_testnet()
115    ///     .await?;
116    /// println!("Unstaked balance: {:?}", balance);
117    /// # Ok(())
118    /// # }
119    /// ```
120    pub fn view_unstaked_balance(
121        &self,
122        pool: AccountId,
123    ) -> RequestBuilder<PostprocessHandler<NearToken, CallResultHandler<u128>>> {
124        Contract(pool)
125            .call_function(
126                "get_account_unstaked_balance",
127                serde_json::json!({
128                    "account_id": self.0.clone(),
129                }),
130            )
131            .read_only()
132            .map(near_data_to_near_token)
133    }
134
135    /// Prepares a new contract query (`get_account_total_balance`) for fetching the total balance ([NearToken]) of the account (free + staked) on the staking pool.
136    ///
137    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
138    ///
139    /// ## Example
140    /// ```rust,no_run
141    /// use near_api::*;
142    ///
143    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
144    /// let balance = Staking::delegation("alice.testnet".parse()?)
145    ///     .view_total_balance("pool.testnet".parse()?)
146    ///     .fetch_from_testnet()
147    ///     .await?;
148    /// println!("Total balance: {:?}", balance);
149    /// # Ok(())
150    /// # }
151    /// ```
152    pub fn view_total_balance(
153        &self,
154        pool: AccountId,
155    ) -> RequestBuilder<PostprocessHandler<NearToken, CallResultHandler<u128>>> {
156        Contract(pool)
157            .call_function(
158                "get_account_total_balance",
159                serde_json::json!({
160                    "account_id": self.0.clone(),
161                }),
162            )
163            .read_only()
164            .map(near_data_to_near_token)
165    }
166
167    /// Returns a full information about the staked balance ([UserStakeBalance]) of the account on the staking pool.
168    ///
169    /// This is a complex query that requires 3 calls (get_account_staked_balance, get_account_unstaked_balance, get_account_total_balance) to the staking pool contract.
170    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
171    ///
172    /// ## Example
173    /// ```rust,no_run
174    /// use near_api::*;
175    ///
176    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
177    /// let balance = Staking::delegation("alice.testnet".parse()?)
178    ///     .view_balance("pool.testnet".parse()?)
179    ///     .fetch_from_testnet()
180    ///     .await?;
181    /// println!("Balance: {:?}", balance);
182    /// # Ok(())
183    /// # }
184    /// ```
185    #[allow(clippy::type_complexity)]
186    pub fn view_balance(
187        &self,
188        pool: AccountId,
189    ) -> MultiRequestBuilder<
190        PostprocessHandler<
191            UserStakeBalance,
192            MultiQueryHandler<(
193                CallResultHandler<NearToken>,
194                CallResultHandler<NearToken>,
195                CallResultHandler<NearToken>,
196            )>,
197        >,
198    > {
199        let postprocess = MultiQueryHandler::default();
200
201        MultiRequestBuilder::new(postprocess, Reference::Optimistic)
202            .add_query_builder(self.view_staked_balance(pool.clone()))
203            .add_query_builder(self.view_unstaked_balance(pool.clone()))
204            .add_query_builder(self.view_total_balance(pool))
205            .map(
206                |(staked, unstaked, total): (Data<NearToken>, Data<NearToken>, Data<NearToken>)| {
207                    UserStakeBalance {
208                        staked: staked.data,
209                        unstaked: unstaked.data,
210                        total: total.data,
211                    }
212                },
213            )
214    }
215
216    /// Prepares a new contract query (`is_account_unstaked_balance_available`) for checking if the unstaked balance of the account is available for withdrawal.
217    ///
218    /// Some pools configures minimum withdrawal period in epochs, so the balance is not available for withdrawal immediately.
219    ///
220    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
221    ///
222    /// ## Example
223    /// ```rust,no_run
224    /// use near_api::*;
225    ///
226    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
227    /// let is_available = Staking::delegation("alice.testnet".parse()?)
228    ///     .is_account_unstaked_balance_available_for_withdrawal("pool.testnet".parse()?)
229    ///     .fetch_from_testnet()
230    ///     .await?;
231    /// println!("Is available: {:?}", is_available);
232    /// # Ok(())
233    /// # }
234    /// ```
235    pub fn is_account_unstaked_balance_available_for_withdrawal(
236        &self,
237        pool: AccountId,
238    ) -> RequestBuilder<CallResultHandler<bool>> {
239        Contract(pool)
240            .call_function(
241                "is_account_unstaked_balance_available",
242                serde_json::json!({
243                    "account_id": self.0.clone(),
244                }),
245            )
246            .read_only()
247    }
248
249    /// Prepares a new transaction contract call (`deposit`) for depositing funds into the staking pool.
250    /// Please note that your deposit is not staked, and it will be allocated as unstaked (free) balance.
251    ///
252    /// Please note that this call will deposit your account tokens into the contract, so you will not be able to use them for other purposes.
253    ///
254    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
255    ///
256    /// ## Example
257    /// ```rust,no_run
258    /// use near_api::*;
259    ///
260    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
261    /// let result = Staking::delegation("alice.testnet".parse()?)
262    ///     .deposit("pool.testnet".parse()?, NearToken::from_near(1))
263    ///     .with_signer(Signer::from_ledger()?)
264    ///     .send_to_testnet()
265    ///     .await?;
266    /// # Ok(())
267    /// # }
268    /// ```
269    pub fn deposit(&self, pool: AccountId, amount: NearToken) -> ConstructTransaction {
270        Contract(pool)
271            .call_function("deposit", ())
272            .transaction()
273            .gas(NearGas::from_tgas(50))
274            .deposit(amount)
275            .with_signer_account(self.0.clone())
276    }
277
278    /// Prepares a new transaction contract call (`deposit_and_stake`) for depositing funds into the staking pool and staking them.
279    ///
280    /// Please note that this call will deposit your account tokens into the contract, so you will not be able to use them for other purposes.
281    /// Also, after you have staked your funds, if you decide to withdraw them, you might need to wait for the two lockup period to end.
282    /// * Mandatory lockup before able to unstake
283    /// * Optional lockup before able to withdraw (depends on the pool configuration)
284    ///
285    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
286    ///
287    /// ## Example
288    /// ```rust,no_run
289    /// use near_api::*;
290    ///
291    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
292    /// let result = Staking::delegation("alice.testnet".parse()?)
293    ///     .deposit_and_stake("pool.testnet".parse()?, NearToken::from_near(1))
294    ///     .with_signer(Signer::from_ledger()?)
295    ///     .send_to_testnet()
296    ///     .await?;
297    /// # Ok(())
298    /// # }
299    /// ```
300    pub fn deposit_and_stake(&self, pool: AccountId, amount: NearToken) -> ConstructTransaction {
301        Contract(pool)
302            .call_function("deposit_and_stake", ())
303            .transaction()
304            .gas(NearGas::from_tgas(50))
305            .deposit(amount)
306            .with_signer_account(self.0.clone())
307    }
308
309    /// Prepares a new transaction contract call (`stake`) for staking funds into the staking pool.
310    ///
311    /// Please note that this call will use your unstaked balance. This means that you have to have enough balance already deposited into the contract.
312    /// This won't use your native account tokens, but just reallocate your balance inside the contract.
313    /// Please also be aware that once you have staked your funds, you might not be able to withdraw them until the lockup periods end.
314    /// * Mandatory lockup before able to unstake
315    /// * Optional lockup before able to withdraw (depends on the pool configuration)
316    ///
317    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
318    ///
319    /// ## Example
320    /// ```rust,no_run
321    /// use near_api::*;
322    ///
323    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
324    /// let result = Staking::delegation("alice.testnet".parse()?)
325    ///     .stake("pool.testnet".parse()?, NearToken::from_near(1))
326    ///     .with_signer(Signer::from_ledger()?)
327    ///     .send_to_testnet()
328    ///     .await?;
329    /// # Ok(())
330    /// # }
331    /// ```
332    pub fn stake(&self, pool: AccountId, amount: NearToken) -> ConstructTransaction {
333        let args = serde_json::json!({
334            "amount": amount,
335        });
336
337        Contract(pool)
338            .call_function("stake", args)
339            .transaction()
340            .gas(NearGas::from_tgas(50))
341            .with_signer_account(self.0.clone())
342    }
343
344    /// Prepares a new transaction contract call (`stake_all`) for staking all available unstaked balance into the staking pool.
345    ///
346    /// Please note that once you have staked your funds, you might not be able to withdraw them until the lockup periods end.
347    /// * Mandatory lockup before able to unstake
348    /// * Optional lockup before able to withdraw (depends on the pool configuration)
349    ///
350    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
351    ///
352    /// ## Example
353    /// ```rust,no_run
354    /// use near_api::*;
355    ///
356    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
357    /// Staking::delegation("alice.testnet".parse()?)
358    ///     .stake_all("pool.testnet".parse()?)
359    ///     .with_signer(Signer::from_ledger()?)
360    ///     .send_to_testnet()
361    ///     .await?;
362    /// # Ok(())
363    /// # }
364    /// ```
365    pub fn stake_all(&self, pool: AccountId) -> ConstructTransaction {
366        Contract(pool)
367            .call_function("stake_all", ())
368            .transaction()
369            .gas(NearGas::from_tgas(50))
370            .with_signer_account(self.0.clone())
371    }
372
373    /// Prepares a new transaction contract call (`unstake`) for unstaking funds and returning them to your unstaked balance.
374    ///
375    /// The contract will check if the minimum epoch height condition is met.
376    ///
377    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
378    ///
379    /// ## Example
380    /// ```rust,no_run
381    /// use near_api::*;
382    ///
383    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
384    /// let result = Staking::delegation("alice.testnet".parse()?)
385    ///     .unstake("pool.testnet".parse()?, NearToken::from_near(1))
386    ///     .with_signer(Signer::from_ledger()?)
387    ///     .send_to_testnet()
388    ///     .await?;
389    /// # Ok(())
390    /// # }
391    /// ```
392    pub fn unstake(&self, pool: AccountId, amount: NearToken) -> ConstructTransaction {
393        let args = serde_json::json!({
394            "amount": amount,
395        });
396
397        Contract(pool)
398            .call_function("unstake", args)
399            .transaction()
400            .gas(NearGas::from_tgas(50))
401            .with_signer_account(self.0.clone())
402    }
403
404    /// Prepares a new transaction contract call (`unstake_all`) for unstaking all available staked balance and returning them to your unstaked balance.
405    ///
406    /// The contract will check if the minimum epoch height condition is met.
407    ///
408    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
409    ///
410    /// ## Example
411    /// ```rust,no_run
412    /// use near_api::*;
413    ///
414    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
415    /// let result = Staking::delegation("alice.testnet".parse()?)
416    ///     .unstake_all("pool.testnet".parse()?)
417    ///     .with_signer(Signer::from_ledger()?)
418    ///     .send_to_testnet()
419    ///     .await?;
420    /// # Ok(())
421    /// # }
422    /// ```
423    pub fn unstake_all(&self, pool: AccountId) -> ConstructTransaction {
424        Contract(pool)
425            .call_function("unstake_all", ())
426            .transaction()
427            .gas(NearGas::from_tgas(50))
428            .with_signer_account(self.0.clone())
429    }
430
431    /// Prepares a new transaction contract call (`withdraw`) for withdrawing funds from the staking pool into your account.
432    ///
433    /// Some pools configures minimum withdrawal period in epochs, so the balance is not available for withdrawal immediately.
434    ///
435    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
436    ///
437    /// ## Example
438    /// ```rust,no_run
439    /// use near_api::*;
440    ///
441    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
442    /// let result = Staking::delegation("alice.testnet".parse()?)
443    ///     .withdraw("pool.testnet".parse()?, NearToken::from_near(1))
444    ///     .with_signer(Signer::from_ledger()?)
445    ///     .send_to_testnet()
446    ///     .await?;
447    /// # Ok(())
448    /// # }
449    /// ```
450    pub fn withdraw(&self, pool: AccountId, amount: NearToken) -> ConstructTransaction {
451        let args = serde_json::json!({
452            "amount": amount,
453        });
454
455        Contract(pool)
456            .call_function("withdraw", args)
457            .transaction()
458            .gas(NearGas::from_tgas(50))
459            .with_signer_account(self.0.clone())
460    }
461
462    /// Prepares a new transaction contract call (`withdraw_all`) for withdrawing all available staked balance from the staking pool into your account.
463    ///
464    /// Some pools configures minimum withdrawal period in epochs, so the balance is not available for withdrawal immediately.
465    ///
466    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
467    ///
468    /// ## Example
469    /// ```rust,no_run
470    /// use near_api::*;
471    ///
472    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
473    /// let result = Staking::delegation("alice.testnet".parse()?)
474    ///     .withdraw_all("pool.testnet".parse()?)
475    ///     .with_signer(Signer::from_ledger()?)
476    ///     .send_to_testnet()
477    ///     .await?;
478    /// # Ok(())
479    /// # }
480    /// ```
481    pub fn withdraw_all(&self, pool: AccountId) -> ConstructTransaction {
482        Contract(pool)
483            .call_function("withdraw_all", ())
484            .transaction()
485            .gas(NearGas::from_tgas(50))
486            .with_signer_account(self.0.clone())
487    }
488}
489
490/// Staking-related interactions with the NEAR Protocol and the staking pools.
491///
492/// The [`Staking`] struct provides methods to interact with NEAR staking, including querying staking pools, validators, and delegators,
493/// as well as delegating and withdrawing from staking pools.
494///
495/// # Examples
496///
497/// ```rust,no_run
498/// use near_api::*;
499///
500/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
501/// let staking_pools = Staking::active_staking_pools().fetch_from_testnet().await?;
502/// println!("Staking pools: {:?}", staking_pools);
503/// # Ok(())
504/// # }
505/// ```
506#[derive(Clone, Debug)]
507pub struct Staking {}
508
509impl Staking {
510    /// Returns a list of active staking pools ([std::collections::BTreeSet]<[AccountId]>]) by querying the staking pools factory contract.
511    ///
512    /// Please note that it might fail on the mainnet as the staking pool factory is super huge.
513    ///
514    /// ## Example
515    /// ```rust,no_run
516    /// use near_api::*;
517    ///
518    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
519    /// let staking_pools = Staking::active_staking_pools().fetch_from_testnet().await?;
520    /// println!("Staking pools: {:?}", staking_pools);
521    /// # Ok(())
522    /// # }
523    /// ```
524    pub fn active_staking_pools() -> RpcBuilder<ActiveStakingPoolQuery, ActiveStakingHandler> {
525        RpcBuilder::new(
526            ActiveStakingPoolQuery,
527            Reference::Optimistic,
528            ActiveStakingHandler,
529        )
530    }
531
532    /// Returns a list of validators and their stake ([near_api_types::RpcValidatorResponse]) for the current epoch.
533    ///
534    /// ## Example
535    /// ```rust,no_run
536    /// use near_api::*;
537    ///
538    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
539    /// let validators = Staking::epoch_validators_info().fetch_from_testnet().await?;
540    /// println!("Validators: {:?}", validators);
541    /// # Ok(())
542    /// # }
543    /// ```
544    pub fn epoch_validators_info() -> RequestBuilder<RpcValidatorHandler> {
545        RequestBuilder::new(
546            SimpleValidatorRpc,
547            EpochReference::Latest,
548            RpcValidatorHandler,
549        )
550    }
551
552    /// Returns a map of validators and their stake ([BTreeMap<AccountId, NearToken>]) for the current epoch.
553    ///
554    /// ## Example
555    /// ```rust,no_run
556    /// use near_api::*;
557    ///
558    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
559    /// let validators = Staking::validators_stake().fetch_from_testnet().await?;
560    /// println!("Validators: {:?}", validators);
561    /// # Ok(())
562    /// # }
563    /// ```
564    pub fn validators_stake(
565    ) -> RequestBuilder<PostprocessHandler<BTreeMap<AccountId, NearToken>, RpcValidatorHandler>>
566    {
567        RequestBuilder::new(
568            SimpleValidatorRpc,
569            EpochReference::Latest,
570            RpcValidatorHandler,
571        )
572        .map(|validator_response| {
573            validator_response
574                .current_proposals
575                .into_iter()
576                .map(|validator_stake_view| {
577                    (validator_stake_view.account_id, validator_stake_view.stake)
578                })
579                .chain(validator_response.current_validators.into_iter().map(
580                    |current_epoch_validator_info| {
581                        (
582                            current_epoch_validator_info.account_id,
583                            current_epoch_validator_info.stake,
584                        )
585                    },
586                ))
587                .chain(validator_response.next_validators.into_iter().map(
588                    |next_epoch_validator_info| {
589                        (
590                            next_epoch_validator_info.account_id,
591                            next_epoch_validator_info.stake,
592                        )
593                    },
594                ))
595                .collect::<BTreeMap<_, NearToken>>()
596        })
597    }
598
599    /// Prepares a new contract query (`get_reward_fee_fraction`) for fetching the reward fee fraction of the staking pool ([Data]<[RewardFeeFraction]>).
600    ///
601    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
602    ///
603    /// ## Example
604    /// ```rust,no_run
605    /// use near_api::*;
606    ///
607    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
608    /// let reward_fee = Staking::staking_pool_reward_fee("pool.testnet".parse()?)
609    /// .fetch_from_testnet().await?;
610    /// println!("Reward fee: {:?}", reward_fee);
611    /// # Ok(())
612    /// # }
613    /// ```
614    pub fn staking_pool_reward_fee(
615        pool: AccountId,
616    ) -> RequestBuilder<CallResultHandler<RewardFeeFraction>> {
617        Contract(pool)
618            .call_function("get_reward_fee_fraction", ())
619            .read_only()
620    }
621
622    /// Prepares a new contract query (`get_number_of_accounts`) for fetching the number of delegators of the staking pool ([Data]<[u64]>).
623    ///
624    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
625    ///
626    /// ## Example
627    /// ```rust,no_run
628    /// use near_api::*;
629    ///
630    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
631    /// let delegators = Staking::staking_pool_delegators("pool.testnet".parse()?)
632    ///     .fetch_from_testnet()
633    ///     .await?;
634    /// println!("Delegators: {:?}", delegators);
635    /// # Ok(())
636    /// # }
637    /// ```
638    pub fn staking_pool_delegators(pool: AccountId) -> RequestBuilder<CallResultHandler<u64>> {
639        Contract(pool)
640            .call_function("get_number_of_accounts", ())
641            .read_only()
642    }
643
644    /// Prepares a new contract query (`get_total_staked_balance`) for fetching the total stake of the staking pool ([NearToken]).
645    ///
646    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
647    ///
648    /// ## Example
649    /// ```rust,no_run
650    /// use near_api::*;
651    ///
652    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
653    /// let total_stake = Staking::staking_pool_total_stake("pool.testnet".parse()?)
654    ///     .fetch_from_testnet()
655    ///     .await?;
656    /// println!("Total stake: {:?}", total_stake);
657    /// # Ok(())
658    /// # }
659    /// ```
660    pub fn staking_pool_total_stake(
661        pool: AccountId,
662    ) -> RequestBuilder<PostprocessHandler<NearToken, CallResultHandler<u128>>> {
663        Contract(pool)
664            .call_function("get_total_staked_balance", ())
665            .read_only()
666            .map(near_data_to_near_token)
667    }
668
669    /// Returns a full information about the staking pool ([StakingPoolInfo]).
670    ///
671    /// This is a complex query that requires 3 calls (get_reward_fee_fraction, get_number_of_accounts, get_total_staked_balance) to the staking pool contract.
672    /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool)
673    ///
674    /// ## Example
675    /// ```rust,no_run
676    /// use near_api::*;
677    ///
678    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
679    /// let staking_pool_info = Staking::staking_pool_info("pool.testnet".parse()?)
680    ///     .fetch_from_testnet()
681    ///     .await?;
682    /// println!("Staking pool info: {:?}", staking_pool_info);
683    /// # Ok(())
684    /// # }
685    /// ```
686    #[allow(clippy::type_complexity)]
687    pub fn staking_pool_info(
688        pool: AccountId,
689    ) -> MultiRequestBuilder<
690        PostprocessHandler<
691            StakingPoolInfo,
692            MultiQueryHandler<(
693                CallResultHandler<RewardFeeFraction>,
694                CallResultHandler<u64>,
695                CallResultHandler<u128>,
696            )>,
697        >,
698    > {
699        let pool_clone = pool.clone();
700        let handler = MultiQueryHandler::new((
701            CallResultHandler::default(),
702            CallResultHandler::default(),
703            CallResultHandler::default(),
704        ));
705
706        MultiRequestBuilder::new(handler, Reference::Optimistic)
707            .add_query_builder(Self::staking_pool_reward_fee(pool.clone()))
708            .add_query_builder(Self::staking_pool_delegators(pool.clone()))
709            .add_query_builder(Self::staking_pool_total_stake(pool))
710            .map(move |(reward_fee, delegators, total_stake)| {
711                let total = near_data_to_near_token(total_stake);
712
713                StakingPoolInfo {
714                    validator_id: pool_clone.clone(),
715
716                    fee: Some(reward_fee.data),
717                    delegators: Some(delegators.data),
718                    stake: total,
719                }
720            })
721    }
722
723    /// Returns a new [`Delegation`] struct for interacting with the staking pool on behalf of the account.
724    pub const fn delegation(account_id: AccountId) -> Delegation {
725        Delegation(account_id)
726    }
727}
728
729#[derive(Clone, Debug)]
730pub struct ActiveStakingPoolQuery;
731
732#[async_trait::async_trait]
733impl RpcType for ActiveStakingPoolQuery {
734    type RpcReference = <SimpleQueryRpc as RpcType>::RpcReference;
735    type Response = <SimpleQueryRpc as RpcType>::Response;
736    type Error = <SimpleQueryRpc as RpcType>::Error;
737
738    async fn send_query(
739        &self,
740        client: &near_openapi_client::Client,
741        network: &NetworkConfig,
742        reference: &Reference,
743    ) -> RetryResponse<RpcQueryResponse, SendRequestError<RpcQueryError>> {
744        let Some(account_id) = network.staking_pools_factory_account_id.clone() else {
745            return RetryResponse::Critical(SendRequestError::RequestCreationError(
746                QueryCreationError::StakingPoolFactoryNotDefined,
747            ));
748        };
749
750        let request = QueryRequest::ViewState {
751            account_id,
752            prefix_base64: near_api_types::StoreKey(to_base64(b"se")),
753            include_proof: Some(false),
754        };
755
756        SimpleQueryRpc { request }
757            .send_query(client, network, reference)
758            .await
759    }
760}
761
762#[derive(Clone, Debug)]
763pub struct ActiveStakingHandler;
764
765#[async_trait::async_trait]
766impl ResponseHandler for ActiveStakingHandler {
767    type Query = ActiveStakingPoolQuery;
768    type Response = std::collections::BTreeSet<AccountId>;
769
770    fn process_response(
771        &self,
772        response: Vec<RpcQueryResponse>,
773    ) -> core::result::Result<Self::Response, QueryError<RpcQueryError>> {
774        let query_result = ViewStateHandler {}.process_response(response)?;
775
776        Ok(query_result
777            .data
778            .values
779            .into_iter()
780            .filter_map(|item| borsh::from_slice(&from_base64(&item.value).ok()?).ok())
781            .collect())
782    }
783}