near_api/
stake.rs

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