Skip to content

Commit

Permalink
chore: added documentation for root level and signer module
Browse files Browse the repository at this point in the history
  • Loading branch information
akorchyn committed Dec 18, 2024
1 parent 7320bbe commit 243e5d6
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 13 deletions.
5 changes: 3 additions & 2 deletions examples/sign_options.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::str::FromStr;

use near_api::*;
use near_crypto::SecretKey;
use near_primitives::account::AccessKeyPermission;
Expand Down Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
//! // 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;
Expand Down
136 changes: 125 additions & 11 deletions src/signer/mod.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
//! 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<dyn std::error::Error>> {
//! 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<dyn std::error::Error>> {
//! 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<dyn std::error::Error>> {
//! 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<dyn std::error::Error>> {
//! 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},
Expand Down Expand Up @@ -97,6 +196,7 @@ pub struct Signer {
}

impl Signer {
/// Creates a new signer and instantiates nonce cache.
#[instrument(skip(signer))]
pub fn new<T: SignerTrait + Send + Sync + 'static>(
signer: T,
Expand All @@ -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<T: SignerTrait + Send + Sync + 'static>(
&self,
Expand Down Expand Up @@ -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<String>,
seed_phrase: &str,
password: Option<&str>,
) -> Result<SecretKeySigner, SecretError> {
Self::seed_phrase_with_hd_path(
seed_phrase,
Expand All @@ -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<String>,
password: Option<&str>,
) -> Result<SecretKeySigner, SecretError> {
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, AccessKeyFileError> {
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,
Expand All @@ -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<PublicKey, SignerError> {
let index = self.current_public_key.fetch_add(1, Ordering::SeqCst);
Expand Down Expand Up @@ -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<String>,
master_seed_phrase: &str,
password: Option<&str>,
) -> Result<SecretKey, SecretError> {
let master_seed =
bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(password.unwrap_or_default());
Expand All @@ -336,14 +450,14 @@ pub fn get_secret_key_from_seed(
pub fn generate_seed_phrase_custom(
word_count: Option<usize>,
hd_path: Option<BIP32Path>,
passphrase: Option<String>,
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::<Vec<&str>>().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,
)?;

Expand All @@ -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))
}
Expand All @@ -381,7 +495,7 @@ pub fn generate_secret_key() -> Result<SecretKey, SecretError> {
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)
Expand All @@ -390,7 +504,7 @@ pub fn generate_secret_key() -> Result<SecretKey, SecretError> {
pub fn generate_secret_key_from_seed_phrase(seed_phrase: String) -> Result<SecretKey, SecretError> {
get_secret_key_from_seed(
DEFAULT_HD_PATH.parse().expect("Valid HD path"),
seed_phrase,
&seed_phrase,
None,
)
}

0 comments on commit 243e5d6

Please sign in to comment.