From 95d086a2063954159ede308ba5c72491015ba4dd Mon Sep 17 00:00:00 2001 From: akorchyn Date: Mon, 27 Jan 2025 16:41:28 +0200 Subject: [PATCH 01/13] doc!: documented types * Documented types. * Bug-fixed viewing balance of the account by calculating manually storage cost --- cspell.json | 15 +++- examples/create_account_and_send_near.rs | 6 +- src/common/mod.rs | 1 - src/common/send.rs | 9 ++- src/tokens.rs | 15 ++-- src/types/contract.rs | 28 +++++++ src/types/mod.rs | 13 +++- src/types/reference.rs | 9 ++- .../signed_delegate_action.rs | 2 + src/types/stake.rs | 21 ++++++ src/types/storage.rs | 8 +- src/types/tokens.rs | 75 ++++++++++++++++++- src/types/transactions.rs | 4 + tests/account.rs | 14 ++-- 14 files changed, 189 insertions(+), 31 deletions(-) rename src/{common => types}/signed_delegate_action.rs (90%) diff --git a/cspell.json b/cspell.json index 500cc0e..40657a9 100644 --- a/cspell.json +++ b/cspell.json @@ -40,13 +40,22 @@ "zstd", "codeowners", "akorchyn", - "frol" + "frol", + "ipfs", + "nep" + ], + "ignoreWords": [ + "bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq", + "nep141", + "nep330", + "nep461", + "signer_id" ], - "ignoreWords": [], "ignoreRegExpList": [ // Base58 encoded public key/secret key "\"ed25519:[-A-Za-z0-9+/]*={0,3}\"", - "Email" + "Email", + "cid" ], "import": [] } diff --git a/examples/create_account_and_send_near.rs b/examples/create_account_and_send_near.rs index 904d490..e0f4ab4 100644 --- a/examples/create_account_and_send_near.rs +++ b/examples/create_account_and_send_near.rs @@ -15,7 +15,7 @@ async fn main() { .await .unwrap(); - println!("Balance: {}", balance.liquid); + println!("Balance: {}", balance.total); let new_account: AccountId = format!("{}.{}", "bob", account.id()).parse().unwrap(); let signer = Signer::new(Signer::from_workspace(&account)).unwrap(); @@ -48,7 +48,7 @@ async fn main() { .await .unwrap(); - println!("Balance: {}", new_account_balance.liquid); + println!("Balance: {}", new_account_balance.total); // Expect to see 2 NEAR in Bob's account. 1 NEAR from create_account and 1 NEAR from send_near - println!("Bob balance: {}", bob_balance.liquid); + println!("Bob balance: {}", bob_balance.total); } diff --git a/src/common/mod.rs b/src/common/mod.rs index 4a0ac70..c96ade4 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -4,5 +4,4 @@ const META_TRANSACTION_VALID_FOR_DEFAULT: BlockHeight = 1000; pub mod query; pub mod send; -pub mod signed_delegate_action; pub mod utils; diff --git a/src/common/send.rs b/src/common/send.rs index 63ba0da..8ce405e 100644 --- a/src/common/send.rs +++ b/src/common/send.rs @@ -18,12 +18,13 @@ use crate::{ ValidationError, }, signer::Signer, - types::{transactions::PrepopulateTransaction, CryptoHash}, + types::{ + signed_delegate_action::SignedDelegateActionAsBase64, transactions::PrepopulateTransaction, + CryptoHash, + }, }; -use super::{ - signed_delegate_action::SignedDelegateActionAsBase64, META_TRANSACTION_VALID_FOR_DEFAULT, -}; +use super::META_TRANSACTION_VALID_FOR_DEFAULT; const TX_EXECUTOR_TARGET: &str = "near_api::tx::executor"; const META_EXECUTOR_TARGET: &str = "near_api::meta::executor"; diff --git a/src/tokens.rs b/src/tokens.rs index b764dcb..90fd6a1 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -32,6 +32,8 @@ use crate::{ NetworkConfig, StorageDeposit, }; +const STORAGE_COST: NearToken = NearToken::from_yoctonear(10_000_000_000_000_000_000u128); + type Result = core::result::Result; // This is not too long as most of the size is a links to the docs @@ -102,7 +104,7 @@ type Result = core::result::Result; /// /// // Check NEAR balance /// let balance = alice_account.near_balance().fetch_from_testnet().await?; -/// println!("NEAR balance: {}", balance.liquid); +/// println!("NEAR balance: {}", balance.total); /// /// // Send NEAR /// alice_account.send_to("bob.testnet".parse()?) @@ -150,12 +152,15 @@ impl Tokens { AccountViewHandler, Box::new(|account: Data| { let account = account.data; - let liquid = NearToken::from_yoctonear(account.amount); - let locked = NearToken::from_yoctonear(account.locked); + let total = NearToken::from_yoctonear(account.amount); + // TODO: locked returns 0 always replace after the fix + let storage_locked = NearToken::from_yoctonear( + account.storage_usage as u128 * STORAGE_COST.as_yoctonear(), + ); let storage_usage = account.storage_usage; UserBalance { - liquid, - locked, + total, + storage_locked, storage_usage, } }), diff --git a/src/types/contract.rs b/src/types/contract.rs index e3a152e..7a45718 100644 --- a/src/types/contract.rs +++ b/src/types/contract.rs @@ -1,14 +1,42 @@ use serde::{Deserialize, Serialize}; +/// The struct provides information about deployed contract's source code and supported standards. +/// +/// Contract source metadata follows [NEP-330 standard](https://nomicon.io/Standards/SourceMetadata) for smart contract verification #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct ContractSourceMetadata { + /// Optional version identifier, typically a commit hash or semantic version + /// + /// Examples: + /// - Git commit: "39f2d2646f2f60e18ab53337501370dc02a5661c" + /// - Semantic version: "1.0.0" pub version: Option, + + // cSpell::ignore bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq + /// Optional URL to source code repository or IPFS CID + /// + /// Examples: + /// - GitHub URL: "https://github.com/near-examples/nft-tutorial" + /// - IPFS CID: "bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq" pub link: Option, + + /// List of supported NEAR standards (NEPs) with their versions + /// + /// Should include NEP-330 itself if implemented: + /// `Standard { standard: "nep330".into(), version: "1.1.0".into() }` pub standards: Vec, } +/// NEAR Standard implementation descriptor following [NEP-330](https://nomicon.io/Standards/SourceMetadata) #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct Standard { + /// Standard name in lowercase NEP format + /// + /// Example: "nep141" for fungible tokens pub standard: String, + + /// Implemented standard version using semantic versioning + /// + /// Example: "1.0.0" for initial release pub version: String, } diff --git a/src/types/mod.rs b/src/types/mod.rs index 30d67b7..4a4f3bd 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -7,11 +7,14 @@ use crate::errors::CryptoHashError; pub mod contract; pub mod reference; +pub mod signed_delegate_action; pub mod stake; pub mod storage; pub mod tokens; pub mod transactions; +/// A wrapper around a generic query result that includes the block height and block hash +/// at which the query was executed #[derive( Debug, Clone, @@ -21,11 +24,15 @@ pub mod transactions; borsh::BorshSerialize, )] pub struct Data { + /// The data returned by the query pub data: T, + /// The block height at which the query was executed pub block_height: BlockHeight, + /// The block hash at which the query was executed pub block_hash: CryptoHash, } +/// A wrapper around [near_jsonrpc_client::auth::ApiKey] #[derive(Eq, Hash, Clone, Debug, PartialEq)] pub struct ApiKey(pub near_jsonrpc_client::auth::ApiKey); @@ -73,8 +80,10 @@ fn from_base58(s: &str) -> Result, bs58::decode::Error> { bs58::decode(s).into_vec() } -// type taken from near_primitives::hash::CryptoHash. -/// CryptoHash is type for storing the hash of a specific block. +/// A type that represents a hash of the data. +/// +/// This type is copy of the [near_primitives::hash::CryptoHash](near_primitives::hash::CryptoHash) +/// as part of the [decoupling initiative](https://github.com/near/near-api-rs/issues/5) #[derive( Copy, Clone, diff --git a/src/types/reference.rs b/src/types/reference.rs index d3c0570..e08d357 100644 --- a/src/types/reference.rs +++ b/src/types/reference.rs @@ -3,7 +3,9 @@ use crate::types::CryptoHash; use near_primitives::types::{BlockHeight, EpochId}; -/// Finality of a transaction or block in which transaction is included in. For more info +/// A reference to a specific block. This type is used to specify the block for most queries. +/// +/// It represents the finality of a transaction or block in which transaction is included in. For more info /// go to the [NEAR finality](https://docs.near.org/docs/concepts/transaction#finality) docs. #[derive(Clone, Debug)] #[non_exhaustive] @@ -38,14 +40,15 @@ impl From for near_primitives::types::BlockReference { } } +/// A reference to a specific epoch. This type is used to specify the epoch for some queries. #[derive(Clone, Debug)] #[non_exhaustive] pub enum EpochReference { /// Reference to a specific Epoch Id AtEpoch(CryptoHash), - /// Reference to a specific block. + /// Reference to an epoch at a specific block height. AtBlock(BlockHeight), - /// Reference to a specific block hash. + /// Reference to an epoch at a specific block hash. AtBlockHash(CryptoHash), /// Latest epoch on the node Latest, diff --git a/src/common/signed_delegate_action.rs b/src/types/signed_delegate_action.rs similarity index 90% rename from src/common/signed_delegate_action.rs rename to src/types/signed_delegate_action.rs index 3bc0711..5d9cc7e 100644 --- a/src/common/signed_delegate_action.rs +++ b/src/types/signed_delegate_action.rs @@ -1,7 +1,9 @@ use near_primitives::{borsh, borsh::BorshDeserialize}; +/// A wrapper around `SignedDelegateAction` that allows for easy serialization and deserialization as base64 string #[derive(Debug, Clone)] pub struct SignedDelegateActionAsBase64 { + /// The inner signed delegate action pub inner: near_primitives::action::delegate::SignedDelegateAction, } diff --git a/src/types/stake.rs b/src/types/stake.rs index 5b37805..8511860 100644 --- a/src/types/stake.rs +++ b/src/types/stake.rs @@ -1,23 +1,44 @@ use near_token::NearToken; use serde::{Deserialize, Serialize}; +/// Aggregate information about the staking pool. +/// +/// The type is related to the [StakingPool](https://github.com/near/core-contracts/tree/master/staking-pool) smart contract. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct StakingPoolInfo { + /// The validator that is running the pool. pub validator_id: near_primitives::types::AccountId, + /// The fee that is taken by the pool contract. pub fee: Option, + /// The number of delegators on the pool. pub delegators: Option, + /// The total staked balance on the pool (by all delegators). pub stake: NearToken, } +/// The reward fee that is taken by the pool contract. +/// +/// This represents the percentage of the reward that is taken by the pool contract. +/// The type is a part of the [StakingPool](https://github.com/near/core-contracts/tree/master/staking-pool) interface #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct RewardFeeFraction { + /// The numerator of the fraction. pub numerator: u32, + /// The denominator of the fraction. pub denominator: u32, } +/// The total user balance on a pool contract +/// +/// The type is related to the [StakingPool](https://github.com/near/core-contracts/tree/master/staking-pool) smart contract. #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct UserStakeBalance { + /// The balance that currently is staked. The user can't withdraw this balance until `unstake` is called + /// and withdraw period is over. pub staked: NearToken, + /// The balance that is not staked. The user can start withdrawing this balance. Some pools + /// have a withdraw period. pub unstaked: NearToken, + /// The total balance of the user on a contract (staked + unstaked) pub total: NearToken, } diff --git a/src/types/storage.rs b/src/types/storage.rs index 09e9f3a..6c169c8 100644 --- a/src/types/storage.rs +++ b/src/types/storage.rs @@ -1,11 +1,17 @@ use near_sdk::NearToken; use serde::de::{Deserialize, Deserializer}; -// Taken from https://github.com/bos-cli-rs/near-socialdb-client-rs/blob/main/src/lib.rs +/// A type that represents the storage balance from the [NEP-145](https://nomicon.io/Standards/StorageManagement) standard +/// on some NEAR contract. #[derive(Debug, Clone, serde::Deserialize)] pub struct StorageBalance { + /// The available balance that might be used for storage. + /// + /// The user can withdraw this balance. #[serde(deserialize_with = "parse_u128_string")] pub available: NearToken, + /// The total user balance on the contract for storage. + /// This is a sum of the `available` and `locked` balances. #[serde(deserialize_with = "parse_u128_string")] pub total: NearToken, } diff --git a/src/types/tokens.rs b/src/types/tokens.rs index 483a0c7..2603503 100644 --- a/src/types/tokens.rs +++ b/src/types/tokens.rs @@ -10,6 +10,38 @@ pub const USDC_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(6, "USDC /// Static instance of [FTBalance] for wNEAR token with correct decimals and symbol. pub const W_NEAR_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(24, "wNEAR"); +/// A helper type that represents the fungible token balance with a given precision. +/// +/// The type is created to simplify the usage of fungible tokens in similar way as the [NearToken] type does. +/// +/// The symbol is used only for display purposes. +/// +/// # Examples +/// +/// ## Defining 2.5 USDT +/// ```rust +/// use near_api::USDT_BALANCE; +/// +/// let usdt_balance = USDT_BALANCE.with_float_str("2.5").unwrap(); +/// +/// assert_eq!(usdt_balance.amount(), 2_500_000); +/// ``` +/// +/// ## Defining 3 wNEAR using yoctoNEAR +/// ```rust +/// use near_api::{W_NEAR_BALANCE, NearToken}; +/// +/// let wnear_balance = W_NEAR_BALANCE.with_amount(3 * 10u128.pow(24)); +/// +/// assert_eq!(wnear_balance.amount(), NearToken::from_near(3).as_yoctonear()); +/// ``` +/// +/// ## Defining 3 wETH using 18 decimals +/// ```rust +/// use near_api::FTBalance; +/// +/// let weth_balance = FTBalance::with_decimals_and_symbol(18, "wETH").with_whole_amount(3); +/// ``` #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct FTBalance { balance: u128, @@ -18,6 +50,9 @@ pub struct FTBalance { } impl FTBalance { + /// Creates a new [FTBalance] with a given precision. + /// + /// The balance is initialized to 0. pub const fn with_decimals(decimals: u8) -> Self { Self { balance: 0, @@ -26,6 +61,9 @@ impl FTBalance { } } + /// Creates a new [FTBalance] with a given precision and symbol. + /// + /// The balance is initialized to 0. pub const fn with_decimals_and_symbol(decimals: u8, symbol: &'static str) -> Self { Self { balance: 0, @@ -34,6 +72,9 @@ impl FTBalance { } } + /// Stores the given amount without any transformations. + /// + /// The [NearToken] alternative is [NearToken::from_yoctonear]. pub const fn with_amount(&self, amount: u128) -> Self { Self { balance: amount, @@ -42,6 +83,9 @@ impl FTBalance { } } + /// Stores the number as an integer token value utilizing the given precision. + /// + /// The [NearToken] alternative is [NearToken::from_near]. pub const fn with_whole_amount(&self, amount: u128) -> Self { Self { balance: amount * 10u128.pow(self.decimals as u32), @@ -50,19 +94,38 @@ impl FTBalance { } } + /// Parses float string and stores the value in defined precision. + /// + /// # Examples + /// + /// ## Defining 2.5 USDT + /// ```rust + /// use near_api::USDT_BALANCE; + /// + /// let usdt_balance = USDT_BALANCE.with_float_str("2.515").unwrap(); + /// + /// assert_eq!(usdt_balance.amount(), 2_515_000); + /// ``` pub fn with_float_str(&self, float_str: &str) -> Result { crate::common::utils::parse_decimal_number(float_str, 10u128.pow(self.decimals as u32)) .map(|amount| self.with_amount(amount)) } + /// Returns the amount without any transformations. + /// + /// The [NearToken] alternative is [NearToken::as_yoctonear]. pub const fn amount(&self) -> u128 { self.balance } + /// Returns the amount as a whole number in the defined precision. + /// + /// The [NearToken] alternative is [NearToken::as_near]. pub const fn to_whole(&self) -> u128 { self.balance / 10u128.pow(self.decimals as u32) } + /// Returns the number of decimals used by the token. pub const fn decimals(&self) -> u8 { self.decimals } @@ -88,10 +151,18 @@ impl std::fmt::Display for FTBalance { } } +/// Account balance on the NEAR blockchain. +/// +/// This balance doesn't include staked NEAR tokens or storage #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct UserBalance { - pub liquid: NearToken, - pub locked: NearToken, + /// The total amount of NEAR tokens in the account. + /// + /// Please note that this is the total amount of NEAR tokens in the account, not the amount available for use. + pub total: NearToken, + /// The amount of NEAR tokens locked in the account for storage usage. + pub storage_locked: NearToken, + /// The storage usage by the account in bytes. pub storage_usage: u64, } diff --git a/src/types/transactions.rs b/src/types/transactions.rs index 5357826..ce74479 100644 --- a/src/types/transactions.rs +++ b/src/types/transactions.rs @@ -1,9 +1,13 @@ use near_primitives::{action::Action, types::AccountId}; use serde::{Deserialize, Serialize}; +/// An internal type that represents unsigned transaction. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PrepopulateTransaction { + /// The account that will sign the transaction. pub signer_id: AccountId, + /// The account that will receive the transaction pub receiver_id: AccountId, + /// The actions that will be executed by the transaction. pub actions: Vec, } diff --git a/tests/account.rs b/tests/account.rs index 7c921e8..719ff53 100644 --- a/tests/account.rs +++ b/tests/account.rs @@ -26,15 +26,15 @@ async fn create_and_delete_account() { .await .unwrap(); - assert_eq!(balance_before_del.liquid.as_near(), 1); + assert_eq!(balance_before_del.total.as_near(), 1); - dbg!(Account(account.id().clone()) + Account(account.id().clone()) .delete_account_with_beneficiary(new_account.clone()) .with_signer(Signer::new(Signer::from_workspace(&account)).unwrap()) .send_to(&network) .await - .unwrap()) - .assert_success(); + .unwrap() + .assert_success(); Tokens::account(account.id().clone()) .near_balance() @@ -50,7 +50,7 @@ async fn create_and_delete_account() { .fetch_from(&network) .await .unwrap(); - assert!(dbg!(balance_after_del).liquid > dbg!(balance_before_del).liquid); + assert!(balance_after_del.total > balance_before_del.total); } #[tokio::test] @@ -82,8 +82,8 @@ async fn transfer_funds() { .unwrap(); // it's actually 49.99 because of the fee - assert_eq!(alice_balance.liquid.as_near(), 49); - assert_eq!(bob_balance.liquid.as_near(), 150); + assert_eq!(alice_balance.total.as_near(), 49); + assert_eq!(bob_balance.total.as_near(), 150); } #[tokio::test] From 592477dd86270cf5310ac0e917e0dcd07e979f24 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Mon, 27 Jan 2025 16:43:24 +0200 Subject: [PATCH 02/13] cspell support --- cspell.json | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/cspell.json b/cspell.json index 40657a9..8e097a4 100644 --- a/cspell.json +++ b/cspell.json @@ -42,20 +42,14 @@ "akorchyn", "frol", "ipfs", - "nep" - ], - "ignoreWords": [ - "bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq", - "nep141", - "nep330", - "nep461", - "signer_id" + "nep", + "cid", + "wnear" ], "ignoreRegExpList": [ // Base58 encoded public key/secret key "\"ed25519:[-A-Za-z0-9+/]*={0,3}\"", "Email", - "cid" ], "import": [] } From a0d1548f1c7d6adbeef7d95abd39a40c100c1716 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Mon, 27 Jan 2025 16:49:53 +0200 Subject: [PATCH 03/13] cargo doc fix --- src/types/contract.rs | 2 +- src/types/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/contract.rs b/src/types/contract.rs index 7a45718..b677103 100644 --- a/src/types/contract.rs +++ b/src/types/contract.rs @@ -16,7 +16,7 @@ pub struct ContractSourceMetadata { /// Optional URL to source code repository or IPFS CID /// /// Examples: - /// - GitHub URL: "https://github.com/near-examples/nft-tutorial" + /// - GitHub URL: "" /// - IPFS CID: "bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq" pub link: Option, diff --git a/src/types/mod.rs b/src/types/mod.rs index 4a4f3bd..df8d1c8 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -82,7 +82,7 @@ fn from_base58(s: &str) -> Result, bs58::decode::Error> { /// A type that represents a hash of the data. /// -/// This type is copy of the [near_primitives::hash::CryptoHash](near_primitives::hash::CryptoHash) +/// This type is copy of the [near_primitives::hash::CryptoHash] /// as part of the [decoupling initiative](https://github.com/near/near-api-rs/issues/5) #[derive( Copy, From 98f0e642b69a618e262a8b27b2e3e2375770be95 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Tue, 28 Jan 2025 13:46:59 +0200 Subject: [PATCH 04/13] restored locked field --- src/tokens.rs | 3 ++- src/types/tokens.rs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tokens.rs b/src/tokens.rs index 90fd6a1..4401fed 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -153,15 +153,16 @@ impl Tokens { Box::new(|account: Data| { let account = account.data; let total = NearToken::from_yoctonear(account.amount); - // TODO: locked returns 0 always replace after the fix let storage_locked = NearToken::from_yoctonear( account.storage_usage as u128 * STORAGE_COST.as_yoctonear(), ); + let locked = NearToken::from_yoctonear(account.locked); let storage_usage = account.storage_usage; UserBalance { total, storage_locked, storage_usage, + locked, } }), ), diff --git a/src/types/tokens.rs b/src/types/tokens.rs index 2603503..88aba53 100644 --- a/src/types/tokens.rs +++ b/src/types/tokens.rs @@ -164,6 +164,12 @@ pub struct UserBalance { pub storage_locked: NearToken, /// The storage usage by the account in bytes. pub storage_usage: u64, + /// The amount of NEAR tokens staked on a protocol level. + /// Applicable for staking pools only in 99.99% of the cases. + /// + /// Please note that this is not related to your delegations into the staking pools. + /// To get your stake info into some pool, use [crate::Delegation] + pub locked: NearToken, } #[cfg(test)] From 8c887ed64377b1dc35cbc434fefc5f48fb1f6954 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 30 Jan 2025 13:43:39 +0200 Subject: [PATCH 05/13] review --- src/contract.rs | 2 +- src/errors.rs | 8 +++ src/storage.rs | 54 ++++++++++++---- src/tokens.rs | 10 ++- src/types/contract.rs | 4 +- src/types/mod.rs | 16 ++++- src/types/signed_delegate_action.rs | 17 +++-- src/types/stake.rs | 2 + src/types/storage.rs | 24 ++++++- src/types/tokens.rs | 99 ++++++++++++++++++++++------- 10 files changed, 181 insertions(+), 55 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 980b202..394f66c 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -252,7 +252,7 @@ impl Contract { self.view_storage_with_prefix(vec![]) } - /// Prepares a query to fetch the contract source metadata([Data]<[ContractSourceMetadata]>) using [NEP-330](https://nomicon.io/Standards/SourceMetadata) standard. + /// Prepares a query to fetch the contract source metadata([Data]<[ContractSourceMetadata]>) using [NEP-330](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) standard. /// /// The contract source metadata is a standard interface that allows auditing and viewing source code for a deployed smart contract. /// Implementation of this standard is purely optional but is recommended for developers whose contracts are open source. diff --git a/src/errors.rs b/src/errors.rs index ed9bffe..a4300c1 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -127,6 +127,14 @@ impl From for LedgerError { } } +#[derive(thiserror::Error, Debug)] +pub enum SignedDelegateActionError { + #[error("Parsing of signed delegate action failed due to base64 sequence being invalid")] + Base64DecodingError, + #[error("Delegate action could not be deserialized from borsh: {0}")] + BorshError(#[from] std::io::Error), +} + #[derive(thiserror::Error, Debug)] pub enum SecretBuilderError { #[error("Public key is not available")] diff --git a/src/storage.rs b/src/storage.rs index d41d2d5..e8169ac 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,18 +1,19 @@ -use near_primitives::types::AccountId; +use near_primitives::types::{AccountId, BlockReference}; use near_token::NearToken; use serde_json::json; use crate::{ - common::query::{CallResultHandler, QueryBuilder}, + common::query::{CallResultHandler, PostprocessHandler, QueryBuilder, SimpleQuery}, contract::{Contract, ContractTransactBuilder}, errors::BuilderError, transactions::ConstructTransaction, - types::storage::StorageBalance, + types::storage::{StorageBalance, StorageBalanceInternal}, + Data, }; -///A wrapper struct that simplifies interactions with the [Storage Management](https://nomicon.io/Standards/StorageManagement) standard +///A wrapper struct that simplifies interactions with the [Storage Management](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard /// -/// Contracts on NEAR Protocol often implement a [NEP-145](https://nomicon.io/Standards/StorageManagement) for managing storage deposits, +/// Contracts on NEAR Protocol often implement a [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) for managing storage deposits, /// which are required for storing data on the blockchain. This struct provides convenient methods /// to interact with these storage-related functions on the contract. /// @@ -62,15 +63,42 @@ impl StorageDeposit { pub fn view_account_storage( &self, account_id: AccountId, - ) -> Result>>, BuilderError> { - Ok(Contract(self.0.clone()) - .call_function( - "storage_balance_of", - json!({ - "account_id": account_id, + ) -> Result< + QueryBuilder< + PostprocessHandler< + Data>, + CallResultHandler>, + >, + >, + BuilderError, + > { + let args = serde_json::to_vec(&json!({ + "account_id": account_id, + }))?; + let request = near_primitives::views::QueryRequest::CallFunction { + account_id: self.0.clone(), + method_name: "storage_balance_of".to_owned(), + args: near_primitives::types::FunctionArgs::from(args), + }; + + Ok(QueryBuilder::new( + SimpleQuery { request }, + BlockReference::latest(), + PostprocessHandler::new( + CallResultHandler::default(), + Box::new(|storage: Data>| Data { + data: storage.data.map(|data| StorageBalance { + available: data.available, + total: data.total, + locked: NearToken::from_yoctonear( + data.total.as_yoctonear() - data.available.as_yoctonear(), + ), + }), + block_height: storage.block_height, + block_hash: storage.block_hash, }), - )? - .read_only()) + ), + )) } /// Prepares a new transaction contract call (`storage_deposit`) for depositing storage on the contract. diff --git a/src/tokens.rs b/src/tokens.rs index 4401fed..f561d7b 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -25,15 +25,13 @@ use crate::{ errors::{BuilderError, FTValidatorError, ValidationError}, transactions::{ConstructTransaction, TransactionWithSign}, types::{ - tokens::{FTBalance, UserBalance}, + tokens::{FTBalance, UserBalance, STORAGE_COST_PER_BYTE}, transactions::PrepopulateTransaction, Data, }, NetworkConfig, StorageDeposit, }; -const STORAGE_COST: NearToken = NearToken::from_yoctonear(10_000_000_000_000_000_000u128); - type Result = core::result::Result; // This is not too long as most of the size is a links to the docs @@ -45,8 +43,8 @@ type Result = core::result::Result; /// /// This struct provides convenient methods to interact with different types of tokens on NEAR Protocol: /// - [Native NEAR](https://docs.near.org/concepts/basics/tokens) token operations -/// - Fungible Token - [Documentation and examples](https://nomicon.io/Standards/Tokens/FungibleToken/Core), [NEP-141](https://nomicon.io/Standards/Tokens/FungibleToken/Core) -/// - Non-Fungible Token - [Documentation and examples](https://docs.near.org/build/primitives/nft), [NEP-171](https://nomicon.io/Standards/Tokens/NonFungibleToken/Core) +/// - Fungible Token - [Documentation and examples](https://docs.near.org/build/primitives/ft), [NEP-141](https://github.com/near/NEPs/blob/master/neps/nep-0141.md) +/// - Non-Fungible Token - [Documentation and examples](https://docs.near.org/build/primitives/nft), [NEP-171](https://github.com/near/NEPs/blob/master/neps/nep-0171.md) /// /// ## Examples /// @@ -154,7 +152,7 @@ impl Tokens { let account = account.data; let total = NearToken::from_yoctonear(account.amount); let storage_locked = NearToken::from_yoctonear( - account.storage_usage as u128 * STORAGE_COST.as_yoctonear(), + account.storage_usage as u128 * STORAGE_COST_PER_BYTE.as_yoctonear(), ); let locked = NearToken::from_yoctonear(account.locked); let storage_usage = account.storage_usage; diff --git a/src/types/contract.rs b/src/types/contract.rs index b677103..c77a8f8 100644 --- a/src/types/contract.rs +++ b/src/types/contract.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; /// The struct provides information about deployed contract's source code and supported standards. /// -/// Contract source metadata follows [NEP-330 standard](https://nomicon.io/Standards/SourceMetadata) for smart contract verification +/// Contract source metadata follows [NEP-330 standard](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) for smart contract verification #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct ContractSourceMetadata { /// Optional version identifier, typically a commit hash or semantic version @@ -27,7 +27,7 @@ pub struct ContractSourceMetadata { pub standards: Vec, } -/// NEAR Standard implementation descriptor following [NEP-330](https://nomicon.io/Standards/SourceMetadata) +/// NEAR Standard implementation descriptor following [NEP-330](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct Standard { /// Standard name in lowercase NEP format diff --git a/src/types/mod.rs b/src/types/mod.rs index df8d1c8..57a5337 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -33,8 +33,22 @@ pub struct Data { } /// A wrapper around [near_jsonrpc_client::auth::ApiKey] +/// +/// This type is used to authenticate requests to the RPC node +/// +/// ## Creating an API key +/// +/// ``` +/// use near_api::types::ApiKey; +/// use std::str::FromStr; +/// +/// # async fn example() -> Result<(), Box> { +/// let api_key = ApiKey::from_str("your_api_key")?; +/// # Ok(()) +/// # } +/// ``` #[derive(Eq, Hash, Clone, Debug, PartialEq)] -pub struct ApiKey(pub near_jsonrpc_client::auth::ApiKey); +pub struct ApiKey(near_jsonrpc_client::auth::ApiKey); impl From for near_jsonrpc_client::auth::ApiKey { fn from(api_key: ApiKey) -> Self { diff --git a/src/types/signed_delegate_action.rs b/src/types/signed_delegate_action.rs index 5d9cc7e..820a5fc 100644 --- a/src/types/signed_delegate_action.rs +++ b/src/types/signed_delegate_action.rs @@ -1,6 +1,10 @@ -use near_primitives::{borsh, borsh::BorshDeserialize}; +use near_primitives::borsh; -/// A wrapper around `SignedDelegateAction` that allows for easy serialization and deserialization as base64 string +use crate::errors::SignedDelegateActionError; + +/// A wrapper around [near_primitives::action::delegate::SignedDelegateAction] that allows for easy serialization and deserialization as base64 string +/// +/// The type implements [std::str::FromStr] and [std::fmt::Display] to serialize and deserialize the type as base64 string #[derive(Debug, Clone)] pub struct SignedDelegateActionAsBase64 { /// The inner signed delegate action @@ -8,14 +12,13 @@ pub struct SignedDelegateActionAsBase64 { } impl std::str::FromStr for SignedDelegateActionAsBase64 { - type Err = String; + type Err = SignedDelegateActionError; fn from_str(s: &str) -> Result { Ok(Self { - inner: near_primitives::action::delegate::SignedDelegateAction::try_from_slice( + inner: borsh::from_slice( &near_primitives::serialize::from_base64(s) - .map_err(|err| format!("parsing of signed delegate action failed due to base64 sequence being invalid: {}", err))?, - ) - .map_err(|err| format!("delegate action could not be deserialized from borsh: {}", err))?, + .map_err(|_| SignedDelegateActionError::Base64DecodingError)?, + )?, }) } } diff --git a/src/types/stake.rs b/src/types/stake.rs index 8511860..8c967c3 100644 --- a/src/types/stake.rs +++ b/src/types/stake.rs @@ -20,6 +20,8 @@ pub struct StakingPoolInfo { /// /// This represents the percentage of the reward that is taken by the pool contract. /// The type is a part of the [StakingPool](https://github.com/near/core-contracts/tree/master/staking-pool) interface +/// +/// The fraction is equal to numerator/denominator, e.g. 3/1000 = 0.3% #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct RewardFeeFraction { /// The numerator of the fraction. diff --git a/src/types/storage.rs b/src/types/storage.rs index 6c169c8..d696411 100644 --- a/src/types/storage.rs +++ b/src/types/storage.rs @@ -1,19 +1,39 @@ use near_sdk::NearToken; use serde::de::{Deserialize, Deserializer}; -/// A type that represents the storage balance from the [NEP-145](https://nomicon.io/Standards/StorageManagement) standard +/// A type that represents the storage balance from the [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard /// on some NEAR contract. +/// +/// As a storing data on-chain requires storage staking, the contracts require users to deposit NEAR to store user-rel. #[derive(Debug, Clone, serde::Deserialize)] pub struct StorageBalance { /// The available balance that might be used for storage. /// - /// The user can withdraw this balance. + /// The user can withdraw this balance from the contract. #[serde(deserialize_with = "parse_u128_string")] pub available: NearToken, /// The total user balance on the contract for storage. + /// /// This is a sum of the `available` and `locked` balances. #[serde(deserialize_with = "parse_u128_string")] pub total: NearToken, + + /// The storage deposit that is locked for the account + /// + /// The user can unlock some funds by removing the data from the contract. + /// Though, it's contract-specific on how much can be unlocked. + #[serde(deserialize_with = "parse_u128_string")] + pub locked: NearToken, +} + +/// Used internally to parse the storage balance from the contract and +/// to convert it into the [StorageBalance] type. +#[derive(Debug, Clone, serde::Deserialize)] +pub struct StorageBalanceInternal { + #[serde(deserialize_with = "parse_u128_string")] + pub available: NearToken, + #[serde(deserialize_with = "parse_u128_string")] + pub total: NearToken, } fn parse_u128_string<'de, D>(deserializer: D) -> Result diff --git a/src/types/tokens.rs b/src/types/tokens.rs index 88aba53..89dbdc2 100644 --- a/src/types/tokens.rs +++ b/src/types/tokens.rs @@ -10,41 +10,54 @@ pub const USDC_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(6, "USDC /// Static instance of [FTBalance] for wNEAR token with correct decimals and symbol. pub const W_NEAR_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(24, "wNEAR"); +/// The cost of storage per byte in NEAR. +pub const STORAGE_COST_PER_BYTE: NearToken = NearToken::from_yoctonear(10u128.pow(19)); + /// A helper type that represents the fungible token balance with a given precision. /// /// The type is created to simplify the usage of fungible tokens in similar way as the [NearToken] type does. /// /// The symbol is used only for display purposes. /// +/// The type has static instances for some of the most popular tokens with correct decimals and symbol. +/// * [USDT_BALANCE] - USDT token with 6 decimals +/// * [USDC_BALANCE] - USDC token with 6 decimals +/// * [W_NEAR_BALANCE] - wNEAR token with 24 decimals +/// /// # Examples /// /// ## Defining 2.5 USDT /// ```rust -/// use near_api::USDT_BALANCE; +/// use near_api::FTBalance; /// -/// let usdt_balance = USDT_BALANCE.with_float_str("2.5").unwrap(); +/// let usdt_balance = FTBalance::with_decimals(6).with_float_str("2.5").unwrap(); /// /// assert_eq!(usdt_balance.amount(), 2_500_000); /// ``` /// -/// ## Defining 3 wNEAR using yoctoNEAR +/// ## Defining 3 USDT using smaller precision /// ```rust -/// use near_api::{W_NEAR_BALANCE, NearToken}; +/// use near_api::FTBalance; +/// +/// let usdt = FTBalance::with_decimals(6); /// -/// let wnear_balance = W_NEAR_BALANCE.with_amount(3 * 10u128.pow(24)); +/// let usdt_balance = usdt.with_amount(3 * 10u128.pow(6)); /// -/// assert_eq!(wnear_balance.amount(), NearToken::from_near(3).as_yoctonear()); +/// assert_eq!(usdt_balance, usdt.with_whole_amount(3)); /// ``` /// /// ## Defining 3 wETH using 18 decimals /// ```rust /// use near_api::FTBalance; /// -/// let weth_balance = FTBalance::with_decimals_and_symbol(18, "wETH").with_whole_amount(3); +/// let weth = FTBalance::with_decimals_and_symbol(18, "wETH"); +/// let weth_balance = weth.with_whole_amount(3); +/// +/// assert_eq!(weth_balance, weth.with_amount(3 * 10u128.pow(18))); /// ``` #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] pub struct FTBalance { - balance: u128, + amount: u128, decimals: u8, symbol: &'static str, } @@ -55,7 +68,7 @@ impl FTBalance { /// The balance is initialized to 0. pub const fn with_decimals(decimals: u8) -> Self { Self { - balance: 0, + amount: 0, decimals, symbol: "FT", } @@ -66,7 +79,7 @@ impl FTBalance { /// The balance is initialized to 0. pub const fn with_decimals_and_symbol(decimals: u8, symbol: &'static str) -> Self { Self { - balance: 0, + amount: 0, decimals, symbol, } @@ -74,10 +87,19 @@ impl FTBalance { /// Stores the given amount without any transformations. /// - /// The [NearToken] alternative is [NearToken::from_yoctonear]. + /// The [NearToken] equivalent to this method is [NearToken::from_yoctonear]. + /// + /// ## Example + /// ```rust + /// use near_api::FTBalance; + /// + /// let usdt_balance = FTBalance::with_decimals(6).with_amount(2_500_000); + /// assert_eq!(usdt_balance.amount(), 2_500_000); + /// assert_eq!(usdt_balance.to_whole(), 2); + /// ``` pub const fn with_amount(&self, amount: u128) -> Self { Self { - balance: amount, + amount, decimals: self.decimals, symbol: self.symbol, } @@ -85,10 +107,19 @@ impl FTBalance { /// Stores the number as an integer token value utilizing the given precision. /// - /// The [NearToken] alternative is [NearToken::from_near]. + /// The [NearToken] equivalent to this method is [NearToken::from_near]. + /// + /// ## Example + /// ```rust + /// use near_api::FTBalance; + /// + /// let usdt_balance = FTBalance::with_decimals(6).with_whole_amount(3); + /// assert_eq!(usdt_balance.amount(), 3 * 10u128.pow(6)); + /// assert_eq!(usdt_balance.to_whole(), 3); + /// ``` pub const fn with_whole_amount(&self, amount: u128) -> Self { Self { - balance: amount * 10u128.pow(self.decimals as u32), + amount: amount * 10u128.pow(self.decimals as u32), decimals: self.decimals, symbol: self.symbol, } @@ -100,9 +131,9 @@ impl FTBalance { /// /// ## Defining 2.5 USDT /// ```rust - /// use near_api::USDT_BALANCE; + /// use near_api::FTBalance; /// - /// let usdt_balance = USDT_BALANCE.with_float_str("2.515").unwrap(); + /// let usdt_balance = FTBalance::with_decimals(6).with_float_str("2.515").unwrap(); /// /// assert_eq!(usdt_balance.amount(), 2_515_000); /// ``` @@ -113,16 +144,18 @@ impl FTBalance { /// Returns the amount without any transformations. /// - /// The [NearToken] alternative is [NearToken::as_yoctonear]. + /// The [NearToken] equivalent to this method is [NearToken::as_yoctonear]. pub const fn amount(&self) -> u128 { - self.balance + self.amount } - /// Returns the amount as a whole number in the defined precision. + /// Returns the amount as a whole number in the integer precision. + /// + /// The method rounds down the fractional part, so 2.5 USDT will be 2. /// - /// The [NearToken] alternative is [NearToken::as_near]. + /// The [NearToken] equivalent to this method is [NearToken::as_near]. pub const fn to_whole(&self) -> u128 { - self.balance / 10u128.pow(self.decimals as u32) + self.amount / 10u128.pow(self.decimals as u32) } /// Returns the number of decimals used by the token. @@ -131,10 +164,20 @@ impl FTBalance { } } +impl PartialOrd for FTBalance { + fn partial_cmp(&self, other: &Self) -> Option { + if self.decimals != other.decimals || self.symbol != other.symbol { + return None; + } + + Some(self.amount.cmp(&other.amount)) + } +} + impl std::fmt::Display for FTBalance { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let whole_part = self.to_whole(); - let fractional_part = self.balance % 10u128.pow(self.decimals as u32); + let fractional_part = self.amount % 10u128.pow(self.decimals as u32); let fractional_part_str = format!( "{:0width$}", @@ -161,14 +204,24 @@ pub struct UserBalance { /// Please note that this is the total amount of NEAR tokens in the account, not the amount available for use. pub total: NearToken, /// The amount of NEAR tokens locked in the account for storage usage. + /// + /// The storage lock equal to [Self::storage_usage] * [STORAGE_COST_PER_BYTE] pub storage_locked: NearToken, /// The storage usage by the account in bytes. pub storage_usage: u64, /// The amount of NEAR tokens staked on a protocol level. /// Applicable for staking pools only in 99.99% of the cases. /// + /// The PoS allows particular users to stake funds to become a validator, but the protocol itself + /// doesn't allow other users to delegate tokens to the validator. + /// This is why, the [NEP-27](https://github.com/near/core-contracts/tree/master/staking-pool) defines a Staking Pool smart contract + /// that allows other users to delegate tokens to the validator. + /// + /// Even though, the user can stake and become validator itself, it's highly unlickly and this field will be 0 + /// for almost all the users, and not 0 for StakingPool contracts. + /// /// Please note that this is not related to your delegations into the staking pools. - /// To get your stake info into some pool, use [crate::Delegation] + /// To get your delegation information in the staking pools, use [crate::Delegation] pub locked: NearToken, } From d305fe57337b4cfdd3a93a5f5d27621c779b7533 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 30 Jan 2025 13:45:12 +0200 Subject: [PATCH 06/13] typo --- src/types/tokens.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/tokens.rs b/src/types/tokens.rs index 89dbdc2..53533e5 100644 --- a/src/types/tokens.rs +++ b/src/types/tokens.rs @@ -217,7 +217,7 @@ pub struct UserBalance { /// This is why, the [NEP-27](https://github.com/near/core-contracts/tree/master/staking-pool) defines a Staking Pool smart contract /// that allows other users to delegate tokens to the validator. /// - /// Even though, the user can stake and become validator itself, it's highly unlickly and this field will be 0 + /// Even though, the user can stake and become validator itself, it's highly unlikely and this field will be 0 /// for almost all the users, and not 0 for StakingPool contracts. /// /// Please note that this is not related to your delegations into the staking pools. From 0ad4623f8bc8b3c8d9c7698cf32f0aaff9927114 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 30 Jan 2025 13:54:53 +0200 Subject: [PATCH 07/13] feat: read-only-with-postprocess --- src/contract.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/contract.rs b/src/contract.rs index 394f66c..b013fd8 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -415,6 +415,30 @@ impl CallFunctionBuilder { ) } + pub fn read_only_with_postprocess( + self, + postprocess: impl Fn(Response) -> MappedType + Send + Sync + 'static, + ) -> QueryBuilder, CallResultHandler>> { + let request = near_primitives::views::QueryRequest::CallFunction { + account_id: self.contract, + method_name: self.method_name, + args: near_primitives::types::FunctionArgs::from(self.args), + }; + + QueryBuilder::new( + SimpleQuery { request }, + BlockReference::latest(), + PostprocessHandler::new( + CallResultHandler(PhantomData), + Box::new(move |data: Data| Data { + data: postprocess(data.data), + block_height: data.block_height, + block_hash: data.block_hash, + }), + ), + ) + } + /// Prepares a transaction that will call a contract function leading to a state change. /// /// This will require a signer to be provided and gas to be paid. From 25750cdc34a23a34ecec60328e807ad1050a758f Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 30 Jan 2025 14:12:48 +0200 Subject: [PATCH 08/13] simplified the contract interaction internally --- src/contract.rs | 32 ++++--------- src/stake.rs | 118 +++++++++++++++--------------------------------- src/storage.rs | 48 ++++++++------------ 3 files changed, 64 insertions(+), 134 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index b013fd8..a6683ee 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -157,23 +157,11 @@ impl Contract { &self, ) -> QueryBuilder, CallResultHandler>>> { - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: self.0.clone(), - method_name: "__contract_abi".to_owned(), - args: near_primitives::types::FunctionArgs::from(vec![]), - }; - - QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - PostprocessHandler::new( - CallResultHandler::default(), - Box::new(|data: Data>| { - serde_json::from_slice(zstd::decode_all(data.data.as_slice()).ok()?.as_slice()) - .ok() - }), - ), - ) + self.call_function("__contract_abi", ()) + .expect("arguments are always serializable") + .read_only_with_postprocess(|data: Data>| { + serde_json::from_slice(zstd::decode_all(data.data.as_slice()).ok()?.as_slice()).ok() + }) } /// Prepares a query to fetch the wasm code ([Data]<[ContractCodeView](near_primitives::views::ContractCodeView)>) of the contract. @@ -417,8 +405,8 @@ impl CallFunctionBuilder { pub fn read_only_with_postprocess( self, - postprocess: impl Fn(Response) -> MappedType + Send + Sync + 'static, - ) -> QueryBuilder, CallResultHandler>> { + postprocess: impl Fn(Data) -> MappedType + Send + Sync + 'static, + ) -> QueryBuilder>> { let request = near_primitives::views::QueryRequest::CallFunction { account_id: self.contract, method_name: self.method_name, @@ -430,11 +418,7 @@ impl CallFunctionBuilder { BlockReference::latest(), PostprocessHandler::new( CallResultHandler(PhantomData), - Box::new(move |data: Data| Data { - data: postprocess(data.data), - block_height: data.block_height, - block_hash: data.block_hash, - }), + Box::new(move |data: Data| postprocess(data)), ), ) } diff --git a/src/stake.rs b/src/stake.rs index a6a6519..3f189a9 100644 --- a/src/stake.rs +++ b/src/stake.rs @@ -9,7 +9,7 @@ use crate::{ common::{ query::{ CallResultHandler, MultiQueryBuilder, MultiQueryHandler, PostprocessHandler, - QueryBuilder, QueryCreator, RpcValidatorHandler, SimpleQuery, SimpleValidatorRpc, + QueryBuilder, QueryCreator, RpcValidatorHandler, SimpleValidatorRpc, ValidatorQueryBuilder, ViewStateHandler, }, utils::{is_critical_query_error, near_data_to_near_token}, @@ -51,23 +51,14 @@ impl Delegation { &self, pool: AccountId, ) -> Result>>> { - let args = serde_json::to_vec(&serde_json::json!({ - "account_id": self.0.clone(), - }))?; - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: pool, - method_name: "get_account_staked_balance".to_owned(), - args: near_primitives::types::FunctionArgs::from(args), - }; - - Ok(QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - PostprocessHandler::new( - CallResultHandler::default(), - Box::new(near_data_to_near_token), - ), - )) + Ok(Contract(pool) + .call_function( + "get_account_staked_balance", + serde_json::json!({ + "account_id": self.0.clone(), + }), + )? + .read_only_with_postprocess(near_data_to_near_token)) } /// 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. @@ -91,23 +82,14 @@ impl Delegation { &self, pool: AccountId, ) -> Result>>> { - let args = serde_json::to_vec(&serde_json::json!({ - "account_id": self.0.clone(), - }))?; - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: pool, - method_name: "get_account_unstaked_balance".to_owned(), - args: near_primitives::types::FunctionArgs::from(args), - }; - - Ok(QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - PostprocessHandler::new( - CallResultHandler::default(), - Box::new(near_data_to_near_token), - ), - )) + Ok(Contract(pool) + .call_function( + "get_account_unstaked_balance", + serde_json::json!({ + "account_id": self.0.clone(), + }), + )? + .read_only_with_postprocess(near_data_to_near_token)) } /// Prepares a new contract query (`get_account_total_balance`) for fetching the total balance ([NearToken]) of the account (free + staked) on the staking pool. @@ -131,23 +113,14 @@ impl Delegation { &self, pool: AccountId, ) -> Result>>> { - let args = serde_json::to_vec(&serde_json::json!({ - "account_id": self.0.clone(), - }))?; - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: pool, - method_name: "get_account_total_balance".to_owned(), - args: near_primitives::types::FunctionArgs::from(args), - }; - - Ok(QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - PostprocessHandler::new( - CallResultHandler::default(), - Box::new(near_data_to_near_token), - ), - )) + Ok(Contract(pool) + .call_function( + "get_account_total_balance", + serde_json::json!({ + "account_id": self.0.clone(), + }), + )? + .read_only_with_postprocess(near_data_to_near_token)) } /// Returns a full information about the staked balance ([UserStakeBalance]) of the account on the staking pool. @@ -234,21 +207,14 @@ impl Delegation { &self, pool: AccountId, ) -> Result>> { - let args = serde_json::to_vec(&serde_json::json!({ - "account_id": self.0.clone(), - }))?; - - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: pool, - method_name: "is_account_unstaked_balance_available".to_owned(), - args: near_primitives::types::FunctionArgs::from(args), - }; - - Ok(QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - CallResultHandler::default(), - )) + Ok(Contract(pool) + .call_function( + "is_account_unstaked_balance_available", + serde_json::json!({ + "account_id": self.0.clone(), + }), + )? + .read_only()) } /// Prepares a new transaction contract call (`deposit`) for depositing funds into the staking pool. @@ -681,20 +647,10 @@ impl Staking { pub fn staking_pool_total_stake( pool: AccountId, ) -> QueryBuilder>> { - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: pool, - method_name: "get_total_staked_balance".to_owned(), - args: near_primitives::types::FunctionArgs::from(vec![]), - }; - - QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - PostprocessHandler::new( - CallResultHandler::default(), - Box::new(near_data_to_near_token), - ), - ) + Contract(pool) + .call_function("get_total_staked_balance", ()) + .expect("arguments are not expected") + .read_only_with_postprocess(near_data_to_near_token) } /// Returns a full information about the staking pool ([StakingPoolInfo]). diff --git a/src/storage.rs b/src/storage.rs index e8169ac..7dc329e 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,14 +1,13 @@ -use near_primitives::types::{AccountId, BlockReference}; use near_token::NearToken; use serde_json::json; use crate::{ - common::query::{CallResultHandler, PostprocessHandler, QueryBuilder, SimpleQuery}, + common::query::{CallResultHandler, PostprocessHandler, QueryBuilder}, contract::{Contract, ContractTransactBuilder}, errors::BuilderError, transactions::ConstructTransaction, types::storage::{StorageBalance, StorageBalanceInternal}, - Data, + AccountId, Data, }; ///A wrapper struct that simplifies interactions with the [Storage Management](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard @@ -72,33 +71,24 @@ impl StorageDeposit { >, BuilderError, > { - let args = serde_json::to_vec(&json!({ - "account_id": account_id, - }))?; - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: self.0.clone(), - method_name: "storage_balance_of".to_owned(), - args: near_primitives::types::FunctionArgs::from(args), - }; - - Ok(QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - PostprocessHandler::new( - CallResultHandler::default(), - Box::new(|storage: Data>| Data { - data: storage.data.map(|data| StorageBalance { - available: data.available, - total: data.total, - locked: NearToken::from_yoctonear( - data.total.as_yoctonear() - data.available.as_yoctonear(), - ), - }), - block_height: storage.block_height, - block_hash: storage.block_hash, + Ok(Contract(self.0.clone()) + .call_function( + "storage_balance_of", + json!({ + "account_id": account_id, + }), + )? + .read_only_with_postprocess(|storage: Data>| Data { + data: storage.data.map(|data| StorageBalance { + available: data.available, + total: data.total, + locked: NearToken::from_yoctonear( + data.total.as_yoctonear() - data.available.as_yoctonear(), + ), }), - ), - )) + block_height: storage.block_height, + block_hash: storage.block_hash, + })) } /// Prepares a new transaction contract call (`storage_deposit`) for depositing storage on the contract. From 6b84430abe48674d0b081893813ebd4152b7fb15 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 30 Jan 2025 14:20:16 +0200 Subject: [PATCH 09/13] documentation --- src/contract.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/contract.rs b/src/contract.rs index a6683ee..1d2afed 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -403,6 +403,20 @@ impl CallFunctionBuilder { ) } + /// Prepares a read-only query that doesn't require a signing transaction, and post-processes the response. + /// + /// This is useful if you want to convert one type to another. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance: NearToken = Contract("some_contract.testnet".parse()?).call_function("get_balance", ())?.read_only_with_postprocess(|balance: Data| NearToken::from_yoctonear(balance.data)).fetch_from_testnet().await?; + /// println!("Balance: {}", balance); + /// # Ok(()) + /// # } + /// ``` pub fn read_only_with_postprocess( self, postprocess: impl Fn(Data) -> MappedType + Send + Sync + 'static, From 42a02f86899eaadb637bd738b29bb87ea040fa94 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Mon, 3 Feb 2025 22:02:38 +0200 Subject: [PATCH 10/13] replaced method read_only_with_mapping to map method on query builder --- src/chain.rs | 25 ++------ src/common/query.rs | 54 +++++++++++++++++ src/contract.rs | 37 +----------- src/fastnear.rs | 4 +- src/stake.rs | 137 ++++++++++++++++++++++---------------------- src/storage.rs | 3 +- src/tokens.rs | 54 ++++++++--------- 7 files changed, 158 insertions(+), 156 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 70cfbf8..d680fe6 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,7 +1,4 @@ -use near_primitives::{ - types::{BlockHeight, BlockReference}, - views::BlockView, -}; +use near_primitives::types::{BlockHeight, BlockReference}; use crate::{ common::query::{BlockQueryBuilder, PostprocessHandler, RpcBlockHandler, SimpleBlockRpc}, @@ -53,14 +50,8 @@ impl Chain { /// # } /// ``` pub fn block_number() -> BlockQueryBuilder> { - BlockQueryBuilder::new( - SimpleBlockRpc, - BlockReference::latest(), - PostprocessHandler::new( - RpcBlockHandler, - Box::new(|data: BlockView| data.header.height), - ), - ) + BlockQueryBuilder::new(SimpleBlockRpc, BlockReference::latest(), RpcBlockHandler) + .map(|data| data.header.height) } /// Set ups a query to fetch the [CryptoHash] of the block @@ -89,14 +80,8 @@ impl Chain { /// # } /// ``` pub fn block_hash() -> BlockQueryBuilder> { - BlockQueryBuilder::new( - SimpleBlockRpc, - BlockReference::latest(), - PostprocessHandler::new( - RpcBlockHandler, - Box::new(|data: BlockView| data.header.hash.into()), - ), - ) + BlockQueryBuilder::new(SimpleBlockRpc, BlockReference::latest(), RpcBlockHandler) + .map(|data| data.header.hash.into()) } /// Set ups a query to fetch the [BlockView] diff --git a/src/common/query.rs b/src/common/query.rs index 1ae80b9..03ee06b 100644 --- a/src/common/query.rs +++ b/src/common/query.rs @@ -147,6 +147,14 @@ pub type MultiQueryBuilder = MultiRpcBuilder = RpcBuilder; pub type BlockQueryBuilder = RpcBuilder; +/// A builder for querying multiple items at once. +/// +/// Sometimes to construct some complex type, you would need to query multiple items at once, and combine them into one. +/// This is where this builder comes in handy. Almost everytime you would want to use [Self::map] method to combine the responses into your desired type. +/// +/// Here is a list of examples on how to use this: +/// - [Tokens::ft_balance](crate::tokens::Tokens::ft_balance) +/// - [StakingPool::staking_pool_info](crate::stake::Staking::staking_pool_info) pub struct MultiRpcBuilder where Reference: Send + Sync, @@ -173,6 +181,22 @@ where } } + /// Map response of the queries to another type. The `map` function is executed after the queries are fetched. + /// + /// The response is a tuple of the responses of the queries. + /// + /// See [Tokens::ft_balance](crate::tokens::Tokens::ft_balance) implementation for an example on how to use this. + pub fn map( + self, + map: impl Fn(Handler::Response) -> MappedType + Send + Sync + 'static, + ) -> MultiRpcBuilder, Method, Reference> { + MultiRpcBuilder { + handler: PostprocessHandler::new(self.handler, map), + requests: self.requests, + reference: self.reference, + } + } + /// Add a query to the queried items. Sometimes you might need to query multiple items at once. /// To combine the result of multiple queries into one. pub fn add_query( @@ -298,6 +322,36 @@ where } } + /// Post-process the response of the query. + /// + /// This is useful if you want to convert one type to another. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance: NearToken = Contract("some_contract.testnet".parse()?) + /// .call_function("get_balance", ())? + /// .read_only() + /// .map(|balance: Data| NearToken::from_yoctonear(balance.data)) + /// .fetch_from_testnet() + /// .await?; + /// println!("Balance: {}", balance); + /// # Ok(()) + /// # } + /// ``` + pub fn map( + self, + map: impl Fn(Handler::Response) -> MappedType + Send + Sync + 'static, + ) -> RpcBuilder, Method, Reference> { + RpcBuilder { + handler: PostprocessHandler::new(self.handler, map), + request: self.request, + reference: self.reference, + } + } + /// Fetch the query from the provided network. #[instrument(skip(self, network))] pub async fn fetch_from( diff --git a/src/contract.rs b/src/contract.rs index 1d2afed..293e4a1 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -159,7 +159,8 @@ impl Contract { { self.call_function("__contract_abi", ()) .expect("arguments are always serializable") - .read_only_with_postprocess(|data: Data>| { + .read_only() + .map(|data: Data>| { serde_json::from_slice(zstd::decode_all(data.data.as_slice()).ok()?.as_slice()).ok() }) } @@ -403,40 +404,6 @@ impl CallFunctionBuilder { ) } - /// Prepares a read-only query that doesn't require a signing transaction, and post-processes the response. - /// - /// This is useful if you want to convert one type to another. - /// - /// ## Example - /// ```rust,no_run - /// use near_api::*; - /// - /// # async fn example() -> Result<(), Box> { - /// let balance: NearToken = Contract("some_contract.testnet".parse()?).call_function("get_balance", ())?.read_only_with_postprocess(|balance: Data| NearToken::from_yoctonear(balance.data)).fetch_from_testnet().await?; - /// println!("Balance: {}", balance); - /// # Ok(()) - /// # } - /// ``` - pub fn read_only_with_postprocess( - self, - postprocess: impl Fn(Data) -> MappedType + Send + Sync + 'static, - ) -> QueryBuilder>> { - let request = near_primitives::views::QueryRequest::CallFunction { - account_id: self.contract, - method_name: self.method_name, - args: near_primitives::types::FunctionArgs::from(self.args), - }; - - QueryBuilder::new( - SimpleQuery { request }, - BlockReference::latest(), - PostprocessHandler::new( - CallResultHandler(PhantomData), - Box::new(move |data: Data| postprocess(data)), - ), - ) - } - /// Prepares a transaction that will call a contract function leading to a state change. /// /// This will require a signer to be provided and gas to be paid. diff --git a/src/fastnear.rs b/src/fastnear.rs index d0631f1..e79e011 100644 --- a/src/fastnear.rs +++ b/src/fastnear.rs @@ -35,7 +35,7 @@ impl FastNearBuilder where T: DeserializeOwned + Send + Sync, { - pub fn with_postprocess(query: String, func: F) -> Self + pub fn map(query: String, func: F) -> Self where F: Fn(T) -> PostProcessed + Send + Sync + 'static, { @@ -67,7 +67,7 @@ impl FastNear { &self, account_id: &AccountId, ) -> Result>, FastNearError> { - let query_builder = FastNearBuilder::with_postprocess( + let query_builder = FastNearBuilder::map( format!("v1/account/{}/staking", account_id), |response: StakingResponse| { response diff --git a/src/stake.rs b/src/stake.rs index 3f189a9..f5beabb 100644 --- a/src/stake.rs +++ b/src/stake.rs @@ -58,7 +58,8 @@ impl Delegation { "account_id": self.0.clone(), }), )? - .read_only_with_postprocess(near_data_to_near_token)) + .read_only() + .map(near_data_to_near_token)) } /// 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. @@ -89,7 +90,8 @@ impl Delegation { "account_id": self.0.clone(), }), )? - .read_only_with_postprocess(near_data_to_near_token)) + .read_only() + .map(near_data_to_near_token)) } /// Prepares a new contract query (`get_account_total_balance`) for fetching the total balance ([NearToken]) of the account (free + staked) on the staking pool. @@ -120,7 +122,8 @@ impl Delegation { "account_id": self.0.clone(), }), )? - .read_only_with_postprocess(near_data_to_near_token)) + .read_only() + .map(near_data_to_near_token)) } /// Returns a full information about the staked balance ([UserStakeBalance]) of the account on the staking pool. @@ -157,13 +160,17 @@ impl Delegation { >, >, > { - let postprocess = PostprocessHandler::new( - MultiQueryHandler::new(( - CallResultHandler::default(), - CallResultHandler::default(), - CallResultHandler::default(), - )), - |(staked, unstaked, total)| { + let postprocess = MultiQueryHandler::new(( + CallResultHandler::default(), + CallResultHandler::default(), + CallResultHandler::default(), + )); + + let multiquery = MultiQueryBuilder::new(postprocess, BlockReference::latest()) + .add_query_builder(self.view_staked_balance(pool.clone())?) + .add_query_builder(self.view_unstaked_balance(pool.clone())?) + .add_query_builder(self.view_total_balance(pool)?) + .map(|(staked, unstaked, total)| { let staked = near_data_to_near_token(staked); let unstaked = near_data_to_near_token(unstaked); let total = near_data_to_near_token(total); @@ -173,14 +180,7 @@ impl Delegation { unstaked, total, } - }, - ); - - let multiquery = MultiQueryBuilder::new(postprocess, BlockReference::latest()) - .add_query_builder(self.view_staked_balance(pool.clone())?) - .add_query_builder(self.view_staked_balance(pool.clone())?) - .add_query_builder(self.view_total_balance(pool)?); - + }); Ok(multiquery) } @@ -502,15 +502,16 @@ impl Staking { QueryBuilder::new( ActiveStakingPoolQuery, BlockReference::latest(), - PostprocessHandler::new(ViewStateHandler, |query_result| { - query_result - .data - .values - .into_iter() - .filter_map(|item| borsh::from_slice(&item.value).ok()) - .collect() - }), + ViewStateHandler, ) + .map(|query_result| { + query_result + .data + .values + .into_iter() + .filter_map(|item| borsh::from_slice(&item.value).ok()) + .collect() + }) } /// Returns a list of validators and their stake ([near_primitives::views::EpochValidatorInfo]) for the current epoch. @@ -551,34 +552,35 @@ impl Staking { ValidatorQueryBuilder::new( SimpleValidatorRpc, EpochReference::Latest, - PostprocessHandler::new(RpcValidatorHandler, |validator_response| { - validator_response - .current_proposals - .into_iter() - .map(|validator_stake_view| { - let validator_stake = validator_stake_view.into_validator_stake(); - validator_stake.account_and_stake() - }) - .chain(validator_response.current_validators.into_iter().map( - |current_epoch_validator_info| { - ( - current_epoch_validator_info.account_id, - current_epoch_validator_info.stake, - ) - }, - )) - .chain(validator_response.next_validators.into_iter().map( - |next_epoch_validator_info| { - ( - next_epoch_validator_info.account_id, - next_epoch_validator_info.stake, - ) - }, - )) - .map(|(account_id, stake)| (account_id, NearToken::from_yoctonear(stake))) - .collect() - }), + RpcValidatorHandler, ) + .map(|validator_response| { + validator_response + .current_proposals + .into_iter() + .map(|validator_stake_view| { + let validator_stake = validator_stake_view.into_validator_stake(); + validator_stake.account_and_stake() + }) + .chain(validator_response.current_validators.into_iter().map( + |current_epoch_validator_info| { + ( + current_epoch_validator_info.account_id, + current_epoch_validator_info.stake, + ) + }, + )) + .chain(validator_response.next_validators.into_iter().map( + |next_epoch_validator_info| { + ( + next_epoch_validator_info.account_id, + next_epoch_validator_info.stake, + ) + }, + )) + .map(|(account_id, stake)| (account_id, NearToken::from_yoctonear(stake))) + .collect() + }) } /// Prepares a new contract query (`get_reward_fee_fraction`) for fetching the reward fee fraction of the staking pool ([Data](crate::Data)<[RewardFeeFraction]>). @@ -650,7 +652,8 @@ impl Staking { Contract(pool) .call_function("get_total_staked_balance", ()) .expect("arguments are not expected") - .read_only_with_postprocess(near_data_to_near_token) + .read_only() + .map(near_data_to_near_token) } /// Returns a full information about the staking pool ([StakingPoolInfo]). @@ -684,13 +687,17 @@ impl Staking { >, > { let pool_clone = pool.clone(); - let postprocess = PostprocessHandler::new( - MultiQueryHandler::new(( - CallResultHandler::default(), - CallResultHandler::default(), - CallResultHandler::default(), - )), - move |(reward_fee, delegators, total_stake)| { + let handler = MultiQueryHandler::new(( + CallResultHandler::default(), + CallResultHandler::default(), + CallResultHandler::default(), + )); + + MultiQueryBuilder::new(handler, BlockReference::latest()) + .add_query_builder(Self::staking_pool_reward_fee(pool.clone())) + .add_query_builder(Self::staking_pool_delegators(pool.clone())) + .add_query_builder(Self::staking_pool_total_stake(pool)) + .map(move |(reward_fee, delegators, total_stake)| { let total = near_data_to_near_token(total_stake); StakingPoolInfo { @@ -700,13 +707,7 @@ impl Staking { delegators: Some(delegators.data), stake: total, } - }, - ); - - MultiQueryBuilder::new(postprocess, BlockReference::latest()) - .add_query_builder(Self::staking_pool_reward_fee(pool.clone())) - .add_query_builder(Self::staking_pool_delegators(pool.clone())) - .add_query_builder(Self::staking_pool_total_stake(pool)) + }) } /// Returns a new [`Delegation`] struct for interacting with the staking pool on behalf of the account. diff --git a/src/storage.rs b/src/storage.rs index 5736c05..9f76e93 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -79,7 +79,8 @@ impl StorageDeposit { "account_id": account_id, }), )? - .read_only_with_postprocess(|storage: Data>| Data { + .read_only() + .map(|storage: Data>| Data { data: storage.data.map(|data| StorageBalance { available: data.available, total: data.total, diff --git a/src/tokens.rs b/src/tokens.rs index f561d7b..47587c4 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -7,7 +7,6 @@ use near_contract_standards::{ use near_primitives::{ action::{Action, TransferAction}, types::{AccountId, BlockReference}, - views::AccountView, }; use near_sdk::json_types::U128; use near_token::NearToken; @@ -27,7 +26,6 @@ use crate::{ types::{ tokens::{FTBalance, UserBalance, STORAGE_COST_PER_BYTE}, transactions::PrepopulateTransaction, - Data, }, NetworkConfig, StorageDeposit, }; @@ -146,25 +144,23 @@ impl Tokens { QueryBuilder::new( SimpleQuery { request }, BlockReference::latest(), - PostprocessHandler::new( - AccountViewHandler, - Box::new(|account: Data| { - let account = account.data; - let total = NearToken::from_yoctonear(account.amount); - let storage_locked = NearToken::from_yoctonear( - account.storage_usage as u128 * STORAGE_COST_PER_BYTE.as_yoctonear(), - ); - let locked = NearToken::from_yoctonear(account.locked); - let storage_usage = account.storage_usage; - UserBalance { - total, - storage_locked, - storage_usage, - locked, - } - }), - ), + AccountViewHandler, ) + .map(|account| { + let account = account.data; + let total = NearToken::from_yoctonear(account.amount); + let storage_locked = NearToken::from_yoctonear( + account.storage_usage as u128 * STORAGE_COST_PER_BYTE.as_yoctonear(), + ); + let locked = NearToken::from_yoctonear(account.locked); + let storage_usage = account.storage_usage; + UserBalance { + total, + storage_locked, + storage_usage, + locked, + } + }) } /// Prepares a new contract query (`nft_metadata`) for fetching the NFT metadata ([NFTContractMetadata]). @@ -282,15 +278,10 @@ impl Tokens { >, >, > { - let postprocess = PostprocessHandler::new( - MultiQueryHandler::new(( - CallResultHandler(PhantomData::), - CallResultHandler(PhantomData::), - )), - |(metadata, amount)| { - FTBalance::with_decimals(metadata.data.decimals).with_amount(amount.data.0) - }, - ); + let postprocess = MultiQueryHandler::new(( + CallResultHandler(PhantomData::), + CallResultHandler(PhantomData::), + )); let query_builder = MultiQueryBuilder::new(postprocess, BlockReference::latest()) .add_query_builder(Self::ft_metadata(ft_contract.clone())?) @@ -303,7 +294,10 @@ impl Tokens { }), )? .read_only::<()>(), - ); + ) + .map(|(metadata, amount)| { + FTBalance::with_decimals(metadata.data.decimals).with_amount(amount.data.0) + }); Ok(query_builder) } From a258db7dd9b5c1e2d3573484e865998455f4cea2 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Mon, 3 Feb 2025 22:08:37 +0200 Subject: [PATCH 11/13] ci --- src/chain.rs | 2 +- src/common/query.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index d680fe6..3c9328a 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -84,7 +84,7 @@ impl Chain { .map(|data| data.header.hash.into()) } - /// Set ups a query to fetch the [BlockView] + /// Set ups a query to fetch the [BlockView][near_primitives::views::BlockView] /// /// ## Fetching the latest block /// diff --git a/src/common/query.rs b/src/common/query.rs index 03ee06b..339a383 100644 --- a/src/common/query.rs +++ b/src/common/query.rs @@ -150,7 +150,7 @@ pub type BlockQueryBuilder = RpcBuilder; /// A builder for querying multiple items at once. /// /// Sometimes to construct some complex type, you would need to query multiple items at once, and combine them into one. -/// This is where this builder comes in handy. Almost everytime you would want to use [Self::map] method to combine the responses into your desired type. +/// This is where this builder comes in handy. Almost every time, you would want to use [Self::map] method to combine the responses into your desired type. /// /// Here is a list of examples on how to use this: /// - [Tokens::ft_balance](crate::tokens::Tokens::ft_balance) From 92bba5512ab12be60f23d390056c7d85f708f45e Mon Sep 17 00:00:00 2001 From: akorchyn Date: Sat, 1 Mar 2025 11:51:48 +0200 Subject: [PATCH 12/13] review --- .github/workflows/test.yml | 2 +- src/common/query.rs | 64 ++++++++++++++++++++++++++++++++++++-- src/contract.rs | 4 +-- src/lib.rs | 5 +++ src/stake.rs | 6 +--- src/storage.rs | 20 ++++++------ src/tokens.rs | 24 +++++++------- src/types/mod.rs | 10 ++++++ src/types/storage.rs | 15 +++++---- 9 files changed, 110 insertions(+), 40 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97896c1..4eef3bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get -y install libudev-dev libsystemd-dev - name: run cargo doc - run: RUSTDOCFLAGS="-D warnings" cargo doc + run: RUSTDOCFLAGS="-D warnings" cargo doc --all-features --document-private-items check-windows: needs: cargo-fmt diff --git a/src/common/query.rs b/src/common/query.rs index 339a383..bd800bb 100644 --- a/src/common/query.rs +++ b/src/common/query.rs @@ -152,6 +152,9 @@ pub type BlockQueryBuilder = RpcBuilder; /// Sometimes to construct some complex type, you would need to query multiple items at once, and combine them into one. /// This is where this builder comes in handy. Almost every time, you would want to use [Self::map] method to combine the responses into your desired type. /// +/// Currently, `MultiQueryHandler` supports tuples of sizes 2 and 3. +/// For single responses, use `QueryBuilder` instead. +/// /// Here is a list of examples on how to use this: /// - [Tokens::ft_balance](crate::tokens::Tokens::ft_balance) /// - [StakingPool::staking_pool_info](crate::stake::Staking::staking_pool_info) @@ -165,6 +168,20 @@ where handler: Handler, } +impl MultiRpcBuilder +where + Reference: Send + Sync, + Handler: Default + Send + Sync, +{ + pub fn with_reference(reference: impl Into) -> Self { + Self { + reference: reference.into(), + requests: vec![], + handler: Default::default(), + } + } +} + impl MultiRpcBuilder where Handler: ResponseHandler + Send + Sync, @@ -183,9 +200,37 @@ where /// Map response of the queries to another type. The `map` function is executed after the queries are fetched. /// - /// The response is a tuple of the responses of the queries. + /// The `Handler::Response` is the type returned by the handler's `process_response` method. /// - /// See [Tokens::ft_balance](crate::tokens::Tokens::ft_balance) implementation for an example on how to use this. + /// For single responses, use `QueryBuilder` instead. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::advanced::{MultiQueryHandler, CallResultHandler, MultiRpcBuilder}; + /// use near_api::types::Data; + /// use std::marker::PhantomData; + /// use near_primitives::types::BlockReference; + /// + /// // Create a handler for multiple query responses and specify the types of the responses + /// let handler = MultiQueryHandler::new(( + /// CallResultHandler::::new(), + /// CallResultHandler::::new(), + /// )); + /// + /// // Create the builder with the handler + /// let builder = MultiRpcBuilder::new(handler, BlockReference::latest()); + /// + /// // Add queries to the builder + /// builder.add_query(todo!()); + /// + /// // Map the tuple of responses to a combined type + /// let mapped_builder = builder.map(|(response1, response2): (Data, Data)| { + /// // Process the combined data + /// format!("{}: {}", response1.data, response2.data) + /// }); + /// ``` + /// + /// See [Tokens::ft_balance](crate::tokens::Tokens::ft_balance) implementation for a real-world example. pub fn map( self, map: impl Fn(Handler::Response) -> MappedType + Send + Sync + 'static, @@ -466,6 +511,13 @@ impl MultiQueryHandler { Self { handlers } } } + +impl Default for MultiQueryHandler { + fn default() -> Self { + Self::new(Default::default()) + } +} + pub struct PostprocessHandler where ::Error: std::fmt::Display + std::fmt::Debug, @@ -515,7 +567,13 @@ where } #[derive(Default, Debug, Clone)] -pub struct CallResultHandler(pub PhantomData); +pub struct CallResultHandler(PhantomData); + +impl CallResultHandler { + pub fn new() -> Self { + Self(PhantomData::) + } +} impl ResponseHandler for CallResultHandler where diff --git a/src/contract.rs b/src/contract.rs index a3f7ea7..1b483b3 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, sync::Arc}; +use std::sync::Arc; use near_gas::NearGas; @@ -405,7 +405,7 @@ impl CallFunctionBuilder { QueryBuilder::new( SimpleQuery { request }, BlockReference::latest(), - CallResultHandler(PhantomData), + CallResultHandler::::new(), ) } diff --git a/src/lib.rs b/src/lib.rs index b853889..ca519dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,11 @@ pub use crate::{ }, }; +pub mod advanced { + pub use crate::common::query::*; + pub use crate::common::send::*; +} + pub use near_primitives; pub use near_account_id::AccountId; diff --git a/src/stake.rs b/src/stake.rs index f5beabb..8d74646 100644 --- a/src/stake.rs +++ b/src/stake.rs @@ -160,11 +160,7 @@ impl Delegation { >, >, > { - let postprocess = MultiQueryHandler::new(( - CallResultHandler::default(), - CallResultHandler::default(), - CallResultHandler::default(), - )); + let postprocess = MultiQueryHandler::default(); let multiquery = MultiQueryBuilder::new(postprocess, BlockReference::latest()) .add_query_builder(self.view_staked_balance(pool.clone())?) diff --git a/src/storage.rs b/src/storage.rs index 9f76e93..3375f9e 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -80,16 +80,16 @@ impl StorageDeposit { }), )? .read_only() - .map(|storage: Data>| Data { - data: storage.data.map(|data| StorageBalance { - available: data.available, - total: data.total, - locked: NearToken::from_yoctonear( - data.total.as_yoctonear() - data.available.as_yoctonear(), - ), - }), - block_height: storage.block_height, - block_hash: storage.block_hash, + .map(|storage: Data>| { + storage.map(|option_storage| { + option_storage.map(|data| StorageBalance { + available: data.available, + total: data.total, + locked: NearToken::from_yoctonear( + data.total.as_yoctonear() - data.available.as_yoctonear(), + ), + }) + }) })) } diff --git a/src/tokens.rs b/src/tokens.rs index 47587c4..74b7437 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -1,5 +1,3 @@ -use std::marker::PhantomData; - use near_contract_standards::{ fungible_token::metadata::FungibleTokenMetadata, non_fungible_token::{metadata::NFTContractMetadata, Token}, @@ -27,7 +25,7 @@ use crate::{ tokens::{FTBalance, UserBalance, STORAGE_COST_PER_BYTE}, transactions::PrepopulateTransaction, }, - NetworkConfig, StorageDeposit, + Data, NetworkConfig, StorageDeposit, }; type Result = core::result::Result; @@ -278,12 +276,11 @@ impl Tokens { >, >, > { - let postprocess = MultiQueryHandler::new(( - CallResultHandler(PhantomData::), - CallResultHandler(PhantomData::), + let handler = MultiQueryHandler::new(( + CallResultHandler::::new(), + CallResultHandler::default(), )); - - let query_builder = MultiQueryBuilder::new(postprocess, BlockReference::latest()) + let multiquery = MultiQueryBuilder::new(handler, BlockReference::latest()) .add_query_builder(Self::ft_metadata(ft_contract.clone())?) .add_query_builder( Contract(ft_contract) @@ -295,11 +292,12 @@ impl Tokens { )? .read_only::<()>(), ) - .map(|(metadata, amount)| { - FTBalance::with_decimals(metadata.data.decimals).with_amount(amount.data.0) - }); - - Ok(query_builder) + .map( + |(metadata, amount): (Data, Data)| { + FTBalance::with_decimals(metadata.data.decimals).with_amount(amount.data.0) + }, + ); + Ok(multiquery) } /// Prepares a new transaction builder for sending tokens to another account. diff --git a/src/types/mod.rs b/src/types/mod.rs index 57a5337..cffb15d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -32,6 +32,16 @@ pub struct Data { pub block_hash: CryptoHash, } +impl Data { + pub fn map(self, f: impl FnOnce(T) -> U) -> Data { + Data { + data: f(self.data), + block_height: self.block_height, + block_hash: self.block_hash, + } + } +} + /// A wrapper around [near_jsonrpc_client::auth::ApiKey] /// /// This type is used to authenticate requests to the RPC node diff --git a/src/types/storage.rs b/src/types/storage.rs index d696411..66dd75a 100644 --- a/src/types/storage.rs +++ b/src/types/storage.rs @@ -1,33 +1,36 @@ use near_sdk::NearToken; use serde::de::{Deserialize, Deserializer}; -/// A type that represents the storage balance from the [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard -/// on some NEAR contract. +/// A type that represents the storage balance. Please note that this type is not part of the NEP-145 standard. +/// This type provides a more detailed view of the storage balance on the contract. +/// +/// [StorageBalanceInternal] is a [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard type. +/// This type is used internally to parse the storage balance from the contract and +/// to convert it into the [StorageBalance] type. /// /// As a storing data on-chain requires storage staking, the contracts require users to deposit NEAR to store user-rel. -#[derive(Debug, Clone, serde::Deserialize)] +#[derive(Debug, Clone)] pub struct StorageBalance { /// The available balance that might be used for storage. /// /// The user can withdraw this balance from the contract. - #[serde(deserialize_with = "parse_u128_string")] pub available: NearToken, /// The total user balance on the contract for storage. /// /// This is a sum of the `available` and `locked` balances. - #[serde(deserialize_with = "parse_u128_string")] pub total: NearToken, /// The storage deposit that is locked for the account /// /// The user can unlock some funds by removing the data from the contract. /// Though, it's contract-specific on how much can be unlocked. - #[serde(deserialize_with = "parse_u128_string")] pub locked: NearToken, } /// Used internally to parse the storage balance from the contract and /// to convert it into the [StorageBalance] type. +/// +/// This type is a part of the [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard. #[derive(Debug, Clone, serde::Deserialize)] pub struct StorageBalanceInternal { #[serde(deserialize_with = "parse_u128_string")] From 308caecfb1c45430870f1fa5d0ea6e0640910320 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Sat, 1 Mar 2025 12:51:43 +0200 Subject: [PATCH 13/13] clippy --- src/common/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/query.rs b/src/common/query.rs index bd800bb..82a465a 100644 --- a/src/common/query.rs +++ b/src/common/query.rs @@ -168,7 +168,7 @@ where handler: Handler, } -impl MultiRpcBuilder +impl MultiRpcBuilder where Reference: Send + Sync, Handler: Default + Send + Sync, @@ -570,7 +570,7 @@ where pub struct CallResultHandler(PhantomData); impl CallResultHandler { - pub fn new() -> Self { + pub const fn new() -> Self { Self(PhantomData::) } }