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}