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