From 243e5d6b645db27a7cf1ceb99f70ca68377d333e Mon Sep 17 00:00:00 2001 From: akorchyn Date: Wed, 18 Dec 2024 16:25:29 +0200 Subject: [PATCH 01/10] chore: added documentation for root level and signer module --- examples/sign_options.rs | 5 +- src/lib.rs | 56 ++++++++++++++++ src/signer/mod.rs | 136 +++++++++++++++++++++++++++++++++++---- 3 files changed, 184 insertions(+), 13 deletions(-) diff --git a/examples/sign_options.rs b/examples/sign_options.rs index c4af55c..bd013cf 100644 --- a/examples/sign_options.rs +++ b/examples/sign_options.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use near_api::*; use near_crypto::SecretKey; use near_primitives::account::AccessKeyPermission; @@ -26,8 +28,7 @@ async fn main() { Account(account.id().clone()) .add_key(AccessKeyPermission::FullAccess, ledger_pubkey) .with_signer( - Signer::new(Signer::seed_phrase(new_seed_phrase, Some("smile".to_string())).unwrap()) - .unwrap(), + Signer::new(Signer::seed_phrase(&new_seed_phrase, Some("smile")).unwrap()).unwrap(), ) .send_to(&network) .await diff --git a/src/lib.rs b/src/lib.rs index eaeecb1..ba9ee4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,59 @@ +//! A Rust library for interacting with the NEAR Protocol blockchain +//! +//! This crate provides a high-level API for interacting with NEAR Protocol, including: +//! - Account management and creation +//! - Contract deployment and interaction +//! - Token operations (NEAR, FT, NFT) +//! - Storage management and staking operations +//! - Custom transaction building and signing +//! - Several ways to sign the transaction (SecretKey, Seedphrase, File, Ledger, Secure keychain). +//! - Account nonce caching and access-key pooling mechanisms to speed up the transaction processing. +//! - Support for backup RPC endpoints +//! +//! # Example +//! ```rust,no_run +//! use near_api::{*, signer::generate_secret_key}; +//! use std::str::FromStr; +//! +//! # async fn example() -> Result<(), Box> { +//! // Initialize network configuration +//! let bob = AccountId::from_str("bob.testnet")?; +//! let bob_seed_phrase = "lucky barrel fall come bottom can rib join rough around subway cloth "; +//! +//! // Fetch NEAR balance +//! let _bob_balance = Tokens::account(bob.clone()) +//! .near_balance() +//! .fetch_from_testnet() +//! .await?; +//! +//! // Create an account instance +//! let signer = Signer::new(Signer::seed_phrase(bob_seed_phrase, None)?)?; +//! let alice_secret_key = generate_secret_key()?; +//! Account::create_account(AccountId::from_str("alice.testnet")?) +//! .fund_myself(bob.clone(), NearToken::from_near(1)) +//! .public_key(alice_secret_key.public_key())? +//! .with_signer(signer) +//! .send_to_testnet() +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Features +//! - `ledger`: Enables hardware wallet support +//! - `keystore`: Enables system keychain integration +//! - `workspaces`: Enables integration with near-workspaces for testing +//! +//! # Modules +//! - [`Account`]: Account management and creation +//! - [`Chain`]: Chain data queries (block, block number) +//! - [`Contract`]: Contract deployment and interaction +//! - [`Staking`]: Staking operations +//! - [`StorageDeposit`]: Storage management and staking operations +//! - [`Tokens`]: Token operations (NEAR, FT, NFT) +//! - [`Transaction`]: Custom transaction building and signing +//! - [`Signer`]: Signer management and signing + mod account; mod chain; mod config; diff --git a/src/signer/mod.rs b/src/signer/mod.rs index 0154cd9..9bef88f 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -1,3 +1,102 @@ +//! Transaction signing functionality for NEAR Protocol +//! +//! The [`Signer`] provides various ways to sign transactions on NEAR, including: +//! - Secret key signing +//! - Seed phrase (mnemonic) signing +//! - Access key file signing +//! - Hardware wallet (Ledger) signing +//! - System keychain signing +//! +//! # Examples +//! +//! ## Sign with Secret Key +//! ```rust,no_run +//! use near_api::*; +//! use near_crypto::SecretKey; +//! +//! # async fn example() -> Result<(), Box> { +//! let secret_key: SecretKey = "ed25519:...".parse()?; +//! let signer = Signer::new(Signer::secret_key(secret_key))?; +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Sign with Seed Phrase +//! ```rust,no_run +//! use near_api::*; +//! +//! # async fn example() -> Result<(), Box> { +//! let seed_phrase = "witch collapse practice feed shame open despair creek road again ice least"; +//! let signer = Signer::new(Signer::seed_phrase(seed_phrase, None)?)?; +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Sign with Ledger +//! ```rust,no_run +//! # #[cfg(feature = "ledger")] +//! # async fn example() -> Result<(), Box> { +//! use near_api::*; +//! +//! let signer = Signer::new(Signer::ledger())?; +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Sign with a key from the system keychain +//! ```rust,no_run +//! # #[cfg(feature = "keystore")] +//! # async fn example() -> Result<(), Box> { +//! use near_api::*; +//! +//! let preloaded_keychain = Signer::keystore_search_for_keys("account_id.testnet".parse()?, &NetworkConfig::testnet()).await?; +//! let signer = Signer::new(preloaded_keychain)?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Access Key Pooling +//! +//! The signer supports pooling multiple access keys for improved transaction throughput. +//! It helps to mitigate concurrency issues that arise when multiple transactions are signed but the +//! transaction with the highest nonce arrives first which would fail transaction with a lower nonce. +//! +//! By using, account key pooling, each transaction is signed with a different key, so that the nonce issue +//! is mitigated as long as the keys are more or equal to the number of signed transactions. +//! ```rust,no_run +//! use near_api::*; +//! use near_crypto::SecretKey; +//! +//! # async fn example() -> Result<(), Box> { +//! let signer = Signer::new(Signer::secret_key("ed25519:...".parse()?))?; +//! +//! // Add additional keys to the pool +//! signer.add_signer_to_pool(Signer::secret_key("ed25519:...".parse()?)).await?; +//! signer.add_signer_to_pool(Signer::secret_key("ed25519:...".parse()?)).await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Nonce Management +//! +//! The signer automatically manages nonces for transactions: +//! - Caches nonces per (account_id, public_key) pair +//! - Handles concurrent transactions safely +//! - Automatically increments nonces for sequential transactions +//! +//! # Secret generation +//! +//! The crate provides utility functions to generate new secret keys and seed phrases: +//! - [`generate_seed_phrase`]: Generates a new seed phrase with default settings +//! - [`generate_seed_phrase_with_hd_path`]: Generates a new seed phrase with a custom HD path +//! - [`generate_seed_phrase_with_passphrase`]: Generates a new seed phrase with a custom passphrase +//! - [`generate_seed_phrase_with_word_count`]: Generates a new seed phrase with a custom word count +//! - [`generate_secret_key`]: Generates a new secret key from a new seed phrase +//! - [`generate_secret_key_from_seed_phrase`]: Generates a secret key from a seed phrase +//! +//! # Custom signer +//! The user can instantiate [`Signer`] with a custom signing logic by utilising the [`SignerTrait`] trait. + use std::{ collections::HashMap, path::{Path, PathBuf}, @@ -97,6 +196,7 @@ pub struct Signer { } impl Signer { + /// Creates a new signer and instantiates nonce cache. #[instrument(skip(signer))] pub fn new( signer: T, @@ -112,6 +212,8 @@ impl Signer { })) } + /// Adds a signer to the pool of signers. + /// The [Signer](`Signer`) will rotate the provided implementation of [SignerTrait](`SignerTrait`) on each call to [get_public_key](`Signer::get_public_key`). #[instrument(skip(self, signer))] pub async fn add_signer_to_pool( &self, @@ -166,9 +268,10 @@ impl Signer { Ok((nonce, nonce_data.block_hash, nonce_data.block_height)) } + /// Helper function to create a [SecretKeySigner](`SecretKeySigner`) using seed phrase with default HD path. pub fn seed_phrase( - seed_phrase: String, - password: Option, + seed_phrase: &str, + password: Option<&str>, ) -> Result { Self::seed_phrase_with_hd_path( seed_phrase, @@ -177,38 +280,46 @@ impl Signer { ) } + /// Helper function to create a [SecretKeySigner](`SecretKeySigner`) using a secret key. pub fn secret_key(secret_key: SecretKey) -> SecretKeySigner { SecretKeySigner::new(secret_key) } + /// Helper function to create a [SecretKeySigner](`SecretKeySigner`) using seed phrase with a custom HD path. pub fn seed_phrase_with_hd_path( - seed_phrase: String, + seed_phrase: &str, hd_path: BIP32Path, - password: Option, + password: Option<&str>, ) -> Result { let secret_key = get_secret_key_from_seed(hd_path, seed_phrase, password)?; Ok(SecretKeySigner::new(secret_key)) } + /// Helper function to create a [AccessKeyFileSigner](`AccessKeyFileSigner`) using a path to the access key file. pub fn access_keyfile(path: PathBuf) -> Result { AccessKeyFileSigner::new(path) } + /// Helper function to create a [LedgerSigner](`ledger::LedgerSigner`) using default HD path. #[cfg(feature = "ledger")] pub fn ledger() -> ledger::LedgerSigner { ledger::LedgerSigner::new(BIP32Path::from_str("44'/397'/0'/0'/1'").expect("Valid HD path")) } + /// Helper function to create a [LedgerSigner](`ledger::LedgerSigner`) using a custom HD path. #[cfg(feature = "ledger")] pub const fn ledger_with_hd_path(hd_path: BIP32Path) -> ledger::LedgerSigner { ledger::LedgerSigner::new(hd_path) } + /// Helper function to create a [KeystoreSigner](`keystore::KeystoreSigner`) with predefined public key. #[cfg(feature = "keystore")] pub fn keystore(pub_key: PublicKey) -> keystore::KeystoreSigner { keystore::KeystoreSigner::new_with_pubkey(pub_key) } + /// Helper function to create a [KeystoreSigner](`keystore::KeystoreSigner`). The provided function will query provided account for public keys and search + /// in the system keychain for the corresponding secret keys. #[cfg(feature = "keystore")] pub async fn keystore_search_for_keys( account_id: AccountId, @@ -217,11 +328,14 @@ impl Signer { keystore::KeystoreSigner::search_for_keys(account_id, network).await } + /// Helper function to create a [SecretKeySigner](`secret_key::SecretKeySigner`) from a [near_workspaces::Account](`near_workspaces::Account`) for testing purposes. #[cfg(feature = "workspaces")] pub fn from_workspace(account: &near_workspaces::Account) -> SecretKeySigner { SecretKeySigner::new(account.secret_key().to_string().parse().unwrap()) } + /// Retrieves the public key from the pool of signers. + /// The public key is rotated on each call. #[instrument(skip(self))] pub async fn get_public_key(&self) -> Result { let index = self.current_public_key.fetch_add(1, Ordering::SeqCst); @@ -314,8 +428,8 @@ pub fn get_signed_delegate_action( #[instrument(skip(seed_phrase_hd_path, master_seed_phrase, password))] pub fn get_secret_key_from_seed( seed_phrase_hd_path: BIP32Path, - master_seed_phrase: String, - password: Option, + master_seed_phrase: &str, + password: Option<&str>, ) -> Result { let master_seed = bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(password.unwrap_or_default()); @@ -336,14 +450,14 @@ pub fn get_secret_key_from_seed( pub fn generate_seed_phrase_custom( word_count: Option, hd_path: Option, - passphrase: Option, + passphrase: Option<&str>, ) -> Result<(String, PublicKey), SecretError> { let mnemonic = bip39::Mnemonic::generate(word_count.unwrap_or(DEFAULT_WORD_COUNT))?; let seed_phrase = mnemonic.words().collect::>().join(" "); let secret_key = get_secret_key_from_seed( hd_path.unwrap_or_else(|| DEFAULT_HD_PATH.parse().expect("Valid HD path")), - seed_phrase.clone(), + &seed_phrase, passphrase, )?; @@ -364,7 +478,7 @@ pub fn generate_seed_phrase_with_hd_path( /// Generates a new seed phrase with a custom passphrase pub fn generate_seed_phrase_with_passphrase( - passphrase: String, + passphrase: &str, ) -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(None, None, Some(passphrase)) } @@ -381,7 +495,7 @@ pub fn generate_secret_key() -> Result { let (seed_phrase, _) = generate_seed_phrase()?; let secret_key = get_secret_key_from_seed( DEFAULT_HD_PATH.parse().expect("Valid HD path"), - seed_phrase, + &seed_phrase, None, )?; Ok(secret_key) @@ -390,7 +504,7 @@ pub fn generate_secret_key() -> Result { pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result { get_secret_key_from_seed( DEFAULT_HD_PATH.parse().expect("Valid HD path"), - seed_phrase, + &seed_phrase, None, ) } From 41a424407a330a91ca1a907fbb28a2a4d4973e83 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Wed, 18 Dec 2024 16:26:16 +0200 Subject: [PATCH 02/10] clippy --- examples/sign_options.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/sign_options.rs b/examples/sign_options.rs index bd013cf..bd88164 100644 --- a/examples/sign_options.rs +++ b/examples/sign_options.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use near_api::*; use near_crypto::SecretKey; use near_primitives::account::AccessKeyPermission; From 70402bc519ce23239603ce97a6093cd5cea2c274 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Wed, 18 Dec 2024 17:01:57 +0200 Subject: [PATCH 03/10] improved signer doc --- src/lib.rs | 2 +- src/signer/mod.rs | 123 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 108 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ba9ee4c..73a4918 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,7 @@ //! - [`StorageDeposit`]: Storage management and staking operations //! - [`Tokens`]: Token operations (NEAR, FT, NFT) //! - [`Transaction`]: Custom transaction building and signing -//! - [`Signer`]: Signer management and signing +//! - [`Signer`](`signer`): Signer management and signing mod account; mod chain; diff --git a/src/signer/mod.rs b/src/signer/mod.rs index 9bef88f..f04e9dd 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -85,14 +85,9 @@ //! - Automatically increments nonces for sequential transactions //! //! # Secret generation +//! The crate provides utility functions to generate new secret keys and seed phrases //! -//! The crate provides utility functions to generate new secret keys and seed phrases: -//! - [`generate_seed_phrase`]: Generates a new seed phrase with default settings -//! - [`generate_seed_phrase_with_hd_path`]: Generates a new seed phrase with a custom HD path -//! - [`generate_seed_phrase_with_passphrase`]: Generates a new seed phrase with a custom passphrase -//! - [`generate_seed_phrase_with_word_count`]: Generates a new seed phrase with a custom word count -//! - [`generate_secret_key`]: Generates a new secret key from a new seed phrase -//! - [`generate_secret_key_from_seed_phrase`]: Generates a secret key from a seed phrase +//! See [functions](#functions) section for details //! //! # Custom signer //! The user can instantiate [`Signer`] with a custom signing logic by utilising the [`SignerTrait`] trait. @@ -133,9 +128,13 @@ pub mod ledger; pub mod secret_key; const SIGNER_TARGET: &str = "near_api::signer"; -const DEFAULT_HD_PATH: &str = "m/44'/397'/0'"; -const DEFAULT_WORD_COUNT: usize = 12; +/// Default HD path for seed phrases and secret keys generation +pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'"; +/// Default word count for seed phrases generation +pub const DEFAULT_WORD_COUNT: usize = 12; +/// A struct representing a pair of public and private keys for an account. +/// This might be useful for getting keys from a file. E.g. ~/.near-credentials. #[derive(Debug, Deserialize, Clone)] pub struct AccountKeyPair { pub public_key: near_crypto::PublicKey, @@ -149,8 +148,78 @@ impl AccountKeyPair { } } +/// A trait for implementing custom signing logic. +/// +/// This trait provides the core functionality needed to sign transactions and delegate actions. +/// It is used by the [`Signer`] to abstract over different signing methods (secret key, ledger, keystore, etc.). +/// +/// # Examples +/// +/// ## Implementing a custom signer +/// ```rust,no_run +/// use near_api::{signer::*, types::{transactions::PrepopulateTransaction, CryptoHash}, errors::SignerError}; +/// use near_crypto::{PublicKey, SecretKey}; +/// use near_primitives::transaction::Transaction; +/// +/// struct CustomSigner { +/// secret_key: SecretKey, +/// } +/// +/// #[async_trait::async_trait] +/// impl SignerTrait for CustomSigner { +/// fn tx_and_secret( +/// &self, +/// tr: PrepopulateTransaction, +/// public_key: PublicKey, +/// nonce: u64, +/// block_hash: CryptoHash, +/// ) -> Result<(Transaction, SecretKey), SignerError> { +/// let mut transaction = Transaction::new_v0( +/// tr.signer_id.clone(), +/// public_key, +/// tr.receiver_id, +/// nonce, +/// block_hash.into(), +/// ); +/// *transaction.actions_mut() = tr.actions; +/// Ok((transaction, self.secret_key.clone())) +/// } +/// +/// fn get_public_key(&self) -> Result { +/// Ok(self.secret_key.public_key()) +/// } +/// } +/// ``` +/// +/// ## Using a custom signer +/// ```rust,no_run +/// # use near_api::{signer::*, types::{transactions::PrepopulateTransaction, CryptoHash}, errors::SignerError}; +/// # use near_crypto::{PublicKey, SecretKey}; +/// # struct CustomSigner; +/// # impl CustomSigner { +/// # fn new(_: SecretKey) -> Self { Self } +/// # } +/// # #[async_trait::async_trait] +/// # impl SignerTrait for CustomSigner { +/// # fn tx_and_secret(&self, _: PrepopulateTransaction, _: PublicKey, _: u64, _: CryptoHash, +/// # ) -> Result<(near_primitives::transaction::Transaction, SecretKey), SignerError> { unimplemented!() } +/// # fn get_public_key(&self) -> Result { unimplemented!() } +/// # } +/// # async fn example() -> Result<(), Box> { +/// let secret_key = "ed25519:...".parse()?; +/// let custom_signer = CustomSigner::new(secret_key); +/// let signer = Signer::new(custom_signer)?; +/// # Ok(()) +/// # } +/// ``` #[async_trait::async_trait] pub trait SignerTrait { + /// Signs a delegate action for meta transactions. + /// + /// This method is used for meta-transactions where one account can delegate transaction delivery and gas payment to another account. + /// The delegate action is signed with a maximum block height to ensure the delegation expiration after some point in time. + /// + /// The default implementation should work for most cases. async fn sign_meta( &self, tr: PrepopulateTransaction, @@ -165,6 +234,12 @@ pub trait SignerTrait { get_signed_delegate_action(unsigned_transaction, signer_secret_key, max_block_height) } + /// Signs a regular transaction. + /// + /// This method is used for standard transactions. It creates a signed transaction + /// that can be sent to the NEAR network. + /// + /// The default implementation should work for most cases. async fn sign( &self, tr: PrepopulateTransaction, @@ -179,6 +254,12 @@ pub trait SignerTrait { Ok(SignedTransaction::new(signature, unsigned_transaction)) } + /// Creates an unsigned transaction and returns it along with the secret key. + /// This is a `helper` method that should be implemented by the signer or fail with SignerError. + /// As long as this method works, the default implementation of the [sign_meta](`SignerTrait::sign_meta`) and [sign](`SignerTrait::sign`) methods should work. + /// + /// If you can't provide a SecretKey for some reason (E.g. Ledger), + /// you can fail with SignerError and override `sign_meta` and `sign` methods. fn tx_and_secret( &self, tr: PrepopulateTransaction, @@ -186,9 +267,15 @@ pub trait SignerTrait { nonce: Nonce, block_hash: CryptoHash, ) -> Result<(Transaction, SecretKey), SignerError>; + + /// Returns the public key associated with this signer. + /// + /// This method is used by the [`Signer`] to manage the pool of signing keys. fn get_public_key(&self) -> Result; } +/// A [Signer](`Signer`) is a wrapper around a single or multiple signer implementations of [SignerTrait](`SignerTrait`). +/// It provides an access key pooling and a nonce caching mechanism to improve transaction throughput. pub struct Signer { pool: tokio::sync::RwLock>>, nonce_cache: tokio::sync::RwLock>, @@ -386,7 +473,7 @@ impl Signer { } #[instrument(skip(unsigned_transaction, private_key))] -pub fn get_signed_delegate_action( +fn get_signed_delegate_action( unsigned_transaction: Transaction, private_key: SecretKey, max_block_height: u64, @@ -425,6 +512,8 @@ pub fn get_signed_delegate_action( }) } +/// Helper utility function to generate a secret key from a seed phrase. +/// Prefer using [generate_secret_key_from_seed_phrase](`generate_secret_key_from_seed_phrase`) if you don't need to customize the HD path and passphrase. #[instrument(skip(seed_phrase_hd_path, master_seed_phrase, password))] pub fn get_secret_key_from_seed( seed_phrase_hd_path: BIP32Path, @@ -446,7 +535,8 @@ pub fn get_secret_key_from_seed( Ok(SecretKey::ED25519(secret_key)) } -/// Generates a new seed phrase with optional customization +/// Helper utility function to generate a new seed phrase with optional customization of word count, HD path and passphrase. +/// Prefer using [generate_seed_phrase](`generate_seed_phrase`) or [generate_secret_key](`generate_secret_key`) if you don't need to customize the seed phrase. pub fn generate_seed_phrase_custom( word_count: Option, hd_path: Option, @@ -464,33 +554,33 @@ pub fn generate_seed_phrase_custom( Ok((seed_phrase, secret_key.public_key())) } -/// Generates a new seed phrase with default settings (12 words, default HD path) +/// Generates a new seed phrase with default settings (12 words, [default HD path](`DEFAULT_HD_PATH`)) pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(None, None, None) } -/// Generates a new seed phrase with a custom HD path +/// Helper utility function to generate a new 12-words seed phrase with a custom HD path pub fn generate_seed_phrase_with_hd_path( hd_path: BIP32Path, ) -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(None, Some(hd_path), None) } -/// Generates a new seed phrase with a custom passphrase +/// Helper utility function to generate a new 12-words seed phrase with a custom passphrase and [default HD path](`DEFAULT_HD_PATH`) pub fn generate_seed_phrase_with_passphrase( passphrase: &str, ) -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(None, None, Some(passphrase)) } -/// Generates a new seed phrase with a custom word count +/// Helper utility function to generate a new seed phrase with a custom word count and [default HD path](`DEFAULT_HD_PATH`) pub fn generate_seed_phrase_with_word_count( word_count: usize, ) -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(Some(word_count), None, None) } -/// Generates a secret key from a new seed phrase using default settings +/// Helper utility function to generate a secret key from a new seed phrase using default settings pub fn generate_secret_key() -> Result { let (seed_phrase, _) = generate_seed_phrase()?; let secret_key = get_secret_key_from_seed( @@ -501,6 +591,7 @@ pub fn generate_secret_key() -> Result { Ok(secret_key) } +/// Helper utility function to generate a secret key from a seed phrase using [default HD path](`DEFAULT_HD_PATH`) pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result { get_secret_key_from_seed( DEFAULT_HD_PATH.parse().expect("Valid HD path"), From b5c800dcf057ed4d6438390d9a72689db6ab0e6f Mon Sep 17 00:00:00 2001 From: akorchyn Date: Wed, 18 Dec 2024 17:53:50 +0200 Subject: [PATCH 04/10] clippy --- src/signer/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/signer/mod.rs b/src/signer/mod.rs index f04e9dd..d51c312 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -274,8 +274,9 @@ pub trait SignerTrait { fn get_public_key(&self) -> Result; } -/// A [Signer](`Signer`) is a wrapper around a single or multiple signer implementations of [SignerTrait](`SignerTrait`). -/// It provides an access key pooling and a nonce caching mechanism to improve transaction throughput. +/// A [Signer](`Signer`) is a wrapper around a single or multiple signer implementations +/// of [SignerTrait](`SignerTrait`). It provides an access key pooling and +/// a nonce caching mechanism to improve transaction throughput. pub struct Signer { pool: tokio::sync::RwLock>>, nonce_cache: tokio::sync::RwLock>, @@ -535,8 +536,9 @@ pub fn get_secret_key_from_seed( Ok(SecretKey::ED25519(secret_key)) } -/// Helper utility function to generate a new seed phrase with optional customization of word count, HD path and passphrase. -/// Prefer using [generate_seed_phrase](`generate_seed_phrase`) or [generate_secret_key](`generate_secret_key`) if you don't need to customize the seed phrase. +/// Helper utility function to generate a new seed phrase with optional customization +/// of word count, HD path and passphrase. Prefer using [generate_seed_phrase](`generate_seed_phrase`) +/// or [generate_secret_key](`generate_secret_key`) if you don't need to customize the seed phrase. pub fn generate_seed_phrase_custom( word_count: Option, hd_path: Option, From 8ce0b1c8d4c91377404bcdde7356ac74445ff982 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Wed, 18 Dec 2024 18:01:22 +0200 Subject: [PATCH 05/10] clippy --- src/signer/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/signer/mod.rs b/src/signer/mod.rs index d51c312..0f375d0 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -275,8 +275,9 @@ pub trait SignerTrait { } /// A [Signer](`Signer`) is a wrapper around a single or multiple signer implementations -/// of [SignerTrait](`SignerTrait`). It provides an access key pooling and -/// a nonce caching mechanism to improve transaction throughput. +/// of [SignerTrait](`SignerTrait`). +/// +/// It provides an access key pooling and a nonce caching mechanism to improve transaction throughput. pub struct Signer { pool: tokio::sync::RwLock>>, nonce_cache: tokio::sync::RwLock>, @@ -536,9 +537,9 @@ pub fn get_secret_key_from_seed( Ok(SecretKey::ED25519(secret_key)) } -/// Helper utility function to generate a new seed phrase with optional customization -/// of word count, HD path and passphrase. Prefer using [generate_seed_phrase](`generate_seed_phrase`) -/// or [generate_secret_key](`generate_secret_key`) if you don't need to customize the seed phrase. +/// Helper utility function to generate a new seed phrase with optional customization. +/// +/// Prefer using [generate_seed_phrase](`generate_seed_phrase`) or [generate_secret_key](`generate_secret_key`) if you don't need to customize the seed phrase. pub fn generate_seed_phrase_custom( word_count: Option, hd_path: Option, From a43a8526982a5c493f861c5f85fa7488b80bdbdc Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 19 Dec 2024 14:03:38 +0200 Subject: [PATCH 06/10] code review --- examples/account_key_pooling.rs | 2 +- examples/sign_options.rs | 9 +- src/account/mod.rs | 15 +++ src/chain.rs | 21 ++++ src/contract.rs | 15 +++ src/lib.rs | 26 ++--- src/signer/mod.rs | 98 +++++++++++-------- src/stake.rs | 16 +++ src/storage.rs | 26 +++++ src/tokens.rs | 74 ++++++++++++++ src/transactions.rs | 30 ++++++ src/types/tokens.rs | 4 +- ...ultiple_tx_at_same_time_from_same-_user.rs | 4 +- 13 files changed, 274 insertions(+), 66 deletions(-) diff --git a/examples/account_key_pooling.rs b/examples/account_key_pooling.rs index 8a0042f..cee5332 100644 --- a/examples/account_key_pooling.rs +++ b/examples/account_key_pooling.rs @@ -38,7 +38,7 @@ async fn main() { .assert_success(); signer - .add_signer_to_pool(Signer::secret_key(secret_key)) + .add_signer_to_pool(Signer::from_secret_key(secret_key)) .await .unwrap(); diff --git a/examples/sign_options.rs b/examples/sign_options.rs index bd88164..b82c070 100644 --- a/examples/sign_options.rs +++ b/examples/sign_options.rs @@ -16,17 +16,18 @@ async fn main() { // Let's add new key and get the seed phrase Account(account.id().clone()) .add_key(AccessKeyPermission::FullAccess, public_key) - .with_signer(Signer::new(Signer::secret_key(current_secret_key.clone())).unwrap()) + .with_signer(Signer::new(Signer::from_secret_key(current_secret_key.clone())).unwrap()) .send_to(&network) .await .unwrap(); // Let's add ledger to the account with the new seed phrase - let ledger_pubkey = Signer::ledger().get_public_key().unwrap(); + let ledger_pubkey = Signer::from_ledger().get_public_key().unwrap(); Account(account.id().clone()) .add_key(AccessKeyPermission::FullAccess, ledger_pubkey) .with_signer( - Signer::new(Signer::seed_phrase(&new_seed_phrase, Some("smile")).unwrap()).unwrap(), + Signer::new(Signer::from_seed_phrase(&new_seed_phrase, Some("smile")).unwrap()) + .unwrap(), ) .send_to(&network) .await @@ -36,7 +37,7 @@ async fn main() { // Let's sign some tx with the ledger key Account(account.id().clone()) .delete_key(current_secret_key.public_key()) - .with_signer(Signer::new(Signer::ledger()).unwrap()) + .with_signer(Signer::new(Signer::from_ledger()).unwrap()) .send_to(&network) .await .unwrap(); diff --git a/src/account/mod.rs b/src/account/mod.rs index d06d406..2c413fa 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -15,6 +15,21 @@ use self::create::CreateAccountBuilder; mod create; +/// Account management related interactions with the NEAR Protocol +/// +/// The [`Account`] struct provides methods to interact with NEAR accounts, including querying account information, managing access keys, and creating new accounts. +/// +/// # Examples +/// +/// ```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(()) +/// # } +/// ``` #[derive(Clone, Debug)] pub struct Account(pub AccountId); diff --git a/src/chain.rs b/src/chain.rs index 7034b7e..95238da 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -8,6 +8,21 @@ use crate::{ types::CryptoHash, }; +/// Chain-related interactions with the NEAR Protocol +/// +/// The [`Chain`] struct provides methods to interact with the NEAR blockchain +/// +/// # Examples +/// +/// ```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(()) +/// # } +/// ``` #[derive(Debug, Clone, Copy)] pub struct Chain; @@ -37,4 +52,10 @@ impl Chain { 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 } diff --git a/src/contract.rs b/src/contract.rs index 4755aa9..a5a64a5 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -23,6 +23,21 @@ use crate::{ types::{contract::ContractSourceMetadata, Data}, }; +/// Contract-related interactions with the NEAR Protocol +/// +/// The [`Contract`] struct provides methods to interact with NEAR contracts, including calling functions, querying storage, and deploying contracts. +/// +/// # Examples +/// +/// ```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(()) +/// # } +/// ``` #[derive(Clone, Debug)] pub struct Contract(pub AccountId); diff --git a/src/lib.rs b/src/lib.rs index 73a4918..e90a556 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,18 @@ //! A Rust library for interacting with the NEAR Protocol blockchain //! //! This crate provides a high-level API for interacting with NEAR Protocol, including: -//! - Account management and creation -//! - Contract deployment and interaction -//! - Token operations (NEAR, FT, NFT) -//! - Storage management and staking operations -//! - Custom transaction building and signing -//! - Several ways to sign the transaction (SecretKey, Seedphrase, File, Ledger, Secure keychain). +//! - [Account management and creation](Account) +//! - [Contract deployment and interaction with it](Contract) +//! - [Token operations (NEAR, FT, NFT)](Tokens) +//! - [Storage management and staking operations](Staking) +//! - [Custom transaction building and signing](Transaction) +//! - [Querying the chain data](Chain) +//! - [Several ways to sign the transaction](signer) //! - Account nonce caching and access-key pooling mechanisms to speed up the transaction processing. //! - Support for backup RPC endpoints //! //! # Example +//! In this example, we use Bob account with a predefined seed phrase to create Alice account and pre-fund it with 1 NEAR. //! ```rust,no_run //! use near_api::{*, signer::generate_secret_key}; //! use std::str::FromStr; @@ -27,7 +29,7 @@ //! .await?; //! //! // Create an account instance -//! let signer = Signer::new(Signer::seed_phrase(bob_seed_phrase, None)?)?; +//! let signer = Signer::new(Signer::from_seed_phrase(bob_seed_phrase, None)?)?; //! let alice_secret_key = generate_secret_key()?; //! Account::create_account(AccountId::from_str("alice.testnet")?) //! .fund_myself(bob.clone(), NearToken::from_near(1)) @@ -43,16 +45,6 @@ //! - `ledger`: Enables hardware wallet support //! - `keystore`: Enables system keychain integration //! - `workspaces`: Enables integration with near-workspaces for testing -//! -//! # Modules -//! - [`Account`]: Account management and creation -//! - [`Chain`]: Chain data queries (block, block number) -//! - [`Contract`]: Contract deployment and interaction -//! - [`Staking`]: Staking operations -//! - [`StorageDeposit`]: Storage management and staking operations -//! - [`Tokens`]: Token operations (NEAR, FT, NFT) -//! - [`Transaction`]: Custom transaction building and signing -//! - [`Signer`](`signer`): Signer management and signing mod account; mod chain; diff --git a/src/signer/mod.rs b/src/signer/mod.rs index 0f375d0..b31a6d5 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -9,53 +9,70 @@ //! //! # Examples //! -//! ## Sign with Secret Key +//! ## Creating a signer using a secret key //! ```rust,no_run //! use near_api::*; //! use near_crypto::SecretKey; //! //! # async fn example() -> Result<(), Box> { -//! let secret_key: SecretKey = "ed25519:...".parse()?; -//! let signer = Signer::new(Signer::secret_key(secret_key))?; +//! let secret_key: SecretKey = "ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?; +//! let signer = Signer::new(Signer::from_secret_key(secret_key))?; //! # Ok(()) //! # } //! ``` //! -//! ## Sign with Seed Phrase +//! ## Creating a signer using a seed phrase //! ```rust,no_run //! use near_api::*; //! //! # async fn example() -> Result<(), Box> { //! let seed_phrase = "witch collapse practice feed shame open despair creek road again ice least"; -//! let signer = Signer::new(Signer::seed_phrase(seed_phrase, None)?)?; +//! let signer = Signer::new(Signer::from_seed_phrase(seed_phrase, None)?)?; //! # Ok(()) //! # } //! ``` //! -//! ## Sign with Ledger +//! ## Creating a Ledger signer //! ```rust,no_run //! # #[cfg(feature = "ledger")] //! # async fn example() -> Result<(), Box> { //! use near_api::*; //! -//! let signer = Signer::new(Signer::ledger())?; +//! let signer = Signer::new(Signer::from_ledger())?; //! # Ok(()) //! # } //! ``` //! -//! ## Sign with a key from the system keychain +//! ## Creating a keystore signer //! ```rust,no_run //! # #[cfg(feature = "keystore")] //! # async fn example() -> Result<(), Box> { //! use near_api::*; //! -//! let preloaded_keychain = Signer::keystore_search_for_keys("account_id.testnet".parse()?, &NetworkConfig::testnet()).await?; +//! let preloaded_keychain = Signer::from_keystore_with_search_for_keys("account_id.testnet".parse()?, &NetworkConfig::testnet()).await?; //! let signer = Signer::new(preloaded_keychain)?; //! # Ok(()) //! # } //! ``` //! -//! # Access Key Pooling +//! ## Example signing with [Signer](`Signer`) +//! +//! ```rust,no_run +//! # use near_api::*; +//! # async fn example() -> Result<(), Box> { +//! # let signer = Signer::new(Signer::from_secret_key("ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?))?; +//! let transaction_result = Tokens::account("alice.testnet".parse()?) +//! .send_to("bob.testnet".parse()?) +//! .near(NearToken::from_near(1)) +//! .with_signer(signer) +//! .send_to_testnet() +//! .await?; +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! # Advanced: Access Key Pooling //! //! The signer supports pooling multiple access keys for improved transaction throughput. //! It helps to mitigate concurrency issues that arise when multiple transactions are signed but the @@ -68,11 +85,11 @@ //! use near_crypto::SecretKey; //! //! # async fn example() -> Result<(), Box> { -//! let signer = Signer::new(Signer::secret_key("ed25519:...".parse()?))?; +//! let signer = Signer::new(Signer::from_secret_key("ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?))?; //! //! // Add additional keys to the pool -//! signer.add_signer_to_pool(Signer::secret_key("ed25519:...".parse()?)).await?; -//! signer.add_signer_to_pool(Signer::secret_key("ed25519:...".parse()?)).await?; +//! signer.add_signer_to_pool(Signer::from_seed_phrase("witch collapse practice feed shame open despair creek road again ice least", None)?).await?; +//! signer.add_signer_to_pool(Signer::from_seed_phrase("return cactus real attack meat pitch trash found autumn upgrade mystery pupil", None)?).await?; //! # Ok(()) //! # } //! ``` @@ -81,8 +98,8 @@ //! //! The signer automatically manages nonces for transactions: //! - Caches nonces per (account_id, public_key) pair -//! - Handles concurrent transactions safely //! - Automatically increments nonces for sequential transactions +//! - Supports concurrent transactions as long as the Arc is same //! //! # Secret generation //! The crate provides utility functions to generate new secret keys and seed phrases @@ -206,7 +223,7 @@ impl AccountKeyPair { /// # fn get_public_key(&self) -> Result { unimplemented!() } /// # } /// # async fn example() -> Result<(), Box> { -/// let secret_key = "ed25519:...".parse()?; +/// let secret_key = "ed25519:2vVTQWpoZvYZBS4HYFZtzU2rxpoQSrhyFWdaHLqSdyaEfgjefbSKiFpuVatuRqax3HFvVq2tkkqWH2h7tso2nK8q".parse()?; /// let custom_signer = CustomSigner::new(secret_key); /// let signer = Signer::new(custom_signer)?; /// # Ok(()) @@ -357,25 +374,25 @@ impl Signer { Ok((nonce, nonce_data.block_hash, nonce_data.block_height)) } - /// Helper function to create a [SecretKeySigner](`SecretKeySigner`) using seed phrase with default HD path. - pub fn seed_phrase( + /// Creates a [SecretKeySigner](`SecretKeySigner`) using seed phrase with default HD path. + pub fn from_seed_phrase( seed_phrase: &str, password: Option<&str>, ) -> Result { - Self::seed_phrase_with_hd_path( + Self::from_seed_phrase_with_hd_path( seed_phrase, BIP32Path::from_str("m/44'/397'/0'").expect("Valid HD path"), password, ) } - /// Helper function to create a [SecretKeySigner](`SecretKeySigner`) using a secret key. - pub fn secret_key(secret_key: SecretKey) -> SecretKeySigner { + /// Creates a [SecretKeySigner](`SecretKeySigner`) using a secret key. + pub fn from_secret_key(secret_key: SecretKey) -> SecretKeySigner { SecretKeySigner::new(secret_key) } - /// Helper function to create a [SecretKeySigner](`SecretKeySigner`) using seed phrase with a custom HD path. - pub fn seed_phrase_with_hd_path( + /// Creates a [SecretKeySigner](`SecretKeySigner`) using seed phrase with a custom HD path. + pub fn from_seed_phrase_with_hd_path( seed_phrase: &str, hd_path: BIP32Path, password: Option<&str>, @@ -384,40 +401,40 @@ impl Signer { Ok(SecretKeySigner::new(secret_key)) } - /// Helper function to create a [AccessKeyFileSigner](`AccessKeyFileSigner`) using a path to the access key file. - pub fn access_keyfile(path: PathBuf) -> Result { + /// Creates a [AccessKeyFileSigner](`AccessKeyFileSigner`) using a path to the access key file. + pub fn from_access_keyfile(path: PathBuf) -> Result { AccessKeyFileSigner::new(path) } - /// Helper function to create a [LedgerSigner](`ledger::LedgerSigner`) using default HD path. + /// Creates a [LedgerSigner](`ledger::LedgerSigner`) using default HD path. #[cfg(feature = "ledger")] - pub fn ledger() -> ledger::LedgerSigner { + pub fn from_ledger() -> ledger::LedgerSigner { ledger::LedgerSigner::new(BIP32Path::from_str("44'/397'/0'/0'/1'").expect("Valid HD path")) } - /// Helper function to create a [LedgerSigner](`ledger::LedgerSigner`) using a custom HD path. + /// Creates a [LedgerSigner](`ledger::LedgerSigner`) using a custom HD path. #[cfg(feature = "ledger")] - pub const fn ledger_with_hd_path(hd_path: BIP32Path) -> ledger::LedgerSigner { + pub const fn from_ledger_with_hd_path(hd_path: BIP32Path) -> ledger::LedgerSigner { ledger::LedgerSigner::new(hd_path) } - /// Helper function to create a [KeystoreSigner](`keystore::KeystoreSigner`) with predefined public key. + /// Creates a [KeystoreSigner](`keystore::KeystoreSigner`) with predefined public key. #[cfg(feature = "keystore")] - pub fn keystore(pub_key: PublicKey) -> keystore::KeystoreSigner { + pub fn from_keystore(pub_key: PublicKey) -> keystore::KeystoreSigner { keystore::KeystoreSigner::new_with_pubkey(pub_key) } - /// Helper function to create a [KeystoreSigner](`keystore::KeystoreSigner`). The provided function will query provided account for public keys and search + /// Creates a [KeystoreSigner](`keystore::KeystoreSigner`). The provided function will query provided account for public keys and search /// in the system keychain for the corresponding secret keys. #[cfg(feature = "keystore")] - pub async fn keystore_search_for_keys( + pub async fn from_keystore_with_search_for_keys( account_id: AccountId, network: &NetworkConfig, ) -> Result { keystore::KeystoreSigner::search_for_keys(account_id, network).await } - /// Helper function to create a [SecretKeySigner](`secret_key::SecretKeySigner`) from a [near_workspaces::Account](`near_workspaces::Account`) for testing purposes. + /// Creates a [SecretKeySigner](`secret_key::SecretKeySigner`) from a [near_workspaces::Account](`near_workspaces::Account`) for testing purposes. #[cfg(feature = "workspaces")] pub fn from_workspace(account: &near_workspaces::Account) -> SecretKeySigner { SecretKeySigner::new(account.secret_key().to_string().parse().unwrap()) @@ -514,7 +531,8 @@ fn get_signed_delegate_action( }) } -/// Helper utility function to generate a secret key from a seed phrase. +/// Generates a secret key from a seed phrase. +/// /// Prefer using [generate_secret_key_from_seed_phrase](`generate_secret_key_from_seed_phrase`) if you don't need to customize the HD path and passphrase. #[instrument(skip(seed_phrase_hd_path, master_seed_phrase, password))] pub fn get_secret_key_from_seed( @@ -537,7 +555,7 @@ pub fn get_secret_key_from_seed( Ok(SecretKey::ED25519(secret_key)) } -/// Helper utility function to generate a new seed phrase with optional customization. +/// Generates a new seed phrase with optional customization. /// /// Prefer using [generate_seed_phrase](`generate_seed_phrase`) or [generate_secret_key](`generate_secret_key`) if you don't need to customize the seed phrase. pub fn generate_seed_phrase_custom( @@ -562,28 +580,28 @@ pub fn generate_seed_phrase() -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(None, None, None) } -/// Helper utility function to generate a new 12-words seed phrase with a custom HD path +/// Generates a new 12-words seed phrase with a custom HD path pub fn generate_seed_phrase_with_hd_path( hd_path: BIP32Path, ) -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(None, Some(hd_path), None) } -/// Helper utility function to generate a new 12-words seed phrase with a custom passphrase and [default HD path](`DEFAULT_HD_PATH`) +/// Generates a new 12-words seed phrase with a custom passphrase and [default HD path](`DEFAULT_HD_PATH`) pub fn generate_seed_phrase_with_passphrase( passphrase: &str, ) -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(None, None, Some(passphrase)) } -/// Helper utility function to generate a new seed phrase with a custom word count and [default HD path](`DEFAULT_HD_PATH`) +/// Generates a new seed phrase with a custom word count and [default HD path](`DEFAULT_HD_PATH`) pub fn generate_seed_phrase_with_word_count( word_count: usize, ) -> Result<(String, PublicKey), SecretError> { generate_seed_phrase_custom(Some(word_count), None, None) } -/// Helper utility function to generate a secret key from a new seed phrase using default settings +/// Generates a secret key from a new seed phrase using default settings pub fn generate_secret_key() -> Result { let (seed_phrase, _) = generate_seed_phrase()?; let secret_key = get_secret_key_from_seed( @@ -594,7 +612,7 @@ pub fn generate_secret_key() -> Result { Ok(secret_key) } -/// Helper utility function to generate a secret key from a seed phrase using [default HD path](`DEFAULT_HD_PATH`) +/// Generates a secret key from a seed phrase using [default HD path](`DEFAULT_HD_PATH`) pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result { get_secret_key_from_seed( DEFAULT_HD_PATH.parse().expect("Valid HD path"), diff --git a/src/stake.rs b/src/stake.rs index 73186d4..96a755e 100644 --- a/src/stake.rs +++ b/src/stake.rs @@ -249,6 +249,22 @@ impl Delegation { } } +/// Staking-related interactions with the NEAR Protocol +/// +/// 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. +/// +/// # Examples +/// +/// ```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(()) +/// # } +/// ``` #[derive(Clone, Debug)] pub struct Staking {} diff --git a/src/storage.rs b/src/storage.rs index f6a3d88..9009b1c 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -10,6 +10,32 @@ use crate::{ types::storage::StorageBalance, }; +/// A wrapper struct that simplifies interactions with NEAR storage management standard. +/// +/// Contracts on NEAR Protocol often implement a [standard interface](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. +/// +/// # Example +/// ``` +/// use near_api::*; +/// +/// # async fn example() -> Result<(), Box> { +/// let storage = StorageDeposit::on_contract("contract.testnet".parse()?); +/// +/// // Check storage balance +/// let balance = storage.view_account_storage("alice.testnet".parse()?)?.fetch_from_testnet().await?; +/// println!("Storage balance: {:?}", balance); +/// +/// // Bob pays for Alice's storage on the contract contract.testnet +/// let deposit_tx = storage.deposit("alice.testnet".parse()?, NearToken::from_near(1))? +/// .with_signer("bob.testnet".parse()?, Signer::new(Signer::from_ledger())?) +/// .send_to_testnet() +/// .await +/// .unwrap(); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone, Debug)] pub struct StorageDeposit(AccountId); diff --git a/src/tokens.rs b/src/tokens.rs index 41743b4..1d89f3d 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -34,6 +34,80 @@ use crate::{ type Result = core::result::Result; +/// A wrapper struct that simplifies interactions with NEAR tokens (NEAR, FT, NFT). +/// +/// This struct provides convenient methods to interact with different types of tokens on NEAR Protocol: +/// - Native NEAR 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 +/// +/// # Examples +/// +/// ## Fungible Token Operations +/// ``` +/// use near_api::*; +/// +/// # async fn example() -> Result<(), Box> { +/// let bob_tokens = Tokens::account("bob.testnet".parse()?); +/// +/// // Check FT balance +/// let balance = bob_tokens.ft_balance("usdt.tether-token.near".parse()?)?.fetch_from_mainnet().await?; +/// println!("Bob balance: {}", balance); +/// +/// // Transfer FT tokens +/// bob_tokens.send_to("alice.testnet".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(()) +/// # } +/// ``` +/// +/// ## NFT Operations +/// ``` +/// use near_api::*; +/// +/// # async fn example() -> Result<(), Box> { +/// let alice_tokens = Tokens::account("alice.testnet".parse()?); +/// +/// // Check NFT assets +/// let tokens = alice_tokens.nft_assets("nft-contract.testnet".parse()?)?.fetch_from_testnet().await?; +/// println!("NFT count: {}", tokens.data.len()); +/// +/// // Transfer NFT +/// alice_tokens.send_to("bob.testnet".parse()?) +/// .nft("nft-contract.testnet".parse()?, "token-id".to_string())? +/// .with_signer(Signer::new(Signer::from_ledger())?) +/// .send_to_testnet() +/// .await?; +/// # Ok(()) +/// # } +/// ``` +/// +/// ## NEAR Token Operations +/// ``` +/// use near_api::*; +/// +/// # async fn example() -> Result<(), Box> { +/// let alice_account = Tokens::account("alice.testnet".parse()?); +/// +/// // Check NEAR balance +/// let balance = alice_account.near_balance().fetch_from_testnet().await?; +/// println!("NEAR balance: {}", balance.liquid); +/// +/// // Send NEAR +/// alice_account.send_to("bob.testnet".parse()?) +/// .near(NearToken::from_near(1)) +/// .with_signer(Signer::new(Signer::from_ledger())?) +/// .send_to_testnet() +/// .await?; +/// # Ok(()) +/// # } +/// ``` #[derive(Debug, Clone)] pub struct Tokens { account_id: AccountId, diff --git a/src/transactions.rs b/src/transactions.rs index d0c2f1d..f22cb96 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -67,6 +67,36 @@ impl Transactionable for ConstructTransaction { } } +/// Low-level transaction builder. +/// +/// 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(()) +/// # } +/// ``` #[derive(Clone, Debug)] pub struct Transaction; diff --git a/src/types/tokens.rs b/src/types/tokens.rs index 4e2a756..8e486b2 100644 --- a/src/types/tokens.rs +++ b/src/types/tokens.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; use crate::errors::DecimalNumberParsingError; -pub const USDT_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(6, "USDC"); -pub const USDC_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(6, "USDT"); +pub const USDT_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(6, "USDT"); +pub const USDC_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(6, "USDC"); pub const W_NEAR_BALANCE: FTBalance = FTBalance::with_decimals_and_symbol(24, "wNEAR"); #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] diff --git a/tests/multiple_tx_at_same_time_from_same-_user.rs b/tests/multiple_tx_at_same_time_from_same-_user.rs index 4b8eef4..57206a7 100644 --- a/tests/multiple_tx_at_same_time_from_same-_user.rs +++ b/tests/multiple_tx_at_same_time_from_same-_user.rs @@ -68,7 +68,7 @@ async fn multiple_tx_at_same_time_from_different_keys() { .assert_success(); signer - .add_signer_to_pool(Signer::secret_key(secret.clone())) + .add_signer_to_pool(Signer::from_secret_key(secret.clone())) .await .unwrap(); @@ -80,7 +80,7 @@ async fn multiple_tx_at_same_time_from_different_keys() { .await .unwrap(); signer - .add_signer_to_pool(Signer::secret_key(secret2.clone())) + .add_signer_to_pool(Signer::from_secret_key(secret2.clone())) .await .unwrap(); From 45ae955a1b81d7326e3ad3969d8c5305d7928a5f Mon Sep 17 00:00:00 2001 From: akorchyn Date: Thu, 19 Dec 2024 14:09:23 +0200 Subject: [PATCH 07/10] fixed cargo doc --- src/signer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/signer/mod.rs b/src/signer/mod.rs index b31a6d5..34251fc 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -99,7 +99,7 @@ //! The signer automatically manages nonces for transactions: //! - Caches nonces per (account_id, public_key) pair //! - Automatically increments nonces for sequential transactions -//! - Supports concurrent transactions as long as the Arc is same +//! - Supports concurrent transactions as long as the `Arc` is same //! //! # Secret generation //! The crate provides utility functions to generate new secret keys and seed phrases From 8fa29077cc53d24fd96c2303c8930b23607555ea Mon Sep 17 00:00:00 2001 From: akorchyn Date: Fri, 20 Dec 2024 00:31:59 +0200 Subject: [PATCH 08/10] review --- src/signer/mod.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/signer/mod.rs b/src/signer/mod.rs index 34251fc..021c91b 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -4,7 +4,7 @@ //! - Secret key signing //! - Seed phrase (mnemonic) signing //! - Access key file signing -//! - Hardware wallet (Ledger) signing +//! - Hardware wallet (`Ledger`) signing //! - System keychain signing //! //! # Examples @@ -32,7 +32,7 @@ //! # } //! ``` //! -//! ## Creating a Ledger signer +//! ## Creating a `Ledger` signer //! ```rust,no_run //! # #[cfg(feature = "ledger")] //! # async fn example() -> Result<(), Box> { @@ -43,7 +43,7 @@ //! # } //! ``` //! -//! ## Creating a keystore signer +//! ## Creating a `keystore` signer //! ```rust,no_run //! # #[cfg(feature = "keystore")] //! # async fn example() -> Result<(), Box> { @@ -151,7 +151,7 @@ pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'"; pub const DEFAULT_WORD_COUNT: usize = 12; /// A struct representing a pair of public and private keys for an account. -/// This might be useful for getting keys from a file. E.g. ~/.near-credentials. +/// This might be useful for getting keys from a file. E.g. `~/.near-credentials`. #[derive(Debug, Deserialize, Clone)] pub struct AccountKeyPair { pub public_key: near_crypto::PublicKey, @@ -168,7 +168,7 @@ impl AccountKeyPair { /// A trait for implementing custom signing logic. /// /// This trait provides the core functionality needed to sign transactions and delegate actions. -/// It is used by the [`Signer`] to abstract over different signing methods (secret key, ledger, keystore, etc.). +/// It is used by the [`Signer`] to abstract over different signing methods (secret key, `ledger`, `keystore`, etc.). /// /// # Examples /// @@ -229,6 +229,11 @@ impl AccountKeyPair { /// # Ok(()) /// # } /// ``` +/// +/// ## Example of implementing `sign_meta` and `sign` methods +/// The default implementation of `sign_meta` and `sign` methods should work for most cases. +/// If you need to implement custom logic, you can override these methods. +/// See [`near_ledger`](`ledger::LedgerSigner`) implementation for an example. #[async_trait::async_trait] pub trait SignerTrait { /// Signs a delegate action for meta transactions. @@ -254,7 +259,7 @@ pub trait SignerTrait { /// Signs a regular transaction. /// /// This method is used for standard transactions. It creates a signed transaction - /// that can be sent to the NEAR network. + /// that can be sent to the `NEAR` network. /// /// The default implementation should work for most cases. async fn sign( @@ -275,7 +280,7 @@ pub trait SignerTrait { /// This is a `helper` method that should be implemented by the signer or fail with SignerError. /// As long as this method works, the default implementation of the [sign_meta](`SignerTrait::sign_meta`) and [sign](`SignerTrait::sign`) methods should work. /// - /// If you can't provide a SecretKey for some reason (E.g. Ledger), + /// If you can't provide a SecretKey for some reason (E.g. `Ledger``), /// you can fail with SignerError and override `sign_meta` and `sign` methods. fn tx_and_secret( &self, From c2fa669bc3f341d66ea59829e8bbd7cc09bafa12 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Fri, 20 Dec 2024 00:35:10 +0200 Subject: [PATCH 09/10] review --- src/lib.rs | 4 ++-- src/tokens.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e90a556..935d8f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! This crate provides a high-level API for interacting with NEAR Protocol, including: //! - [Account management and creation](Account) //! - [Contract deployment and interaction with it](Contract) -//! - [Token operations (NEAR, FT, NFT)](Tokens) +//! - [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) //! - [Custom transaction building and signing](Transaction) //! - [Querying the chain data](Chain) @@ -12,7 +12,7 @@ //! - Support for backup RPC endpoints //! //! # Example -//! In this example, we use Bob account with a predefined seed phrase to create Alice account and pre-fund it with 1 NEAR. +//! In this example, we use Bob account with a predefined seed phrase to create Alice account and pre-fund it with 1 `NEAR`. //! ```rust,no_run //! use near_api::{*, signer::generate_secret_key}; //! use std::str::FromStr; diff --git a/src/tokens.rs b/src/tokens.rs index 1d89f3d..d2a2745 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -37,7 +37,7 @@ type Result = core::result::Result; /// A wrapper struct that simplifies interactions with NEAR tokens (NEAR, FT, NFT). /// /// This struct provides convenient methods to interact with different types of tokens on NEAR Protocol: -/// - Native NEAR token operations +/// - [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 /// From 3e9c650f42d8d0e92186c56840968b6bb40ba2e6 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Fri, 20 Dec 2024 00:41:18 +0200 Subject: [PATCH 10/10] added link --- src/signer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/signer/mod.rs b/src/signer/mod.rs index 021c91b..c617585 100644 --- a/src/signer/mod.rs +++ b/src/signer/mod.rs @@ -72,7 +72,7 @@ //! # } //! ``` //! -//! # Advanced: Access Key Pooling +//! # Advanced: [Access Key Pooling](https://github.com/akorchyn/near-api/issues/2) //! //! The signer supports pooling multiple access keys for improved transaction throughput. //! It helps to mitigate concurrency issues that arise when multiple transactions are signed but the