diff --git a/examples/nft.rs b/examples/nft.rs index 4d65438..974e54d 100644 --- a/examples/nft.rs +++ b/examples/nft.rs @@ -8,6 +8,7 @@ async fn main() { let network = near_workspaces::sandbox().await.unwrap(); let nft = network.dev_create_account().await.unwrap(); let account = network.dev_create_account().await.unwrap(); + let account2 = network.dev_create_account().await.unwrap(); let network = NetworkConfig::from(network); let nft_signer = Signer::new(Signer::from_workspace(&nft)).unwrap(); @@ -65,7 +66,7 @@ async fn main() { println!("Account has {}", tokens.data.first().unwrap().token_id); Tokens::account(account.id().clone()) - .send_to(nft.id().clone()) + .send_to(account2.id().clone()) .nft(nft.id().clone(), "1".to_string()) .unwrap() .with_signer(nft_signer.clone()) @@ -83,7 +84,7 @@ async fn main() { assert!(tokens.data.is_empty()); - let tokens = Tokens::account(nft.id().clone()) + let tokens = Tokens::account(account2.id().clone()) .nft_assets(nft.id().clone()) .unwrap() .fetch_from(&network) @@ -91,5 +92,5 @@ async fn main() { .unwrap(); assert_eq!(tokens.data.len(), 1); - println!("nft has {}", tokens.data.first().unwrap().token_id); + println!("account 2 has {}", tokens.data.first().unwrap().token_id); } diff --git a/src/account/create.rs b/src/account/create.rs index 5a7499d..dee1ddf 100644 --- a/src/account/create.rs +++ b/src/account/create.rs @@ -22,6 +22,9 @@ pub struct CreateAccountBuilder { } impl CreateAccountBuilder { + /// Create an NEAR account and fund it by your own + /// + /// You can only create an sub-account of your own account or sub-account of the linkdrop account ([near](https://nearblocks.io/address/near) on mainnet , [testnet](https://testnet.nearblocks.io/address/testnet) on testnet) pub fn fund_myself( self, signer_account_id: AccountId, @@ -85,13 +88,14 @@ impl CreateAccountBuilder { })) } - pub fn sponsor_by_faucet_service( - self, - account_id: AccountId, - ) -> PublicKeyProvider { + /// Create an account sponsored by faucet service + /// + /// This is a way to create an account without having to fund it. It works only on testnet. + /// You can only create an sub-account of the [testnet](https://testnet.nearblocks.io/address/testnet) account + pub fn sponsor_by_faucet_service(self) -> PublicKeyProvider { PublicKeyProvider::new(Box::new(move |public_key| { Ok(CreateAccountByFaucet { - new_account_id: account_id, + new_account_id: self.account_id, public_key, }) })) @@ -105,11 +109,24 @@ pub struct CreateAccountByFaucet { } impl CreateAccountByFaucet { + /// Sends the account creation request to the default testnet faucet service. + /// + /// The account will be created as a sub-account of the [testnet](https://testnet.nearblocks.io/address/testnet) account pub async fn send_to_testnet_faucet(self) -> Result { let testnet = NetworkConfig::testnet(); self.send_to_config_faucet(&testnet).await } + /// Sends the account creation request to the faucet service specified in the network config. + /// This way you can specify your own faucet service. + /// + /// The function sends the request in the following format: + /// ```json + /// { + /// "newAccountId": "new_account_id", + /// "newAccountPublicKey": "new_account_public_key" + /// } + /// ``` pub async fn send_to_config_faucet( self, config: &NetworkConfig, @@ -122,6 +139,15 @@ impl CreateAccountByFaucet { self.send_to_faucet(faucet_service_url).await } + /// Sends the account creation request to the faucet service specified by the URL. + /// + /// The function sends the request in the following format: + /// ```json + /// { + /// "newAccountId": "new_account_id", + /// "newAccountPublicKey": "new_account_public_key" + /// } + /// ``` pub async fn send_to_faucet(self, url: &Url) -> Result { let mut data = std::collections::HashMap::new(); data.insert("newAccountId", self.new_account_id.to_string()); diff --git a/src/account/mod.rs b/src/account/mod.rs index 2c413fa..13d952e 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -34,6 +34,18 @@ mod create; pub struct Account(pub AccountId); impl Account { + /// Prepares a query to fetch the [Data](crate::Data)<[AccountView](near_primitives::views::AccountView)> with the account information for the given account ID. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let account_info = Account("alice.testnet".parse()?).view().fetch_from_testnet().await?; + /// println!("Account: {:?}", account_info); + /// # Ok(()) + /// # } + /// ``` pub fn view(&self) -> QueryBuilder { let request = near_primitives::views::QueryRequest::ViewAccount { account_id: self.0.clone(), @@ -45,6 +57,23 @@ impl Account { ) } + /// Prepares a query to fetch the [Data](crate::Data)<[AccessKeyView](near_primitives::views::AccessKeyView)> with the access key information for the given account public key. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// use near_crypto::PublicKey; + /// use std::str::FromStr; + /// + /// # async fn example() -> Result<(), Box> { + /// let access_key = Account("alice.testnet".parse()?) + /// .access_key(PublicKey::from_str("ed25519:H4sIAAAAAAAAA+2X0Q6CMBAAtVlJQgYAAAA=")?) + /// .fetch_from_testnet() + /// .await?; + /// println!("Access key: {:?}", access_key); + /// # Ok(()) + /// # } + /// ``` pub fn access_key(&self, signer_public_key: PublicKey) -> QueryBuilder { let request = near_primitives::views::QueryRequest::ViewAccessKey { account_id: self.0.clone(), @@ -57,6 +86,18 @@ impl Account { ) } + /// Prepares a query to fetch the [AccessKeyList](near_primitives::views::AccessKeyList) list of access keys for the given account ID. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let access_keys = Account("alice.testnet".parse()?).list_keys().fetch_from_testnet().await?; + /// println!("Access keys: {:?}", access_keys); + /// # Ok(()) + /// # } + /// ``` pub fn list_keys(&self) -> QueryBuilder { let request = near_primitives::views::QueryRequest::ViewAccessKeyList { account_id: self.0.clone(), @@ -68,6 +109,25 @@ impl Account { ) } + /// Adds a new access key to the given account ID. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// use near_primitives::account::AccessKeyPermission; + /// use near_crypto::PublicKey; + /// use std::str::FromStr; + /// + /// # async fn example() -> Result<(), Box> { + /// let pk = PublicKey::from_str("ed25519:H4sIAAAAAAAAA+2X0Q6CMBAAtVlJQgYAAAA=")?; + /// let result: near_primitives::views::FinalExecutionOutcomeView = Account("alice.testnet".parse()?) + /// .add_key(AccessKeyPermission::FullAccess, pk) + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn add_key( &self, permission: AccessKeyPermission, @@ -84,6 +144,22 @@ impl Account { ) } + /// Deletes an access key from the given account ID. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// use near_crypto::PublicKey; + /// use std::str::FromStr; + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Account("alice.testnet".parse()?) + /// .delete_key(PublicKey::from_str("ed25519:H4sIAAAAAAAAA+2X0Q6CMBAAtVlJQgYAAAA=")?) + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn delete_key(&self, public_key: PublicKey) -> ConstructTransaction { ConstructTransaction::new(self.0.clone(), self.0.clone()).add_action( near_primitives::transaction::Action::DeleteKey(Box::new(DeleteKeyAction { @@ -92,6 +168,23 @@ impl Account { ) } + /// Deletes multiple access keys from the given account ID. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// use near_crypto::PublicKey; + /// use std::str::FromStr; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Account("alice.testnet".parse()?) + /// .delete_keys(vec![PublicKey::from_str("ed25519:H4sIAAAAAAAAA+2X0Q6CMBAAtVlJQgYAAAA=")?]) + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn delete_keys(&self, public_keys: Vec) -> ConstructTransaction { let actions = public_keys .into_iter() @@ -105,6 +198,27 @@ impl Account { ConstructTransaction::new(self.0.clone(), self.0.clone()).add_actions(actions) } + /// Deletes the account with the given beneficiary ID. The account balance will be transfered to the beneficiary. + /// + /// Please note that this action is irreversible. Also, you have to understand that another person could potentially + /// re-create the account with the same name and pretend to be you on other websites that use your account ID as a unique identifier. + /// (near.social, devhub proposal, etc) + /// + /// Do not use it unless you understand the consequences. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Account("alice.testnet".parse()?) + /// .delete_account_with_beneficiary("bob.testnet".parse()?) + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn delete_account_with_beneficiary( &self, beneficiary_id: AccountId, @@ -116,6 +230,72 @@ impl Account { ) } + /// Creates a new account builder for the given account ID. + /// + /// ## Creating account sponsored by faucet service + /// + /// This is a way to create an account without having to fund it. It works only on testnet. + /// The account should be created as a sub-account of the [testnet](https://testnet.nearblocks.io/address/testnet) account + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let secret = near_api::signer::generate_secret_key()?; + /// let result: reqwest::Response = Account::create_account("alice.testnet".parse()?) + /// .sponsor_by_faucet_service() + /// .public_key(secret.public_key())? + /// .send_to_testnet_faucet() + /// .await?; + /// // You have to save the secret key somewhere safe + /// std::fs::write("secret.key", secret.to_string())?; + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Creating sub-account of the linkdrop root account funded by your own NEAR and signed by your account + /// + /// There is a few linkdrop root accounts that you can use to create sub-accounts. + /// * For mainnet, you can use the [near](https://explorer.near.org/accounts/near) account. + /// * For testnet, you can use the [testnet](https://testnet.nearblocks.io/address/testnet) account. + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let secret = near_api::signer::generate_secret_key()?; + /// let bob_signer = Signer::new(Signer::from_seed_phrase("lucky barrel fall come bottom can rib join rough around subway cloth ", None)?)?; + /// let result: near_primitives::views::FinalExecutionOutcomeView = Account::create_account("alice.testnet".parse()?) + /// .fund_myself("bob.testnet".parse()?, NearToken::from_near(1)) + /// .public_key(secret.public_key())? + /// .with_signer(bob_signer) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Creating sub-account of your own account funded by your NEAR + /// + /// You can create only one level deep of sub-accounts. + /// + /// E.g you are `alice.testnet`, you can't create `sub.sub.alice.testnet`, but you can create `sub.alice.testnet`. + /// Though, 'sub.alice.testnet' can create sub-accounts of its own. + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let secret = near_api::signer::generate_secret_key()?; + /// let bob_signer = Signer::new(Signer::from_seed_phrase("lucky barrel fall come bottom can rib join rough around subway cloth ", None)?)?; + /// let result: near_primitives::views::FinalExecutionOutcomeView = Account::create_account("subaccount.bob.testnet".parse()?) + /// .fund_myself("bob.testnet".parse()?, NearToken::from_near(1)) + /// .public_key(secret.public_key())? + /// .with_signer(bob_signer) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub const fn create_account(account_id: AccountId) -> CreateAccountBuilder { CreateAccountBuilder { account_id } } diff --git a/src/chain.rs b/src/chain.rs index 95238da..70cfbf8 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -27,6 +27,31 @@ use crate::{ pub struct Chain; impl Chain { + /// Set ups a query to fetch the [BlockHeight] of the current block + /// + /// ## Fetching the latest block number + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let block_number = Chain::block_number().fetch_from_testnet().await?; + /// println!("Current block number: {}", block_number); + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Fetching the final block number + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let block_number = Chain::block_number().at(Reference::Final).fetch_from_testnet().await?; + /// println!("Final block number: {}", block_number); + /// # Ok(()) + /// # } + /// ``` pub fn block_number() -> BlockQueryBuilder> { BlockQueryBuilder::new( SimpleBlockRpc, @@ -38,6 +63,31 @@ impl Chain { ) } + /// Set ups a query to fetch the [CryptoHash] of the block + /// + /// ## Fetching the latest block hash + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let block_hash = Chain::block_hash().fetch_from_testnet().await?; + /// println!("Current block hash: {}", block_hash); + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Fetching the hash at a specific block number + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let block_hash = Chain::block_hash().at(Reference::AtBlock(1000000)).fetch_from_testnet().await?; + /// println!("Block hash at block number 1000000: {}", block_hash); + /// # Ok(()) + /// # } + /// ``` pub fn block_hash() -> BlockQueryBuilder> { BlockQueryBuilder::new( SimpleBlockRpc, @@ -49,13 +99,47 @@ impl Chain { ) } + /// Set ups a query to fetch the [BlockView] + /// + /// ## Fetching the latest block + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let block = Chain::block().fetch_from_testnet().await?; + /// println!("Current block: {:?}", block); + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Fetching the block at a specific block number + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let block = Chain::block().at(Reference::AtBlock(1000000)).fetch_from_testnet().await?; + /// println!("Block at block number 1000000: {:?}", block); + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Fetching the block at a specific block hash + /// + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// # let block_hash = near_api::types::CryptoHash::default(); + /// let block = Chain::block().at(Reference::AtBlockHash(block_hash)).fetch_from_testnet().await?; + /// println!("Block at block hash: {:?}", block); + /// # Ok(()) + /// # } + /// ``` pub fn block() -> BlockQueryBuilder { BlockQueryBuilder::new(SimpleBlockRpc, BlockReference::latest(), RpcBlockHandler) } - // TODO: fetch transaction status - // TODO: fetch transaction receipt - // TODO: fetch transaction proof - // TODO: fetch epoch id - // TODO: fetch epoch info + // TODO: chunk info } diff --git a/src/common/query.rs b/src/common/query.rs index 85a7bf9..1ae80b9 100644 --- a/src/common/query.rs +++ b/src/common/query.rs @@ -1,3 +1,4 @@ +// TODO: root level doc might be needed here. It's pretty complicated. use std::{marker::PhantomData, sync::Arc}; use futures::future::join_all; @@ -146,14 +147,14 @@ pub type MultiQueryBuilder = MultiRpcBuilder = RpcBuilder; pub type BlockQueryBuilder = RpcBuilder; -pub struct MultiRpcBuilder +pub struct MultiRpcBuilder where Reference: Send + Sync, - ResponseHandler: Send + Sync, + Handler: Send + Sync, { reference: Reference, requests: Vec + Send + Sync>>, - handler: ResponseHandler, + handler: Handler, } impl MultiRpcBuilder @@ -172,6 +173,8 @@ where } } + /// 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( mut self, request: Arc + Send + Sync>, @@ -180,18 +183,21 @@ where self } + /// It might be easier to use this method to add a query builder to the queried items. pub fn add_query_builder(mut self, query_builder: RpcBuilder) -> Self { self.requests.push(query_builder.request); self } - pub fn at(self, block_reference: Reference) -> Self { + /// Set the block reference for the queries. + pub fn at(self, reference: impl Into) -> Self { Self { - reference: block_reference, + reference: reference.into(), ..self } } + /// Fetch the queries from the provided network. #[instrument(skip(self, network), fields(request_count = self.requests.len()))] pub async fn fetch_from( self, @@ -245,11 +251,13 @@ where self.handler.process_response(requests) } + /// Fetch the queries from the default mainnet network configuration. pub async fn fetch_from_mainnet(self) -> ResultWithMethod { let network = NetworkConfig::mainnet(); self.fetch_from(&network).await } + /// Fetch the queries from the default testnet network configuration. pub async fn fetch_from_testnet(self) -> ResultWithMethod { let network = NetworkConfig::testnet(); self.fetch_from(&network).await @@ -282,6 +290,7 @@ where } } + /// Set the block reference for the query. pub fn at(self, reference: impl Into) -> Self { Self { reference: reference.into(), @@ -289,6 +298,7 @@ where } } + /// Fetch the query from the provided network. #[instrument(skip(self, network))] pub async fn fetch_from( self, @@ -321,11 +331,13 @@ where self.handler.process_response(vec![query_response]) } + /// Fetch the query from the default mainnet network configuration. pub async fn fetch_from_mainnet(self) -> ResultWithMethod { let network = NetworkConfig::mainnet(); self.fetch_from(&network).await } + /// Fetch the query from the default testnet network configuration. pub async fn fetch_from_testnet(self) -> ResultWithMethod { let network = NetworkConfig::testnet(); self.fetch_from(&network).await diff --git a/src/common/send.rs b/src/common/send.rs index 9d743c0..63ba0da 100644 --- a/src/common/send.rs +++ b/src/common/send.rs @@ -44,7 +44,9 @@ pub trait Transactionable: Send + Sync { } pub enum TransactionableOrSigned { + /// A transaction that is not signed. Transactionable(Box), + /// A transaction that is signed and ready to be sent to the network. Signed((Signed, Box)), } @@ -76,8 +78,13 @@ impl From for PrepopulateTransaction { } } +/// The handler for signing and sending transaction to the network. +/// +/// This is the main entry point for the transaction sending functionality. pub struct ExecuteSignedTransaction { + /// The transaction that is either not signed yet or already signed. pub tr: TransactionableOrSigned, + /// The signer that will be used to sign the transaction. pub signer: Arc, } @@ -89,10 +96,21 @@ impl ExecuteSignedTransaction { } } + /// Changes the transaction to a [meta transaction](https://docs.near.org/concepts/abstraction/meta-transactions), allowing some 3rd party entity to execute it and + /// pay for the gas. + /// + /// Please note, that if you already presigned the transaction, it would require you to sign it again as a meta transaction + /// is a different type of transaction. pub fn meta(self) -> ExecuteMetaTransaction { ExecuteMetaTransaction::from_box(self.tr.transactionable(), self.signer) } + /// Signs the transaction offline without fetching the nonce or block hash from the network. + /// + /// The transaction won't be broadcasted to the network and just stored signed in the [Self::tr] struct variable. + /// + /// This is useful if you already have the nonce and block hash, or if you want to sign the transaction + /// offline. Please note that incorrect nonce will lead to transaction failure. pub async fn presign_offline( mut self, public_key: PublicKey, @@ -113,6 +131,12 @@ impl ExecuteSignedTransaction { Ok(self) } + /// Signs the transaction with the custom network configuration but doesn't broadcast it. + /// + /// Signed transaction is stored in the [Self::tr] struct variable. + /// + /// This is useful if you want to sign with non-default network configuration (e.g, custom RPC URL, sandbox). + /// The provided call will fetch the nonce and block hash from the given network. pub async fn presign_with( self, network: &NetworkConfig, @@ -132,16 +156,30 @@ impl ExecuteSignedTransaction { Ok(self.presign_offline(signer_key, hash, nonce).await?) } + /// Signs the transaction with the default mainnet configuration. Does not broadcast it. + /// + /// Signed transaction is stored in the [Self::tr] struct variable. + /// + /// The provided call will fetch the nonce and block hash from the network. pub async fn presign_with_mainnet(self) -> Result { let network = NetworkConfig::mainnet(); self.presign_with(&network).await } + /// Signs the transaction with the default testnet configuration. Does not broadcast it. + /// + /// Signed transaction is stored in the [Self::tr] struct variable. + /// + /// The provided call will fetch the nonce and block hash from the network. pub async fn presign_with_testnet(self) -> Result { let network = NetworkConfig::testnet(); self.presign_with(&network).await } + /// Sends the transaction to the custom provided network. + /// + /// This is useful if you want to send the transaction to a non-default network configuration (e.g, custom RPC URL, sandbox). + /// Please note that if the transaction is not presigned, it will be signed with the network's nonce and block hash. pub async fn send_to( mut self, network: &NetworkConfig, @@ -189,6 +227,9 @@ impl ExecuteSignedTransaction { Self::send_impl(network, signed).await } + /// Sends the transaction to the default mainnet configuration. + /// + /// Please note that this will sign the transaction with the mainnet's nonce and block hash if it's not presigned yet. pub async fn send_to_mainnet( self, ) -> Result { @@ -196,6 +237,9 @@ impl ExecuteSignedTransaction { self.send_to(&network).await } + /// Sends the transaction to the default testnet configuration. + /// + /// Please note that this will sign the transaction with the testnet's nonce and block hash if it's not presigned yet. pub async fn send_to_testnet( self, ) -> Result { @@ -262,11 +306,22 @@ impl ExecuteMetaTransaction { } } + /// Sets the transaction live for the given block amount. + /// + /// This is useful if you want to set the transaction to be valid for a specific amount of blocks.\ + /// The default amount is 1000 blocks. pub const fn tx_live_for(mut self, tx_live_for: BlockHeight) -> Self { self.tx_live_for = Some(tx_live_for); self } + /// Signs the transaction offline without fetching the nonce or block hash from the network. Does not broadcast it. + /// + /// Signed transaction is stored in the [Self::tr] struct variable. + /// + /// This is useful if you already have the nonce and block hash, or if you want to sign the transaction + /// offline. Please note that incorrect nonce will lead to transaction failure and incorrect block height + /// will lead to incorrectly populated transaction live value. pub async fn presign_offline( mut self, signer_key: PublicKey, @@ -300,6 +355,11 @@ impl ExecuteMetaTransaction { Ok(self) } + /// Signs the transaction with the custom network configuration but doesn't broadcast it. + /// + /// Signed transaction is stored in the [Self::tr] struct variable. + /// + /// This is useful if you want to sign with non-default network configuration (e.g, custom RPC URL, sandbox). pub async fn presign_with( self, network: &NetworkConfig, @@ -327,16 +387,30 @@ impl ExecuteMetaTransaction { .await } + /// Signs the transaction with the default mainnet configuration but doesn't broadcast it. + /// + /// Signed transaction is stored in the [Self::tr] struct variable. + /// + /// The provided call will fetch the nonce and block hash, block height from the network. pub async fn presign_with_mainnet(self) -> Result { let network = NetworkConfig::mainnet(); self.presign_with(&network).await } + /// Signs the transaction with the default testnet configuration but doesn't broadcast it. + /// + /// Signed transaction is stored in the [Self::tr] struct variable. + /// + /// The provided call will fetch the nonce and block hash, block height from the network. pub async fn presign_with_testnet(self) -> Result { let network = NetworkConfig::testnet(); self.presign_with(&network).await } + /// Sends the transaction to the custom provided network. + /// + /// This is useful if you want to send the transaction to a non-default network configuration (e.g, custom RPC URL, sandbox). + /// Please note that if the transaction is not presigned, it will be sign with the network's nonce and block hash. pub async fn send_to( mut self, network: &NetworkConfig, @@ -384,11 +458,17 @@ impl ExecuteMetaTransaction { Self::send_impl(network, signed).await } + /// Sends the transaction to the default mainnet configuration. + /// + /// Please note that this will sign the transaction with the mainnet's nonce and block hash if it's not presigned yet. pub async fn send_to_mainnet(self) -> Result { let network = NetworkConfig::mainnet(); self.send_to(&network).await } + /// Sends the transaction to the default testnet configuration. + /// + /// Please note that this will sign the transaction with the testnet's nonce and block hash if it's not presigned yet. pub async fn send_to_testnet(self) -> Result { let network = NetworkConfig::testnet(); self.send_to(&network).await diff --git a/src/common/utils.rs b/src/common/utils.rs index 9521e60..493ac1b 100644 --- a/src/common/utils.rs +++ b/src/common/utils.rs @@ -2,6 +2,11 @@ use crate::errors::DecimalNumberParsingError; +/// Converts [crate::Data]<[u128]>] to [crate::NearToken]. +pub const fn near_data_to_near_token(data: crate::Data) -> crate::NearToken { + crate::NearToken::from_yoctonear(data.data) +} + /// Parsing decimal numbers from `&str` type in `u128`. /// Function also takes a value of metric prefix in u128 type. /// `parse_str` use the `u128` type, and have the same max and min values. diff --git a/src/contract.rs b/src/contract.rs index a5a64a5..980b202 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -42,6 +42,43 @@ use crate::{ pub struct Contract(pub AccountId); impl Contract { + /// Prepares a call to a contract function. + /// + /// This will return a builder that can be used to prepare a query or a transaction. + /// + /// ## Calling view function `get_number` + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let number: Data = Contract("some_contract.testnet".parse()?) + /// .call_function("get_number", ())? + /// .read_only() + /// .fetch_from_testnet() + /// .await?; + /// println!("Number: {:?}", number); + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Calling a state changing function `set_number` + /// ```rust,no_run + /// use near_api::*; + /// use serde_json::json; + /// + /// # async fn example() -> Result<(), Box> { + /// let signer = Signer::new(Signer::from_ledger())?; + /// let result: near_primitives::views::FinalExecutionOutcomeView = Contract("some_contract.testnet".parse()?) + /// .call_function("set_number", json!({ "number": 100 }))? + /// .transaction() + /// // Optional + /// .gas(NearGas::from_tgas(200)) + /// .with_signer("alice.testnet".parse()?, signer) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn call_function( &self, method_name: &str, @@ -59,10 +96,63 @@ impl Contract { }) } + /// Prepares a transaction to deploy a contract to the provided account. + /// + /// The code is the wasm bytecode of the contract. For more information on how to compile your contract, + /// please refer to the [NEAR documentation](https://docs.near.org/build/smart-contracts/quickstart). + /// + /// ## Deploying the contract + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let code = std::fs::read("path/to/your/contract.wasm")?; + /// let signer = Signer::new(Signer::from_ledger())?; + /// let result: near_primitives::views::FinalExecutionOutcomeView = Contract::deploy("contract.testnet".parse()?, code) + /// .without_init_call() + /// .with_signer(signer) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Deploying the contract with an init call + /// ```rust,no_run + /// use near_api::*; + /// use serde_json::json; + /// + /// # async fn example() -> Result<(), Box> { + /// let code = std::fs::read("path/to/your/contract.wasm")?; + /// let signer = Signer::new(Signer::from_ledger())?; + /// let result: near_primitives::views::FinalExecutionOutcomeView = Contract::deploy("contract.testnet".parse()?, code) + /// .with_init_call("init", json!({ "number": 100 }))? + /// // Optional + /// .gas(NearGas::from_tgas(200)) + /// .with_signer(signer) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub const fn deploy(contract: AccountId, code: Vec) -> DeployContractBuilder { DeployContractBuilder::new(contract, code) } + /// Prepares a query to fetch the [ABI](near_abi::AbiRoot) of the contract using the following [standard](https://github.com/near/near-abi-rs). + /// + /// Please be aware that not all the contracts provide the ABI. + /// + /// # Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let abi = Contract("some_contract.testnet".parse()?).abi().fetch_from_testnet().await?; + /// println!("ABI: {:?}", abi); + /// # Ok(()) + /// # } + /// ``` pub fn abi( &self, ) -> QueryBuilder, CallResultHandler>>> @@ -86,6 +176,18 @@ impl Contract { ) } + /// Prepares a query to fetch the wasm code ([Data]<[ContractCodeView](near_primitives::views::ContractCodeView)>) of the contract. + /// + /// # Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let wasm = Contract("some_contract.testnet".parse()?).wasm().fetch_from_testnet().await?; + /// println!("WASM: {:?}", wasm.data.code.len()); + /// # Ok(()) + /// # } + /// ``` pub fn wasm(&self) -> QueryBuilder { let request = near_primitives::views::QueryRequest::ViewCode { account_id: self.0.clone(), @@ -98,6 +200,23 @@ impl Contract { ) } + /// Prepares a query to fetch the storage of the contract ([Data]<[ViewStateResult](near_primitives::views::ViewStateResult)>) using the given prefix as a filter. + /// + /// It helpful if you are aware of the storage that you are looking for. + /// + /// # Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let storage = Contract("some_contract.testnet".parse()?) + /// .view_storage_with_prefix(b"se".to_vec()) + /// .fetch_from_testnet() + /// .await?; + /// println!("Storage: {:?}", storage); + /// # Ok(()) + /// # } + /// ``` pub fn view_storage_with_prefix(&self, prefix: Vec) -> QueryBuilder { let request = near_primitives::views::QueryRequest::ViewState { account_id: self.0.clone(), @@ -112,10 +231,45 @@ impl Contract { ) } + /// Prepares a query to fetch the storage of the contract ([Data]<[ViewStateResult](near_primitives::views::ViewStateResult)>). + /// + /// Please be aware that large storage queries might fail. + /// + /// # Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let storage = Contract("some_contract.testnet".parse()?) + /// .view_storage() + /// .fetch_from_testnet() + /// .await?; + /// println!("Storage: {:?}", storage); + /// # Ok(()) + /// # } + /// ``` pub fn view_storage(&self) -> QueryBuilder { 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. + /// + /// 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. + /// + /// # Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let source_metadata = Contract("some_contract.testnet".parse()?) + /// .contract_source_metadata() + /// .fetch_from_testnet() + /// .await?; + /// println!("Source metadata: {:?}", source_metadata); + /// # Ok(()) + /// # } + /// ``` pub fn contract_source_metadata( &self, ) -> QueryBuilder> { @@ -136,28 +290,89 @@ impl DeployContractBuilder { Self { contract, code } } + /// Prepares a transaction to deploy a contract to the provided account without an init call. + /// + /// This will deploy the contract without calling any function. pub fn without_init_call(self) -> ConstructTransaction { Transaction::construct(self.contract.clone(), self.contract.clone()).add_action( Action::DeployContract(DeployContractAction { code: self.code }), ) } + /// Prepares a transaction to deploy a contract to the provided account with an init call. + /// + /// This will deploy the contract and call the init function with the provided arguments as a single transaction. pub fn with_init_call( self, method_name: &str, args: Args, - ) -> Result { + ) -> Result { let args = serde_json::to_vec(&args)?; - Ok(ContractTransactBuilder::new( + Ok(DeployContractTransactBuilder::new( self.contract.clone(), method_name.to_string(), args, - Some(Action::DeployContract(DeployContractAction { + self.code, + )) + } +} + +#[derive(Clone, Debug)] +pub struct DeployContractTransactBuilder { + contract: AccountId, + method_name: String, + args: Vec, + code: Vec, + gas: Option, + deposit: Option, +} + +impl DeployContractTransactBuilder { + const fn new(contract: AccountId, method_name: String, args: Vec, code: Vec) -> Self { + Self { + contract, + method_name, + args, + code, + gas: None, + deposit: None, + } + } + + /// Specify the gas limit for the transaction. By default it is set to 100 TGas. + pub const fn gas(mut self, gas: NearGas) -> Self { + self.gas = Some(gas); + self + } + + /// Specify the near deposit for the transaction. By default it is set to 0. + /// + /// Please note that the method should be [`payable`](https://docs.near.org/build/smart-contracts/anatomy/functions#payable-functions) in the contract to accept the deposit. + /// Otherwise the transaction will fail. + pub const fn deposit(mut self, deposit: NearToken) -> Self { + self.deposit = Some(deposit); + self + } + + /// Specify the signer for the transaction. This will wrap-up the process of the preparing transaction. + /// + /// This will return the [`ExecuteSignedTransaction`] that can be used to sign and send the transaction to the network. + pub fn with_signer(self, signer: Arc) -> ExecuteSignedTransaction { + let gas = self.gas.unwrap_or_else(|| NearGas::from_tgas(100)); + let deposit = self.deposit.unwrap_or_else(|| NearToken::from_yoctonear(0)); + + Transaction::construct(self.contract.clone(), self.contract) + .add_action(Action::DeployContract(DeployContractAction { code: self.code, - })), - ) - .with_signer_account(self.contract)) + })) + .add_action(Action::FunctionCall(Box::new(FunctionCallAction { + method_name: self.method_name.to_owned(), + args: self.args, + gas: gas.as_gas(), + deposit: deposit.as_yoctonear(), + }))) + .with_signer(signer) } } @@ -169,6 +384,21 @@ pub struct CallFunctionBuilder { } impl CallFunctionBuilder { + /// Prepares a read-only query that doesn't require a signing transaction. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance: Data = Contract("some_contract.testnet".parse()?).call_function("get_balance", ())?.read_only().fetch_from_testnet().await?; + /// println!("Balance: {:?}", balance); + /// + /// let balance_at_block: Data = Contract("some_contract.testnet".parse()?).call_function("get_balance", ())?.read_only().at(Reference::AtBlock(1000000)).fetch_from_testnet().await?; + /// println!("Balance at block 1000000: {:?}", balance_at_block); + /// # Ok(()) + /// # } + /// ``` pub fn read_only( self, ) -> QueryBuilder> { @@ -185,8 +415,11 @@ impl CallFunctionBuilder { ) } + /// 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. pub fn transaction(self) -> ContractTransactBuilder { - ContractTransactBuilder::new(self.contract, self.method_name, self.args, None) + ContractTransactBuilder::new(self.contract, self.method_name, self.args) } } @@ -195,38 +428,39 @@ pub struct ContractTransactBuilder { contract: AccountId, method_name: String, args: Vec, - pre_action: Option, gas: Option, deposit: Option, } impl ContractTransactBuilder { - const fn new( - contract: AccountId, - method_name: String, - args: Vec, - pre_action: Option, - ) -> Self { + const fn new(contract: AccountId, method_name: String, args: Vec) -> Self { Self { contract, method_name, args, - pre_action, gas: None, deposit: None, } } + /// Specify the gas limit for the transaction. By default it is set to 100 TGas. pub const fn gas(mut self, gas: NearGas) -> Self { self.gas = Some(gas); self } + /// Specify the near deposit for the transaction. By default it is set to 0. + /// + /// Please note that the method should be [`payable`](https://docs.near.org/build/smart-contracts/anatomy/functions#payable-functions) in the contract to accept the deposit. + /// Otherwise the transaction will fail. pub const fn deposit(mut self, deposit: NearToken) -> Self { self.deposit = Some(deposit); self } + /// Specify the signer for the transaction. This will wrap-up the process of the preparing transaction. + /// + /// This will return the [`ExecuteSignedTransaction`] that can be used to sign and send the transaction to the network. pub fn with_signer( self, signer_id: AccountId, @@ -240,17 +474,13 @@ impl ContractTransactBuilder { let gas = self.gas.unwrap_or_else(|| NearGas::from_tgas(100)); let deposit = self.deposit.unwrap_or_else(|| NearToken::from_yoctonear(0)); - let tx: ConstructTransaction = if let Some(preaction) = self.pre_action { - Transaction::construct(signer_id, self.contract).add_action(preaction) - } else { - Transaction::construct(signer_id, self.contract) - }; - - tx.add_action(Action::FunctionCall(Box::new(FunctionCallAction { - method_name: self.method_name.to_owned(), - args: self.args, - gas: gas.as_gas(), - deposit: deposit.as_yoctonear(), - }))) + Transaction::construct(signer_id, self.contract).add_action(Action::FunctionCall(Box::new( + FunctionCallAction { + method_name: self.method_name.to_owned(), + args: self.args, + gas: gas.as_gas(), + deposit: deposit.as_yoctonear(), + }, + ))) } } diff --git a/src/lib.rs b/src/lib.rs index baa759f..1e20d17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,8 @@ //! - [Account management and creation](Account) //! - [Contract deployment and interaction with it](Contract) //! - [Token operations](Tokens) ([`NEAR`](https://docs.near.org/concepts/basics/tokens), [`FT`](https://docs.near.org/build/primitives/ft), [`NFT`](https://docs.near.org/build/primitives/nft)) -//! - [Storage management and staking operations](Staking) +//! - [Storage management](StorageDeposit) +//! - [Staking operations](Staking) //! - [Custom transaction building and signing](Transaction) //! - [Querying the chain data](Chain) //! - [Several ways to sign the transaction](signer) @@ -71,7 +72,7 @@ pub use crate::{ config::{NetworkConfig, RPCEndpoint, RetryMethod}, contract::Contract, signer::{Signer, SignerTrait}, - stake::Staking, + stake::{Delegation, Staking}, storage::StorageDeposit, tokens::Tokens, transactions::Transaction, @@ -82,5 +83,8 @@ pub use crate::{ }, }; +pub use near_primitives; + pub use near_account_id::AccountId; +pub use near_gas::NearGas; pub use near_token::NearToken; diff --git a/src/stake.rs b/src/stake.rs index 96a755e..a6a6519 100644 --- a/src/stake.rs +++ b/src/stake.rs @@ -12,27 +12,41 @@ use crate::{ QueryBuilder, QueryCreator, RpcValidatorHandler, SimpleQuery, SimpleValidatorRpc, ValidatorQueryBuilder, ViewStateHandler, }, - utils::is_critical_query_error, + utils::{is_critical_query_error, near_data_to_near_token}, }, contract::Contract, errors::{BuilderError, QueryCreationError, QueryError}, transactions::ConstructTransaction, - types::{ - stake::{RewardFeeFraction, StakingPoolInfo, UserStakeBalance}, - Data, - }, + types::stake::{RewardFeeFraction, StakingPoolInfo, UserStakeBalance}, }; -const fn near_data_to_near_token(data: Data) -> NearToken { - NearToken::from_yoctonear(data.data) -} - type Result = core::result::Result; +/// 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. +/// +/// Delegation is a wrapper that provides the functionality to manage user account stake in +/// the staking pool. #[derive(Clone, Debug)] pub struct Delegation(pub AccountId); impl Delegation { + /// Prepares a new contract query (`get_account_staked_balance`) for fetching the staked balance ([NearToken]) of the account on the staking pool. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance = Staking::delegation("alice.testnet".parse()?) + /// .view_staked_balance("pool.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("Staked balance: {:?}", balance); + /// # Ok(()) + /// # } + /// ``` pub fn view_staked_balance( &self, pool: AccountId, @@ -56,6 +70,23 @@ impl Delegation { )) } + /// 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. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance = Staking::delegation("alice.testnet".parse()?) + /// .view_unstaked_balance("pool.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("Unstaked balance: {:?}", balance); + /// # Ok(()) + /// # } + /// ``` pub fn view_unstaked_balance( &self, pool: AccountId, @@ -79,6 +110,23 @@ impl Delegation { )) } + /// Prepares a new contract query (`get_account_total_balance`) for fetching the total balance ([NearToken]) of the account (free + staked) on the staking pool. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance = Staking::delegation("alice.testnet".parse()?) + /// .view_total_balance("pool.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("Total balance: {:?}", balance); + /// # Ok(()) + /// # } + /// ``` pub fn view_total_balance( &self, pool: AccountId, @@ -102,6 +150,24 @@ impl Delegation { )) } + /// Returns a full information about the staked balance ([UserStakeBalance]) of the account on the staking pool. + /// + /// 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. + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance = Staking::delegation("alice.testnet".parse()?) + /// .view_balance("pool.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("Balance: {:?}", balance); + /// # Ok(()) + /// # } + /// ``` #[allow(clippy::complexity)] pub fn view_balance( &self, @@ -145,6 +211,25 @@ impl Delegation { Ok(multiquery) } + /// Prepares a new contract query (`is_account_unstaked_balance_available`) for checking if the unstaked balance of the account is available for withdrawal. + /// + /// Some pools configures minimum withdrawal period in epochs, so the balance is not available for withdrawal immediately. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let is_available = Staking::delegation("alice.testnet".parse()?) + /// .is_account_unstaked_balance_available_for_withdrawal("pool.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("Is available: {:?}", is_available); + /// # Ok(()) + /// # } + /// ``` pub fn is_account_unstaked_balance_available_for_withdrawal( &self, pool: AccountId, @@ -166,6 +251,26 @@ impl Delegation { )) } + /// Prepares a new transaction contract call (`deposit`) for depositing funds into the staking pool. + /// Please note that your deposit is not staked, and it will be allocated as unstaked (free) balance. + /// + /// 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. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Staking::delegation("alice.testnet".parse()?) + /// .deposit("pool.testnet".parse()?, NearToken::from_near(1))? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn deposit(&self, pool: AccountId, amount: NearToken) -> Result { Ok(Contract(pool) .call_function("deposit", ())? @@ -175,6 +280,28 @@ impl Delegation { .with_signer_account(self.0.clone())) } + /// Prepares a new transaction contract call (`deposit_and_stake`) for depositing funds into the staking pool and staking them. + /// + /// 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. + /// 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. + /// * Mandatory lockup before able to unstake + /// * Optional lockup before able to withdraw (depends on the pool configuration) + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Staking::delegation("alice.testnet".parse()?) + /// .deposit_and_stake("pool.testnet".parse()?, NearToken::from_near(1))? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn deposit_and_stake( &self, pool: AccountId, @@ -188,6 +315,29 @@ impl Delegation { .with_signer_account(self.0.clone())) } + /// Prepares a new transaction contract call (`stake`) for staking funds into the staking pool. + /// + /// Please note that this call will use your unstaked balance. This means that you have to have enough balance already deposited into the contract. + /// This won't use your native account tokens, but just reallocate your balance inside the contract. + /// Please also be aware that once you have staked your funds, you might not be able to withdraw them until the lockup periods end. + /// * Mandatory lockup before able to unstake + /// * Optional lockup before able to withdraw (depends on the pool configuration) + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Staking::delegation("alice.testnet".parse()?) + /// .stake("pool.testnet".parse()?, NearToken::from_near(1))? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn stake(&self, pool: AccountId, amount: NearToken) -> Result { let args = serde_json::json!({ "amount": amount.as_yoctonear(), @@ -200,6 +350,27 @@ impl Delegation { .with_signer_account(self.0.clone())) } + /// Prepares a new transaction contract call (`stake_all`) for staking all available unstaked balance into the staking pool. + /// + /// Please note that once you have staked your funds, you might not be able to withdraw them until the lockup periods end. + /// * Mandatory lockup before able to unstake + /// * Optional lockup before able to withdraw (depends on the pool configuration) + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// Staking::delegation("alice.testnet".parse()?) + /// .stake_all("pool.testnet".parse()?)? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn stake_all(&self, pool: AccountId) -> Result { Ok(Contract(pool) .call_function("stake_all", ())? @@ -208,6 +379,25 @@ impl Delegation { .with_signer_account(self.0.clone())) } + /// Prepares a new transaction contract call (`unstake`) for unstaking funds and returning them to your unstaked balance. + /// + /// The contract will check if the minimum epoch height condition is met. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Staking::delegation("alice.testnet".parse()?) + /// .unstake("pool.testnet".parse()?, NearToken::from_near(1))? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn unstake(&self, pool: AccountId, amount: NearToken) -> Result { let args = serde_json::json!({ "amount": amount.as_yoctonear(), @@ -220,6 +410,25 @@ impl Delegation { .with_signer_account(self.0.clone())) } + /// Prepares a new transaction contract call (`unstake_all`) for unstaking all available staked balance and returning them to your unstaked balance. + /// + /// The contract will check if the minimum epoch height condition is met. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Staking::delegation("alice.testnet".parse()?) + /// .unstake_all("pool.testnet".parse()?)? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn unstake_all(&self, pool: AccountId) -> Result { Ok(Contract(pool) .call_function("unstake_all", ())? @@ -228,6 +437,25 @@ impl Delegation { .with_signer_account(self.0.clone())) } + /// Prepares a new transaction contract call (`withdraw`) for withdrawing funds from the staking pool into your account. + /// + /// Some pools configures minimum withdrawal period in epochs, so the balance is not available for withdrawal immediately. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Staking::delegation("alice.testnet".parse()?) + /// .withdraw("pool.testnet".parse()?, NearToken::from_near(1))? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn withdraw(&self, pool: AccountId, amount: NearToken) -> Result { let args = serde_json::json!({ "amount": amount.as_yoctonear(), @@ -240,6 +468,25 @@ impl Delegation { .with_signer_account(self.0.clone())) } + /// Prepares a new transaction contract call (`withdraw_all`) for withdrawing all available staked balance from the staking pool into your account. + /// + /// Some pools configures minimum withdrawal period in epochs, so the balance is not available for withdrawal immediately. + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let result: near_primitives::views::FinalExecutionOutcomeView = Staking::delegation("alice.testnet".parse()?) + /// .withdraw_all("pool.testnet".parse()?)? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn withdraw_all(&self, pool: AccountId) -> Result { Ok(Contract(pool) .call_function("withdraw_all", ())? @@ -249,7 +496,7 @@ impl Delegation { } } -/// Staking-related interactions with the NEAR Protocol +/// Staking-related interactions with the NEAR Protocol and the staking pools. /// /// The [`Staking`] struct provides methods to interact with NEAR staking, including querying staking pools, validators, and delegators, /// as well as delegating and withdrawing from staking pools. @@ -269,6 +516,20 @@ impl Delegation { pub struct Staking {} impl Staking { + /// Returns a list of active staking pools ([std::collections::BTreeSet]<[AccountId]>]) by querying the staking pools factory contract. + /// + /// Please note that it might fail on the mainnet as the staking pool factory is super huge. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let staking_pools = Staking::active_staking_pools().fetch_from_testnet().await?; + /// println!("Staking pools: {:?}", staking_pools); + /// # Ok(()) + /// # } + /// ``` pub fn active_staking_pools( ) -> QueryBuilder, ViewStateHandler>> { @@ -286,6 +547,18 @@ impl Staking { ) } + /// Returns a list of validators and their stake ([near_primitives::views::EpochValidatorInfo]) for the current epoch. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let validators = Staking::epoch_validators_info().fetch_from_testnet().await?; + /// println!("Validators: {:?}", validators); + /// # Ok(()) + /// # } + /// ``` pub fn epoch_validators_info() -> ValidatorQueryBuilder { ValidatorQueryBuilder::new( SimpleValidatorRpc, @@ -294,6 +567,18 @@ impl Staking { ) } + /// Returns a map of validators and their stake ([BTreeMap]) for the current epoch. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let validators = Staking::validators_stake().fetch_from_testnet().await?; + /// println!("Validators: {:?}", validators); + /// # Ok(()) + /// # } + /// ``` pub fn validators_stake() -> ValidatorQueryBuilder< PostprocessHandler, RpcValidatorHandler>, > { @@ -330,6 +615,21 @@ impl Staking { ) } + /// Prepares a new contract query (`get_reward_fee_fraction`) for fetching the reward fee fraction of the staking pool ([Data](crate::Data)<[RewardFeeFraction]>). + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let reward_fee = Staking::staking_pool_reward_fee("pool.testnet".parse()?) + /// .fetch_from_testnet().await?; + /// println!("Reward fee: {:?}", reward_fee); + /// # Ok(()) + /// # } + /// ``` pub fn staking_pool_reward_fee( pool: AccountId, ) -> QueryBuilder> { @@ -339,6 +639,22 @@ impl Staking { .read_only() } + /// Prepares a new contract query (`get_number_of_accounts`) for fetching the number of delegators of the staking pool ([Data](crate::Data)<[u64]>). + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let delegators = Staking::staking_pool_delegators("pool.testnet".parse()?) + /// .fetch_from_testnet() + /// .await?; + /// println!("Delegators: {:?}", delegators); + /// # Ok(()) + /// # } + /// ``` pub fn staking_pool_delegators(pool: AccountId) -> QueryBuilder> { Contract(pool) .call_function("get_number_of_accounts", ()) @@ -346,6 +662,22 @@ impl Staking { .read_only() } + /// Prepares a new contract query (`get_total_staked_balance`) for fetching the total stake of the staking pool ([NearToken]). + /// + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let total_stake = Staking::staking_pool_total_stake("pool.testnet".parse()?) + /// .fetch_from_testnet() + /// .await?; + /// println!("Total stake: {:?}", total_stake); + /// # Ok(()) + /// # } + /// ``` pub fn staking_pool_total_stake( pool: AccountId, ) -> QueryBuilder>> { @@ -365,6 +697,23 @@ impl Staking { ) } + /// Returns a full information about the staking pool ([StakingPoolInfo]). + /// + /// 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. + /// The call depends that the contract implements [`StakingPool`](https://github.com/near/core-contracts/tree/master/staking-pool) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let staking_pool_info = Staking::staking_pool_info("pool.testnet".parse()?) + /// .fetch_from_testnet() + /// .await?; + /// println!("Staking pool info: {:?}", staking_pool_info); + /// # Ok(()) + /// # } + /// ``` #[allow(clippy::complexity)] pub fn staking_pool_info( pool: AccountId, @@ -404,6 +753,7 @@ impl Staking { .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. pub const fn delegation(account_id: AccountId) -> Delegation { Delegation(account_id) } diff --git a/src/storage.rs b/src/storage.rs index 9009b1c..d41d2d5 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -10,11 +10,11 @@ use crate::{ types::storage::StorageBalance, }; -/// A wrapper struct that simplifies interactions with NEAR storage management standard. +///A wrapper struct that simplifies interactions with the [Storage Management](https://nomicon.io/Standards/StorageManagement) standard /// -/// Contracts on NEAR Protocol often implement a [standard interface](https://nomicon.io/Standards/StorageManagement) for managing storage deposits, +/// Contracts on NEAR Protocol often implement a [NEP-145](https://nomicon.io/Standards/StorageManagement) 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. +/// to interact with these storage-related functions on the contract. /// /// # Example /// ``` @@ -44,6 +44,21 @@ impl StorageDeposit { Self(contract_id) } + /// Prepares a new contract query (`storage_balance_of`) for fetching the storage balance (Option<[StorageBalance]>) of the account on the contract. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let balance = StorageDeposit::on_contract("contract.testnet".parse()?) + /// .view_account_storage("alice.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("Storage balance: {:?}", balance); + /// # Ok(()) + /// # } + /// ``` pub fn view_account_storage( &self, account_id: AccountId, @@ -52,12 +67,27 @@ impl StorageDeposit { .call_function( "storage_balance_of", json!({ - "account_id": account_id, + "account_id": account_id, }), )? .read_only()) } + /// Prepares a new transaction contract call (`storage_deposit`) for depositing storage on the contract. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let tx: near_primitives::views::FinalExecutionOutcomeView = StorageDeposit::on_contract("contract.testnet".parse()?) + /// .deposit("alice.testnet".parse()?, NearToken::from_near(1))? + /// .with_signer("bob.testnet".parse()?, Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn deposit( &self, receiver_account_id: AccountId, @@ -74,6 +104,21 @@ impl StorageDeposit { .deposit(amount)) } + /// Prepares a new transaction contract call (`storage_withdraw`) for withdrawing storage from the contract. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let tx: near_primitives::views::FinalExecutionOutcomeView = StorageDeposit::on_contract("contract.testnet".parse()?) + /// .withdraw("alice.testnet".parse()?, NearToken::from_near(1))? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn withdraw( &self, account_id: AccountId, diff --git a/src/tokens.rs b/src/tokens.rs index d2a2745..a1bca8c 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -34,16 +34,21 @@ use crate::{ type Result = core::result::Result; -/// A wrapper struct that simplifies interactions with NEAR tokens (NEAR, FT, NFT). +// This is not too long as most of the size is a links to the docs +#[allow(clippy::too_long_first_doc_paragraph)] +/// A wrapper struct that simplifies interactions with +/// [NEAR](https://docs.near.org/concepts/basics/tokens), +/// [FT](https://docs.near.org/build/primitives/ft), +/// [NFT](https://docs.near.org/build/primitives/nft) /// /// 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](https://docs.near.org/build/primitives/ft) (FT) standard operations -/// - [Non-Fungible Token](https://docs.near.org/build/primitives/nft) (NFT) standard 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) /// -/// # Examples +/// ## Examples /// -/// ## Fungible Token Operations +/// ### Fungible Token Operations /// ``` /// use near_api::*; /// @@ -67,7 +72,7 @@ type Result = core::result::Result; /// # } /// ``` /// -/// ## NFT Operations +/// ### NFT Operations /// ``` /// use near_api::*; /// @@ -88,7 +93,7 @@ type Result = core::result::Result; /// # } /// ``` /// -/// ## NEAR Token Operations +/// ### NEAR Token Operations /// ``` /// use near_api::*; /// @@ -118,6 +123,19 @@ impl Tokens { Self { account_id } } + /// Fetches the total NEAR balance ([UserBalance]) of the account. + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let alice_tokens = Tokens::account("alice.testnet".parse()?); + /// let balance = alice_tokens.near_balance().fetch_from_testnet().await?; + /// println!("Alice's NEAR balance: {:?}", balance); + /// # Ok(()) + /// # } + /// ``` pub fn near_balance( &self, ) -> QueryBuilder> { @@ -145,6 +163,22 @@ impl Tokens { ) } + /// Prepares a new contract query (`nft_metadata`) for fetching the NFT metadata ([NFTContractMetadata]). + /// + /// The function depends that the contract implements [`NEP-171`](https://nomicon.io/Standards/Tokens/NonFungibleToken/Core#nep-171) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let metadata = Tokens::nft_metadata("nft-contract.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("NFT metadata: {:?}", metadata); + /// # Ok(()) + /// # } + /// ``` pub fn nft_metadata( contract_id: AccountId, ) -> Result>> { @@ -153,6 +187,23 @@ impl Tokens { .read_only()) } + /// Prepares a new contract query (`nft_tokens_for_owner`) for fetching the NFT assets of the account ([Vec]<[Token]>). + /// + /// The function depends that the contract implements [`NEP-171`](https://nomicon.io/Standards/Tokens/NonFungibleToken/Core#nep-171) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let alice_tokens = Tokens::account("alice.testnet".parse()?); + /// let alice_assets = alice_tokens.nft_assets("nft-contract.testnet".parse()?)? + /// .fetch_from_testnet() + /// .await?; + /// println!("Alice's NFT assets: {:?}", alice_assets); + /// # Ok(()) + /// # } + /// ``` pub fn nft_assets( &self, nft_contract: AccountId, @@ -167,6 +218,23 @@ impl Tokens { .read_only()) } + /// Prepares a new contract query (`ft_metadata`) for fetching the FT metadata ([FungibleTokenMetadata]). + /// + /// The function depends that the contract implements [`NEP-141`](https://nomicon.io/Standards/Tokens/FungibleToken/Core#nep-141) + /// + /// ## Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let metadata = Tokens::ft_metadata("usdt.tether-token.near".parse()?)? + /// .fetch_from_testnet() + /// .await? + /// .data; + /// println!("FT metadata: {} {}", metadata.name, metadata.symbol); + /// # Ok(()) + /// # } + /// ``` pub fn ft_metadata( contract_id: AccountId, ) -> Result>> { @@ -175,6 +243,26 @@ impl Tokens { .read_only()) } + /// Prepares a new contract query (`ft_balance_of`, `ft_metadata`) for fetching the [FTBalance] of the account. + /// + /// This query is a multi-query, meaning it will fetch the FT metadata and the FT balance of the account. + /// The result is then postprocessed to create a `FTBalance` instance. + /// + /// The function depends that the contract implements [`NEP-141`](https://nomicon.io/Standards/Tokens/FungibleToken/Core#nep-141) + /// + /// # Example + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let alice_usdt_balance = Tokens::account("alice.near".parse()?) + /// .ft_balance("usdt.tether-token.near".parse()?)? + /// .fetch_from_mainnet() + /// .await?; + /// println!("Alice's USDT balance: {}", alice_usdt_balance); + /// # Ok(()) + /// # } + /// ``` #[allow(clippy::complexity)] pub fn ft_balance( &self, @@ -216,8 +304,59 @@ impl Tokens { Ok(query_builder) } - pub fn send_to(&self, receiver_id: AccountId) -> SendTo { - SendTo { + /// Prepares a new transaction builder for sending tokens to another account. + /// + /// This builder is used to construct transactions for sending NEAR, FT, and NFT tokens. + /// + /// ## Sending NEAR + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let alice_tokens = Tokens::account("alice.near".parse()?); + /// + /// let result: near_primitives::views::FinalExecutionOutcomeView = alice_tokens.send_to("bob.near".parse()?) + /// .near(NearToken::from_near(1)) + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_mainnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Sending FT + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let alice_tokens = Tokens::account("alice.near".parse()?); + /// + /// let result: near_primitives::views::FinalExecutionOutcomeView = alice_tokens.send_to("bob.near".parse()?) + /// .ft("usdt.tether-token.near".parse()?, USDT_BALANCE.with_whole_amount(100))? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_mainnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Sending NFT + /// ```rust,no_run + /// use near_api::*; + /// + /// # async fn example() -> Result<(), Box> { + /// let alice_tokens = Tokens::account("alice.near".parse()?); + /// + /// let result: near_primitives::views::FinalExecutionOutcomeView = alice_tokens.send_to("bob.near".parse()?) + /// .nft("nft-contract.testnet".parse()?, "token-id".to_string())? + /// .with_signer(Signer::new(Signer::from_ledger())?) + /// .send_to_testnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn send_to(&self, receiver_id: AccountId) -> SendToBuilder { + SendToBuilder { from: self.account_id.clone(), receiver_id, } @@ -225,12 +364,13 @@ impl Tokens { } #[derive(Debug, Clone)] -pub struct SendTo { +pub struct SendToBuilder { from: AccountId, receiver_id: AccountId, } -impl SendTo { +impl SendToBuilder { + /// Prepares a new transaction for sending NEAR tokens to another account. pub fn near(self, amount: NearToken) -> ConstructTransaction { ConstructTransaction::new(self.from, self.receiver_id).add_action(Action::Transfer( TransferAction { @@ -239,6 +379,12 @@ impl SendTo { )) } + /// Prepares a new transaction contract call (`ft_transfer`, `ft_metadata`, `storage_balance_of`, `storage_deposit`) for sending FT tokens to another account. + /// + /// Please note that if the receiver does not have enough storage, we will automatically deposit 100 milliNEAR for storage from + /// the sender. + /// + /// The provided function depends that the contract implements [`NEP-141`](https://nomicon.io/Standards/Tokens/FungibleToken/Core#nep-141) pub fn ft( self, ft_contract: AccountId, @@ -265,6 +411,9 @@ impl SendTo { }) } + /// Prepares a new transaction contract call (`nft_transfer`) for sending NFT tokens to another account. + /// + /// The provided function depends that the contract implements [`NEP-171`](https://nomicon.io/Standards/Tokens/NonFungibleToken/Core#nep-171) pub fn nft(self, nft_contract: AccountId, token_id: String) -> Result { Ok(Contract(nft_contract) .call_function( @@ -280,6 +429,9 @@ impl SendTo { } } +/// The structs validates the decimals correctness on runtime level before +/// sending the ft tokens as well as deposits 100milliNear of the deposit if +/// the receiver doesn't have any allocated storage in the provided FT contract #[derive(Clone, Debug)] pub struct FTTransactionable { prepopulated: PrepopulateTransaction, diff --git a/src/transactions.rs b/src/transactions.rs index f22cb96..ce5e17e 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -21,12 +21,14 @@ impl TransactionWithSign { } } +/// A builder for constructing transactions using Actions. #[derive(Debug, Clone)] pub struct ConstructTransaction { pub tr: PrepopulateTransaction, } impl ConstructTransaction { + /// Prepopulates a transaction with the given signer and receiver IDs. pub const fn new(signer_id: AccountId, receiver_id: AccountId) -> Self { Self { tr: PrepopulateTransaction { @@ -37,16 +39,19 @@ impl ConstructTransaction { } } + /// Adds an action to the transaction. pub fn add_action(mut self, action: Action) -> Self { self.tr.actions.push(action); self } - pub fn add_actions(mut self, action: Vec) -> Self { - self.tr.actions.extend(action); + /// Adds multiple actions to the transaction. + pub fn add_actions(mut self, actions: Vec) -> Self { + self.tr.actions.extend(actions); self } + /// Signs the transaction with the given signer. pub fn with_signer(self, signer: Arc) -> ExecuteSignedTransaction { ExecuteSignedTransaction::new(self, signer) } @@ -67,44 +72,78 @@ impl Transactionable for ConstructTransaction { } } -/// Low-level transaction builder. +/// Transaction related functionality. /// -/// This struct provides a low-level interface for constructing and signing transactions. -/// It is designed to be used in scenarios where more control over the transaction process is required. -/// -/// # Examples -/// -/// ```rust,no_run -/// use near_api::*; -/// use near_primitives::{action::Action, transaction::TransferAction}; -/// -/// # async fn example() -> Result<(), Box> { -/// let signer = Signer::new(Signer::from_ledger())?; -/// -/// // Construct a transaction to transfer tokens -/// let transaction_result = Transaction::construct( -/// "sender.near".parse()?, -/// "receiver.near".parse()? -/// ) -/// .add_action(Action::Transfer( -/// TransferAction { -/// deposit: NearToken::from_near(1).as_yoctonear(), -/// }, -/// )) -/// .with_signer(signer) -/// .send_to_mainnet() -/// .await?; -/// # Ok(()) -/// # } -/// ``` +/// This struct provides ability to interact with transactions. #[derive(Clone, Debug)] pub struct Transaction; impl Transaction { + /// Constructs a new transaction builder with the given signer and receiver IDs. + /// This pattern is useful for batching actions into a single transaction. + /// + /// This is the low level interface for constructing transactions. + /// It is designed to be used in scenarios where more control over the transaction process is required. + /// + /// # Example + /// + /// This example constructs a transaction with a two transfer actions. + /// + /// ```rust,no_run + /// use near_api::*; + /// use near_primitives::transaction::{Action, TransferAction}; + /// + /// # async fn example() -> Result<(), Box> { + /// let signer = Signer::new(Signer::from_ledger())?; + /// + /// let transaction_result: near_primitives::views::FinalExecutionOutcomeView = Transaction::construct( + /// "sender.near".parse()?, + /// "receiver.near".parse()? + /// ) + /// .add_action(Action::Transfer( + /// TransferAction { + /// deposit: NearToken::from_near(1).as_yoctonear(), + /// }, + /// )) + /// .add_action(Action::Transfer( + /// TransferAction { + /// deposit: NearToken::from_near(1).as_yoctonear(), + /// }, + /// )) + /// .with_signer(signer) + /// .send_to_mainnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub const fn construct(signer_id: AccountId, receiver_id: AccountId) -> ConstructTransaction { ConstructTransaction::new(signer_id, receiver_id) } + /// Signs a transaction with the given signer. + /// + /// This provides ability to sign customly constructed pre-populated transactions. + /// + /// # Examples + /// + /// ```rust,no_run + /// use near_api::*; + /// use near_primitives::transaction::{Action, TransferAction}; + /// + /// # async fn example() -> Result<(), Box> { + /// let signer = Signer::new(Signer::from_ledger())?; + /// # let unsigned_tx = todo!(); + /// + /// let transaction_result: near_primitives::views::FinalExecutionOutcomeView = Transaction::sign_transaction( + /// unsigned_tx, + /// signer + /// ) + /// .await? + /// .send_to_mainnet() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub async fn sign_transaction( unsigned_tx: near_primitives::transaction::Transaction, signer: Arc, @@ -122,4 +161,8 @@ impl Transaction { .presign_offline(public_key, block_hash.into(), nonce) .await } + + // TODO: fetch transaction status + // TODO: fetch transaction receipt + // TODO: fetch transaction proof } diff --git a/src/types/storage.rs b/src/types/storage.rs index 100c37a..09e9f3a 100644 --- a/src/types/storage.rs +++ b/src/types/storage.rs @@ -1,19 +1,21 @@ +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 #[derive(Debug, Clone, serde::Deserialize)] pub struct StorageBalance { #[serde(deserialize_with = "parse_u128_string")] - pub available: u128, + pub available: NearToken, #[serde(deserialize_with = "parse_u128_string")] - pub total: u128, + pub total: NearToken, } -fn parse_u128_string<'de, D>(deserializer: D) -> Result +fn parse_u128_string<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { ::deserialize(deserializer)? .parse::() + .map(NearToken::from_yoctonear) .map_err(serde::de::Error::custom) } diff --git a/src/types/tokens.rs b/src/types/tokens.rs index 8e486b2..483a0c7 100644 --- a/src/types/tokens.rs +++ b/src/types/tokens.rs @@ -3,8 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::errors::DecimalNumberParsingError; +/// Static instance of [FTBalance] for USDT token with correct decimals and symbol. pub const USDT_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(6, "USDT"); +/// Static instance of [FTBalance] for USDC token with correct decimals and symbol. 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"); #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)]