diff --git a/helium-lib/src/asset.rs b/helium-lib/src/asset.rs index e860411b..8a9d4e57 100644 --- a/helium-lib/src/asset.rs +++ b/helium-lib/src/asset.rs @@ -1,40 +1,42 @@ use crate::{ + bs58, + client::{DasClient, DasSearchAssetsParams, SolanaRpcClient}, dao::Dao, entity_key::{self, AsEntityKey}, + error::{DecodeError, Error}, + helium_entity_manager, keypair::{serde_opt_pubkey, serde_pubkey, Pubkey}, kta, - result::{DecodeError, Error, Result}, - settings::{DasClient, DasSearchAssetsParams, Settings}, + solana_sdk::instruction::AccountMeta, }; -use helium_anchor_gen::helium_entity_manager; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use solana_sdk::bs58; use std::{collections::HashMap, result::Result as StdResult, str::FromStr}; -pub async fn for_entity_key(settings: &Settings, entity_key: &E) -> Result +pub async fn for_entity_key>( + client: &C, + entity_key: &E, +) -> Result where E: AsEntityKey, { let kta = kta::for_entity_key(entity_key).await?; - for_kta(settings, &kta).await + for_kta(client, &kta).await } -pub async fn for_kta( - settings: &Settings, +pub async fn for_kta>( + client: &C, kta: &helium_entity_manager::KeyToAssetV0, -) -> Result { - let jsonrpc = settings.mk_jsonrpc_client()?; - let asset_responase: Asset = jsonrpc.get_asset(&kta.asset).await?; +) -> Result { + let asset_responase: Asset = client.as_ref().get_asset(&kta.asset).await?; Ok(asset_responase) } -pub async fn for_kta_with_proof( - settings: &Settings, +pub async fn for_kta_with_proof>( + client: &C, kta: &helium_entity_manager::KeyToAssetV0, -) -> Result<(Asset, AssetProof)> { - let (asset, asset_proof) = - futures::try_join!(for_kta(settings, kta), proof::get(settings, kta))?; +) -> Result<(Asset, AssetProof), Error> { + let (asset, asset_proof) = futures::try_join!(for_kta(client, kta), proof::get(client, kta))?; Ok((asset, asset_proof)) } @@ -42,7 +44,7 @@ pub mod canopy { use super::*; use spl_account_compression::state::{merkle_tree_get_size, ConcurrentMerkleTreeHeader}; - async fn get_heights() -> Result> { + async fn get_heights() -> Result, Error> { const KNOWN_CANOPY_HEIGHT_URL: &str = "https://shdw-drive.genesysgo.net/6tcnBSybPG7piEDShBcrVtYJDPSvGrDbVvXmXKpzBvWP/merkles.json"; let client = reqwest::Client::new(); let map: HashMap = client @@ -62,13 +64,15 @@ pub mod canopy { .try_collect() } - pub async fn height_for_tree(settings: &Settings, tree: &Pubkey) -> Result { + pub async fn height_for_tree>( + client: &C, + tree: &Pubkey, + ) -> Result { use helium_anchor_gen::anchor_lang::AnchorDeserialize; if let Some(height) = get_heights().await?.get(tree) { return Ok(*height); } - let solana_client = settings.mk_solana_client()?; - let tree_account = solana_client.get_account(tree).await?; + let tree_account = client.as_ref().get_account(tree).await?; let header = ConcurrentMerkleTreeHeader::deserialize(&mut &tree_account.data[..]) .map_err(|_| DecodeError::other("invalid merkle tree header"))?; let merkle_tree_size = merkle_tree_get_size(&header) @@ -84,39 +88,39 @@ pub mod canopy { pub mod proof { use super::*; - pub async fn get( - settings: &Settings, + pub async fn get>( + client: &C, kta: &helium_entity_manager::KeyToAssetV0, - ) -> Result { - let jsonrpc = settings.mk_jsonrpc_client()?; - let asset_proof_response: AssetProof = jsonrpc.get_asset_proof(&kta.asset).await?; - + ) -> Result { + let asset_proof_response: AssetProof = client.as_ref().get_asset_proof(&kta.asset).await?; Ok(asset_proof_response) } - pub async fn for_entity_key(settings: &Settings, entity_key: &E) -> Result - where - E: AsEntityKey, - { + pub async fn for_entity_key>( + client: &C, + entity_key: &E, + ) -> Result { let kta = kta::for_entity_key(entity_key).await?; - get(settings, &kta).await + get(client, &kta).await } } -pub async fn search(client: &DasClient, params: DasSearchAssetsParams) -> Result { - Ok(client.search_assets(params).await?) +pub async fn search>( + client: &C, + params: DasSearchAssetsParams, +) -> Result { + Ok(client.as_ref().search_assets(params).await?) } -pub async fn for_owner( - settings: &Settings, +pub async fn for_owner>( + client: &C, creator: &Pubkey, owner: &Pubkey, -) -> Result> { +) -> Result, Error> { let mut params = DasSearchAssetsParams::for_owner(*owner, *creator); let mut results = vec![]; - let client = settings.mk_jsonrpc_client()?; loop { - let page = search(&client, params.clone()).await.map_err(Error::from)?; + let page = search(client, params.clone()).await.map_err(Error::from)?; if page.items.is_empty() { break; } @@ -190,32 +194,29 @@ pub struct AssetProof { } impl Asset { - pub fn kta_key(&self) -> Result { + pub fn kta_key(&self) -> Result { if let Some(creator) = self.creators.get(1) { return Ok(creator.address); } - let entity_key_str = self - .content - .json_uri - .path() - .strip_prefix('/') - .map(ToString::to_string) - .ok_or(DecodeError::other(format!( + let Some((_, entity_key_str)) = self.content.json_uri.path().rsplit_once('/') else { + return Err(DecodeError::other(format!( "missing entity key in \"{}\"", self.content.json_uri - )))?; + )) + .into()); + }; let key_serialization = if ["IOT OPS", "CARRIER"].contains(&self.content.metadata.symbol.as_str()) { helium_entity_manager::KeySerialization::UTF8 } else { helium_entity_manager::KeySerialization::B58 }; - let entity_key = entity_key::from_string(entity_key_str, key_serialization)?; + let entity_key = entity_key::from_str(entity_key_str, key_serialization)?; let kta_key = Dao::Hnt.entity_key_to_kta_key(&entity_key); Ok(kta_key) } - pub async fn get_kta(&self) -> Result { + pub async fn get_kta(&self) -> Result { kta::get(&self.kta_key()?).await } } @@ -224,7 +225,7 @@ impl AssetProof { pub fn proof( &self, len: Option, - ) -> Result> { + ) -> Result, Error> { self.proof .iter() .take(len.unwrap_or(self.proof.len())) @@ -241,12 +242,12 @@ impl AssetProof { .collect() } - pub async fn proof_for_tree( + pub async fn proof_for_tree>( &self, - settings: &Settings, + client: &C, tree: &Pubkey, - ) -> Result> { - let height = canopy::height_for_tree(settings, tree).await?; + ) -> Result, Error> { + let height = canopy::height_for_tree(client, tree).await?; self.proof(Some(height)) } } diff --git a/helium-lib/src/b64.rs b/helium-lib/src/b64.rs index 968c6d9c..d492a0e3 100644 --- a/helium-lib/src/b64.rs +++ b/helium-lib/src/b64.rs @@ -1,4 +1,4 @@ -use crate::result::{DecodeError, EncodeError, Result}; +use crate::error::{DecodeError, EncodeError}; use base64::{ engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}, Engine, @@ -9,13 +9,13 @@ pub fn encode>(v: T) -> String { STANDARD.encode(v.as_ref()) } -pub fn encode_message(v: &T) -> Result { +pub fn encode_message(v: &T) -> Result { let mut buf = vec![]; v.encode(&mut buf).map_err(EncodeError::from)?; Ok(STANDARD.encode(buf)) } -pub fn decode_message(v: &str) -> Result +pub fn decode_message(v: &str) -> Result where T: Message + Default, { @@ -28,21 +28,21 @@ pub fn url_encode>(v: T) -> String { URL_SAFE_NO_PAD.encode(v.as_ref()) } -pub fn decode>(v: T) -> Result> { - Ok(STANDARD.decode(v.as_ref()).map_err(DecodeError::from)?) +pub fn decode>(v: T) -> Result, DecodeError> { + STANDARD.decode(v.as_ref()).map_err(DecodeError::from) } -pub fn url_decode>(v: T) -> Result> { - Ok(URL_SAFE_NO_PAD +pub fn url_decode>(v: T) -> Result, DecodeError> { + URL_SAFE_NO_PAD .decode(v.as_ref()) - .map_err(DecodeError::from)?) + .map_err(DecodeError::from) } pub fn encode_u64(v: u64) -> String { STANDARD.encode(v.to_le_bytes()) } -pub fn decode_u64(v: &str) -> Result { +pub fn decode_u64(v: &str) -> Result { let decoded = STANDARD.decode(v).map_err(DecodeError::from)?; let int_bytes = decoded.as_slice().try_into().map_err(DecodeError::from)?; Ok(u64::from_le_bytes(int_bytes)) diff --git a/helium-lib/src/settings.rs b/helium-lib/src/client.rs similarity index 70% rename from helium-lib/src/settings.rs rename to helium-lib/src/client.rs index d5839f87..362d49d6 100644 --- a/helium-lib/src/settings.rs +++ b/helium-lib/src/client.rs @@ -1,19 +1,12 @@ use crate::{ - asset, is_zero, keypair, - result::{DecodeError, Error, Result as CrateResult}, - solana_client::nonblocking::rpc_client::RpcClient as SolanaRpcClient, + asset, + error::{DecodeError, Error}, + is_zero, keypair, solana_client, }; -use anchor_client::Client as AnchorClient; use jsonrpc_client::SendRequest; -use reqwest::Client as RestClient; -use serde::Deserialize; -use solana_sdk::signer::Signer; -use std::{ops::Deref, str::FromStr}; +use std::sync::Arc; use tracing::instrument; -static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); -static _SESSION_KEY_URL: &str = "https://wallet-api-v2.helium.com/api/sessionKey"; - pub static ONBOARDING_URL_MAINNET: &str = "https://onboarding.dewi.org/api/v3"; pub static ONBOARDING_URL_DEVNET: &str = "https://onboarding.web.test-helium.com/api/v3"; @@ -23,77 +16,71 @@ pub static VERIFIER_URL_DEVNET: &str = "https://ecc-verifier.web.test-helium.com pub static SOLANA_URL_MAINNET: &str = "https://solana-rpc.web.helium.io:443?session-key=Pluto"; pub static SOLANA_URL_DEVNET: &str = "https://solana-rpc.web.test-helium.com"; -#[derive(Debug, Clone, Deserialize)] -pub struct Settings { - url: url::Url, +pub use solana_client::nonblocking::rpc_client::RpcClient as SolanaRpcClient; + +#[derive(Clone)] +pub struct Client { + pub solana_client: Arc, + pub das_client: Arc, } -impl TryFrom<&Settings> for url::Url { - type Error = Error; - fn try_from(value: &Settings) -> CrateResult { - Ok(value - .to_string() - .parse::() - .map_err(DecodeError::from)?) - } +#[async_trait::async_trait] +pub trait GetAnchorAccount { + async fn anchor_account( + &self, + pubkey: &keypair::Pubkey, + ) -> Result; } -impl Default for Settings { - fn default() -> Self { - Self { - url: SOLANA_URL_MAINNET.parse().unwrap(), - } +#[async_trait::async_trait] +impl GetAnchorAccount for SolanaRpcClient { + async fn anchor_account( + &self, + pubkey: &keypair::Pubkey, + ) -> Result { + let account = self.get_account(pubkey).await?; + let decoded = T::try_deserialize(&mut account.data.as_ref())?; + Ok(decoded) } } -impl std::fmt::Display for Settings { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.url.fmt(f) +#[async_trait::async_trait] +impl GetAnchorAccount for Client { + async fn anchor_account( + &self, + pubkey: &keypair::Pubkey, + ) -> Result { + self.solana_client.anchor_account(pubkey).await } } -impl TryFrom<&str> for Settings { +impl TryFrom<&str> for Client { type Error = Error; - fn try_from(value: &str) -> CrateResult { + fn try_from(value: &str) -> Result { let url = match value { "m" | "mainnet-beta" => SOLANA_URL_MAINNET, "d" | "devnet" => SOLANA_URL_DEVNET, url => url, }; - let url: url::Url = url.parse().map_err(DecodeError::from)?; - Ok(Self { url }) + let das_client = Arc::new(DasClient::with_base_url(url)?); + let solana_client = Arc::new(SolanaRpcClient::new(url.to_string())); + Ok(Self { + solana_client, + das_client, + }) } } -impl Settings { - pub fn mk_anchor_client>( - &self, - payer: C, - ) -> CrateResult> { - let url_str = self.to_string(); - let cluster = anchor_client::Cluster::from_str(&url_str).map_err(DecodeError::other)?; - Ok(AnchorClient::new_with_options( - cluster, - payer, - solana_sdk::commitment_config::CommitmentConfig::finalized(), - )) - } - - pub fn mk_solana_client(&self) -> CrateResult { - Ok(SolanaRpcClient::new_with_commitment( - self.to_string(), - solana_sdk::commitment_config::CommitmentConfig::finalized(), - )) - } - - pub fn mk_jsonrpc_client(&self) -> CrateResult { - let client = DasClient::from_settings(self)?; - Ok(client) +impl AsRef for Client { + fn as_ref(&self) -> &SolanaRpcClient { + &self.solana_client } +} - pub fn mk_rest_client() -> CrateResult { - Ok(RestClient::builder().user_agent(USER_AGENT).build()?) +impl AsRef for Client { + fn as_ref(&self) -> &DasClient { + &self.das_client } } @@ -137,6 +124,8 @@ pub type DasClientError = jsonrpc_client::Error; #[jsonrpc_client::api] pub trait DAS {} +static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); + #[jsonrpc_client::implement(DAS)] #[derive(Debug, Clone)] pub struct DasClient { @@ -144,10 +133,17 @@ pub struct DasClient { base_url: reqwest::Url, } +impl Default for DasClient { + fn default() -> Self { + // safe to unwrap + Self::with_base_url(SOLANA_URL_MAINNET).unwrap() + } +} + impl DasClient { - pub fn from_settings(settings: &Settings) -> CrateResult { + pub fn with_base_url(url: &str) -> Result { let client = reqwest::Client::new(); - let base_url = settings.to_string().parse().map_err(DecodeError::from)?; + let base_url = url.parse().map_err(DecodeError::from)?; Ok(Self { inner: client, base_url, diff --git a/helium-lib/src/dao.rs b/helium-lib/src/dao.rs index 560808b6..2b1fbb6e 100644 --- a/helium-lib/src/dao.rs +++ b/helium-lib/src/dao.rs @@ -1,8 +1,7 @@ use crate::{ - entity_key::AsEntityKey, keypair::Pubkey, programs::TOKEN_METADATA_PROGRAM_ID, result::Result, - token::Token, + data_credits, entity_key::AsEntityKey, error::Error, helium_entity_manager, helium_sub_daos, + keypair::Pubkey, lazy_distributor, programs::TOKEN_METADATA_PROGRAM_ID, token::Token, }; -use helium_anchor_gen::{data_credits, helium_entity_manager, helium_sub_daos, lazy_distributor}; use sha2::{Digest, Sha256}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] @@ -201,7 +200,10 @@ impl SubDao { key } - pub fn info_key_for_helium_key(&self, public_key: &helium_crypto::PublicKey) -> Result { + pub fn info_key_for_helium_key( + &self, + public_key: &helium_crypto::PublicKey, + ) -> Result { let entity_key = public_key.as_entity_key(); Ok(self.info_key(&entity_key)) } diff --git a/helium-lib/src/dc.rs b/helium-lib/src/dc.rs index 7060f0d5..74865760 100644 --- a/helium-lib/src/dc.rs +++ b/helium-lib/src/dc.rs @@ -1,23 +1,24 @@ use crate::{ + anchor_lang::{InstructionData, ToAccountMetas}, + circuit_breaker, + client::{GetAnchorAccount, SolanaRpcClient}, dao::{Dao, SubDao}, - keypair::{GetPubkey, Pubkey}, - result::{DecodeError, Result}, - settings::Settings, + data_credits, + error::{DecodeError, Error}, + keypair::{Keypair, Pubkey}, + solana_sdk::{instruction::Instruction, signer::Signer, transaction::Transaction}, token::{Token, TokenAmount}, }; -use anchor_client::solana_sdk::signature::Signer; -use helium_anchor_gen::{circuit_breaker, data_credits}; -use std::{ops::Deref, result::Result as StdResult}; -pub async fn mint + GetPubkey>( - settings: &Settings, +pub async fn mint>( + client: &C, amount: TokenAmount, payee: &Pubkey, - keypair: C, -) -> Result { + keypair: &Keypair, +) -> Result { fn token_amount_to_mint_args( amount: TokenAmount, - ) -> StdResult { + ) -> Result { match amount.token { Token::Hnt => Ok(data_credits::MintDataCreditsArgsV0 { hnt_amount: Some(amount.amount), @@ -30,118 +31,134 @@ pub async fn mint + GetPubkey>( other => Err(DecodeError::other(format!("Invalid token type: {other}"))), } } + fn mk_accounts( + owner: Pubkey, + recipient: Pubkey, + hnt_price_oracle: Pubkey, + ) -> impl ToAccountMetas { + data_credits::accounts::MintDataCreditsV0 { + data_credits: SubDao::dc_key(), + owner, + hnt_mint: *Token::Hnt.mint(), + dc_mint: *Token::Dc.mint(), + recipient, + recipient_token_account: Token::Dc.associated_token_adress(&recipient), + system_program: solana_sdk::system_program::ID, + token_program: anchor_spl::token::ID, + associated_token_program: anchor_spl::associated_token::ID, + hnt_price_oracle, + circuit_breaker_program: circuit_breaker::id(), + circuit_breaker: Token::Dc.mint_circuit_breaker_address(), + burner: Token::Hnt.associated_token_adress(&owner), + } + } - // let client = self.settings.mk_anchor_client(keypair.clone())?; - let dc_program = settings - .mk_anchor_client(keypair.clone())? - .program(data_credits::id())?; - let data_credits = SubDao::dc_key(); - let hnt_price_oracle = dc_program - .account::(data_credits) + let hnt_price_oracle = client + .as_ref() + .anchor_account::(&SubDao::dc_key()) .await? .hnt_price_oracle; - let burner = Token::Hnt.associated_token_adress(&keypair.pubkey()); - let recipient_token_account = Token::Dc.associated_token_adress(payee); - let accounts = data_credits::accounts::MintDataCreditsV0 { - data_credits, - owner: keypair.pubkey(), - hnt_mint: *Token::Hnt.mint(), - dc_mint: *Token::Dc.mint(), - recipient: *payee, - recipient_token_account, - system_program: solana_sdk::system_program::ID, - token_program: anchor_spl::token::ID, - associated_token_program: anchor_spl::associated_token::ID, - hnt_price_oracle, - circuit_breaker_program: circuit_breaker::id(), - circuit_breaker: Token::Dc.mint_circuit_breaker_address(), - burner, + let mint_ix = Instruction { + program_id: data_credits::id(), + accounts: mk_accounts(keypair.pubkey(), *payee, hnt_price_oracle).to_account_metas(None), + data: data_credits::instruction::MintDataCreditsV0 { + _args: token_amount_to_mint_args(amount)?, + } + .data(), }; - let args = data_credits::instruction::MintDataCreditsV0 { - _args: token_amount_to_mint_args(amount)?, - }; - let tx = dc_program - .request() - .accounts(accounts) - .args(args) - .signed_transaction() - .await?; + let recent_blockhash = client.as_ref().get_latest_blockhash().await?; + let tx = Transaction::new_signed_with_payer( + &[mint_ix], + Some(&keypair.pubkey()), + &[keypair], + recent_blockhash, + ); + Ok(tx) } -pub async fn delegate + GetPubkey>( - settings: &Settings, +pub async fn delegate>( + client: &C, subdao: SubDao, payer_key: &str, amount: u64, - keypair: C, -) -> Result { - let client = settings.mk_anchor_client(keypair.clone())?; - let dc_program = client.program(data_credits::id())?; - - let delegated_data_credits = subdao.delegated_dc_key(payer_key); - - let accounts = data_credits::accounts::DelegateDataCreditsV0 { - delegated_data_credits, - data_credits: SubDao::dc_key(), - dc_mint: *Token::Dc.mint(), - dao: Dao::Hnt.key(), - sub_dao: subdao.key(), - owner: keypair.pubkey(), - from_account: Token::Dc.associated_token_adress(&keypair.pubkey()), - escrow_account: subdao.escrow_key(&delegated_data_credits), - payer: keypair.pubkey(), - associated_token_program: anchor_spl::associated_token::ID, - token_program: anchor_spl::token::ID, - system_program: solana_sdk::system_program::ID, - }; + keypair: &Keypair, +) -> Result { + fn mk_accounts(delegated_dc_key: Pubkey, subdao: SubDao, owner: Pubkey) -> impl ToAccountMetas { + data_credits::accounts::DelegateDataCreditsV0 { + delegated_data_credits: delegated_dc_key, + data_credits: SubDao::dc_key(), + dc_mint: *Token::Dc.mint(), + dao: Dao::Hnt.key(), + sub_dao: subdao.key(), + owner, + from_account: Token::Dc.associated_token_adress(&owner), + escrow_account: subdao.escrow_key(&delegated_dc_key), + payer: owner, + associated_token_program: anchor_spl::associated_token::ID, + token_program: anchor_spl::token::ID, + system_program: solana_sdk::system_program::ID, + } + } - let args = data_credits::instruction::DelegateDataCreditsV0 { - _args: data_credits::DelegateDataCreditsArgsV0 { - amount, - router_key: payer_key.to_string(), - }, + let delegated_dc_key = subdao.delegated_dc_key(payer_key); + let delegate_ix = Instruction { + program_id: data_credits::id(), + accounts: mk_accounts(delegated_dc_key, subdao, keypair.pubkey()).to_account_metas(None), + data: data_credits::instruction::DelegateDataCreditsV0 { + _args: data_credits::DelegateDataCreditsArgsV0 { + amount, + router_key: payer_key.to_string(), + }, + } + .data(), }; - let tx = dc_program - .request() - .accounts(accounts) - .args(args) - .signed_transaction() - .await?; + let recent_blockhash = client.as_ref().get_latest_blockhash().await?; + let tx = Transaction::new_signed_with_payer( + &[delegate_ix], + Some(&keypair.pubkey()), + &[keypair], + recent_blockhash, + ); Ok(tx) } -pub async fn burn + GetPubkey>( - settings: &Settings, +pub async fn burn>( + client: &C, amount: u64, - keypair: C, -) -> Result { - let client = settings.mk_anchor_client(keypair.clone())?; - let dc_program = client.program(data_credits::id())?; - - let accounts = data_credits::accounts::BurnWithoutTrackingV0 { - BurnWithoutTrackingV0burn_accounts: - data_credits::accounts::BurnWithoutTrackingV0BurnAccounts { - burner: Token::Dc.associated_token_adress(&keypair.pubkey()), - dc_mint: *Token::Dc.mint(), - data_credits: SubDao::dc_key(), - token_program: anchor_spl::token::ID, - system_program: solana_sdk::system_program::ID, - associated_token_program: anchor_spl::associated_token::ID, - owner: keypair.pubkey(), - }, - }; + keypair: &Keypair, +) -> Result { + fn mk_accounts(owner: Pubkey) -> impl ToAccountMetas { + data_credits::accounts::BurnWithoutTrackingV0 { + BurnWithoutTrackingV0burn_accounts: + data_credits::accounts::BurnWithoutTrackingV0BurnAccounts { + burner: Token::Dc.associated_token_adress(&owner), + dc_mint: *Token::Dc.mint(), + data_credits: SubDao::dc_key(), + token_program: anchor_spl::token::ID, + system_program: solana_sdk::system_program::ID, + associated_token_program: anchor_spl::associated_token::ID, + owner, + }, + } + } - let args = data_credits::instruction::BurnWithoutTrackingV0 { - _args: data_credits::BurnWithoutTrackingArgsV0 { amount }, + let burn_ix = Instruction { + program_id: data_credits::id(), + accounts: mk_accounts(keypair.pubkey()).to_account_metas(None), + data: data_credits::instruction::BurnWithoutTrackingV0 { + _args: data_credits::BurnWithoutTrackingArgsV0 { amount }, + } + .data(), }; - let tx = dc_program - .request() - .accounts(accounts) - .args(args) - .signed_transaction() - .await?; + let recent_blockhash = client.as_ref().get_latest_blockhash().await?; + let tx = Transaction::new_signed_with_payer( + &[burn_ix], + Some(&keypair.pubkey()), + &[keypair], + recent_blockhash, + ); Ok(tx) } diff --git a/helium-lib/src/entity_key.rs b/helium-lib/src/entity_key.rs index d599e199..6a3b7564 100644 --- a/helium-lib/src/entity_key.rs +++ b/helium-lib/src/entity_key.rs @@ -1,4 +1,4 @@ -use crate::result::{DecodeError, Result}; +use crate::error::DecodeError; use solana_sdk::bs58; pub trait AsEntityKey { @@ -39,10 +39,12 @@ impl AsEntityKey for helium_crypto::PublicKey { pub use helium_anchor_gen::helium_entity_manager::KeySerialization; -pub fn from_string(str: String, encoding: KeySerialization) -> Result> { +pub fn from_str(str: &str, encoding: KeySerialization) -> Result, DecodeError> { let entity_key = match encoding { KeySerialization::UTF8 => str.as_entity_key(), - KeySerialization::B58 => bs58::decode(str).into_vec().map_err(DecodeError::from)?, + KeySerialization::B58 => bs58::decode(str) + .into_vec() + .map_err(|_| DecodeError::other(format!("invalid entity key {}", str)))?, }; Ok(entity_key) } diff --git a/helium-lib/src/result.rs b/helium-lib/src/error.rs similarity index 95% rename from helium-lib/src/result.rs rename to helium-lib/src/error.rs index 36edf829..8f1b2400 100644 --- a/helium-lib/src/result.rs +++ b/helium-lib/src/error.rs @@ -1,10 +1,7 @@ +use crate::{client, onboarding, token}; use std::{array::TryFromSliceError, num::TryFromIntError}; use thiserror::Error; -use crate::{onboarding, settings, token}; - -pub type Result = std::result::Result; - #[derive(Error, Debug)] pub enum Error { #[cfg(feature = "mnemonic")] @@ -17,7 +14,7 @@ pub enum Error { #[error("anchor lang: {0}")] AnchorLang(#[from] helium_anchor_gen::anchor_lang::error::Error), #[error("DAS client: {0}")] - Das(#[from] settings::DasClientError), + Das(#[from] client::DasClientError), #[error("price client: {0}")] Price(#[from] token::price::PriceError), #[error("rest client: {0}")] diff --git a/helium-lib/src/hotspot.rs b/helium-lib/src/hotspot.rs index fbbcc411..a9acfd0c 100644 --- a/helium-lib/src/hotspot.rs +++ b/helium-lib/src/hotspot.rs @@ -1,14 +1,23 @@ use crate::{ - asset, + anchor_lang::{AnchorDeserialize, Discriminator, InstructionData, ToAccountMetas}, + asset, bs58, + client::{DasClient, DasSearchAssetsParams, GetAnchorAccount, SolanaRpcClient}, dao::{Dao, SubDao}, + data_credits, entity_key::AsEntityKey, - is_zero, - keypair::{pubkey, serde_pubkey, GetPubkey, Keypair, Pubkey}, + error::{DecodeError, EncodeError, Error}, + helium_entity_manager, helium_sub_daos, is_zero, + keypair::{pubkey, serde_pubkey, Keypair, Pubkey}, kta, onboarding, - priority_fee::{self, compute_budget_instruction, compute_price_instruction, SetPriorityFees}, + priority_fee::{self, compute_budget_instruction, compute_price_instruction_for_accounts}, programs::{SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID}, - result::{DecodeError, EncodeError, Error, Result}, - settings::{DasClient, DasSearchAssetsParams, Settings}, + solana_sdk::{ + commitment_config::CommitmentConfig, + instruction::{AccountMeta, Instruction}, + signature::Signature, + signer::Signer, + transaction::Transaction, + }, token::Token, }; use angry_purple_tiger::AnimalName; @@ -17,21 +26,17 @@ use futures::{ stream::{self, StreamExt, TryStreamExt}, TryFutureExt, }; -use helium_anchor_gen::{ - anchor_lang::{AnchorDeserialize, Discriminator, ToAccountMetas}, - data_credits, helium_entity_manager, helium_sub_daos, -}; use itertools::Itertools; use rust_decimal::prelude::*; use serde::{Deserialize, Serialize}; -use solana_program::instruction::AccountMeta; -use solana_sdk::{bs58, commitment_config::CommitmentConfig, signature::Signature, signer::Signer}; -use std::{collections::HashMap, ops::Deref, result::Result as StdResult, str::FromStr}; +use std::{collections::HashMap, str::FromStr}; pub const HOTSPOT_CREATOR: Pubkey = pubkey!("Fv5hf1Fg58htfC7YEXKNEfkpuogUUQDDTLgjGWxxv48H"); pub const ECC_VERIFIER: Pubkey = pubkey!("eccSAJM3tq7nQSpQTm8roxv4FPoipCkMsGizW2KBhqZ"); -pub fn key_from_kta(kta: helium_entity_manager::KeyToAssetV0) -> Result { +pub fn key_from_kta( + kta: helium_entity_manager::KeyToAssetV0, +) -> Result { let key_str = match kta.key_serialization { helium_entity_manager::KeySerialization::B58 => bs58::encode(kta.entity_key).into_string(), helium_entity_manager::KeySerialization::UTF8 => String::from_utf8(kta.entity_key) @@ -40,35 +45,47 @@ pub fn key_from_kta(kta: helium_entity_manager::KeyToAssetV0) -> Result Result> { - let assets = asset::for_owner(settings, &HOTSPOT_CREATOR, owner).await?; - stream::iter(assets) +pub async fn for_owner>( + client: &C, + owner: &Pubkey, +) -> Result, Error> { + let assets = asset::for_owner(client, &HOTSPOT_CREATOR, owner).await?; + let hotspot_assets = assets + .into_iter() + .filter(|asset| asset.content.metadata.symbol == "HOTSPOT"); + stream::iter(hotspot_assets) .map(|asset| async move { Hotspot::from_asset(asset).await }) .buffered(5) .try_collect::>() .await } -pub async fn search(client: &DasClient, params: DasSearchAssetsParams) -> Result { +pub async fn search>( + client: &C, + params: DasSearchAssetsParams, +) -> Result { asset::search(client, params) .and_then(HotspotPage::from_asset_page) .await } -pub async fn get(settings: &Settings, hotspot_key: &helium_crypto::PublicKey) -> Result { +pub async fn get>( + client: &C, + hotspot_key: &helium_crypto::PublicKey, +) -> Result { let kta = kta::for_entity_key(hotspot_key).await?; - let asset = asset::for_kta(settings, &kta).await?; + let asset = asset::for_kta(client, &kta).await?; Hotspot::from_asset(asset).await } -pub async fn get_with_info( - settings: &Settings, +pub async fn get_with_info + GetAnchorAccount>( + client: &C, subdaos: &[SubDao], hotspot_key: &helium_crypto::PublicKey, -) -> Result { +) -> Result { let (mut hotspot, info) = futures::try_join!( - get(settings, hotspot_key), - info::get(settings, subdaos, hotspot_key) + get(client, hotspot_key), + info::get(client, subdaos, hotspot_key) )?; if !info.is_empty() { hotspot.info = Some(info); @@ -95,49 +112,34 @@ pub mod info { UiParsedInstruction, UiTransactionEncoding, }; - pub async fn for_subdao( - settings: &Settings, + pub async fn for_subdao( + client: &C, subdao: SubDao, key: &helium_crypto::PublicKey, - ) -> Result> { - fn maybe_info( - result: StdResult, - ) -> Result> - where - T: Into, - { - match result { - Ok(account) => Ok(Some(account.into())), - Err(anchor_client::ClientError::AccountNotFound) => Ok(None), - Err(err) => Err(err.into()), - } - } - - let client = settings.mk_anchor_client(Keypair::void())?; + ) -> Result, Error> { let info_key = subdao.info_key_for_helium_key(key)?; - let program = client.program(helium_entity_manager::id())?; - match subdao { - SubDao::Iot => maybe_info( - program - .account::(info_key) - .await, - ), - SubDao::Mobile => maybe_info( - program - .account::(info_key) - .await, - ), + let hotspot_info = match subdao { + SubDao::Iot => client + .anchor_account::(&info_key) + .await + .map(Into::into), + SubDao::Mobile => client + .anchor_account::(&info_key) + .await + .map(Into::into), } + .ok(); + Ok(hotspot_info) } - pub async fn get( - settings: &Settings, + pub async fn get( + client: &C, subdaos: &[SubDao], key: &helium_crypto::PublicKey, - ) -> Result> { + ) -> Result, Error> { stream::iter(subdaos.to_vec()) .map(|subdao| { - for_subdao(settings, subdao, key) + for_subdao(client, subdao, key) .map_ok(move |maybe_metadata| maybe_metadata.map(|metadata| (subdao, metadata))) }) .buffer_unordered(10) @@ -147,7 +149,7 @@ pub mod info { .await } - #[derive(Serialize, Deserialize, Debug, Default)] + #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct HotspotInfoUpdateParams { #[serde(skip_serializing_if = "Option::is_none")] pub before: Option, @@ -168,13 +170,13 @@ pub mod info { } } - pub async fn updates( - settings: &Settings, + pub async fn updates>( + client: &C, account: &Pubkey, params: HotspotInfoUpdateParams, - ) -> Result> { - let client = settings.mk_solana_client()?; + ) -> Result, Error> { let signatures = client + .as_ref() .get_signatures_for_address_with_config(account, params.into()) .await?; @@ -187,8 +189,8 @@ pub mod info { .map_err(Error::from) }) .map_ok(|signature| async move { - let client = settings.mk_solana_client()?; client + .as_ref() .get_transaction_with_config( &signature, RpcTransactionConfig { @@ -213,7 +215,7 @@ pub mod info { impl CommittedHotspotInfoUpdate { fn from_transaction( txn: EncodedConfirmedTransactionWithStatusMeta, - ) -> StdResult, DecodeError> { + ) -> Result, DecodeError> { let EncodedTransaction::Json(ui_txn) = txn.transaction.transaction else { return Err(DecodeError::other("not a json encoded transaction")); }; @@ -232,9 +234,9 @@ pub mod info { .into_iter() .map(HotspotInfoUpdate::from_ui_instruction) .filter(|result| matches!(result, Ok(Some(_v)))) - .collect::, _>>>() + .collect::, _>>>() .into_iter() - .collect::>, _>>()? + .collect::>, _>>()? .first() .cloned() .flatten() @@ -249,9 +251,7 @@ pub mod info { } impl HotspotInfoUpdate { - fn from_ui_instruction( - ixn: UiInstruction, - ) -> StdResult, DecodeError> { + fn from_ui_instruction(ixn: UiInstruction) -> Result, DecodeError> { use solana_transaction_status::UiPartiallyDecodedInstruction; let UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(decoded)) = ixn else { return Err(DecodeError::other("not a decoded instruction")); @@ -273,7 +273,7 @@ pub mod info { fn get_info_key( decoded: &UiPartiallyDecodedInstruction, index: usize, - ) -> StdResult { + ) -> Result { let account_str = decoded.accounts.get(index).ok_or_else(|| { DecodeError::other("missing info key in instruction accounts") })?; @@ -320,13 +320,13 @@ pub mod info { } } -pub async fn direct_update + GetPubkey>( - settings: &Settings, +pub async fn direct_update + AsRef>( + client: &C, hotspot: &helium_crypto::PublicKey, - keypair: C, update: HotspotInfoUpdate, -) -> Result { - fn mk_update_accounts( + keypair: &Keypair, +) -> Result { + fn mk_accounts( subdao: SubDao, kta: &helium_entity_manager::KeyToAssetV0, asset: &asset::Asset, @@ -364,19 +364,15 @@ pub async fn direct_update + GetPubkey>( } } - let anchor_client = settings.mk_anchor_client(keypair.clone())?; - let program = anchor_client.program(helium_entity_manager::id())?; - let solana_client = settings.mk_solana_client()?; - let kta = kta::for_entity_key(hotspot).await?; - let (asset, asset_proof) = asset::for_kta_with_proof(settings, &kta).await?; - let accounts = mk_update_accounts(update.subdao(), &kta, &asset, &program.payer()); - - let mut ixs = program - .request() - .compute_budget(200_000) - .compute_price(priority_fee::get_estimate(&solana_client, &accounts).await?) - .args(helium_entity_manager::instruction::UpdateIotInfoV0 { + let (asset, asset_proof) = asset::for_kta_with_proof(&client, &kta).await?; + let mut accounts = mk_accounts(update.subdao(), &kta, &asset, &keypair.pubkey()); + accounts.extend_from_slice(&asset_proof.proof(Some(3))?); + + let update_ix = Instruction { + program_id: helium_entity_manager::id(), + accounts: accounts.to_account_metas(None), + data: helium_entity_manager::instruction::UpdateIotInfoV0 { _args: helium_entity_manager::UpdateIotInfoArgsV0 { root: asset_proof.root.to_bytes(), data_hash: asset.compression.data_hash, @@ -386,37 +382,45 @@ pub async fn direct_update + GetPubkey>( gain: update.gain_i32(), location: update.location_u64(), }, - }) - .accounts(accounts) - .instructions()?; - ixs[2] - .accounts - .extend_from_slice(&asset_proof.proof(Some(3))?); - - let mut tx = solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&program.payer())); - let blockhash = program.rpc().get_latest_blockhash()?; - tx.try_sign(&[&*keypair], blockhash)?; + } + .data(), + }; + + let ixs = &[ + priority_fee::compute_budget_instruction(200_000), + priority_fee::compute_price_instruction_for_accounts(client, &accounts).await?, + update_ix, + ]; + + let recent_blockhash = AsRef::::as_ref(client) + .get_latest_blockhash() + .await?; + let tx = Transaction::new_signed_with_payer( + ixs, + Some(&keypair.pubkey()), + &[keypair], + recent_blockhash, + ); Ok(tx) } -pub async fn update + GetPubkey>( - settings: &Settings, +pub async fn update + AsRef>( + client: &C, onboarding_server: Option, hotspot: &helium_crypto::PublicKey, update: HotspotInfoUpdate, - keypair: C, -) -> Result { + keypair: &Keypair, +) -> Result { let public_key = keypair.pubkey(); if let Some(server) = onboarding_server { let onboarding_client = onboarding::Client::new(&server); let mut tx = onboarding_client .get_update_txn(hotspot, &public_key, update) .await?; - tx.try_partial_sign(&[&*keypair], tx.message.recent_blockhash)?; + tx.try_partial_sign(&[keypair], tx.message.recent_blockhash)?; return Ok(tx); }; - let mut tx = direct_update(settings, hotspot, keypair.clone(), update).await?; - tx.try_partial_sign(&[&*keypair], tx.message.recent_blockhash)?; + let tx = direct_update(client, hotspot, update, keypair).await?; Ok(tx) } @@ -425,19 +429,17 @@ pub async fn update + GetPubkey>( /// The hotspot is transferred from the owner of the hotspot to the given recipient /// Note that the owner is currently expected to sign this transaction and pay for /// transaction fees. -pub async fn transfer_transaction( - settings: &Settings, +pub async fn transfer_transaction + AsRef>( + client: &C, hotspot_key: &helium_crypto::PublicKey, recipient: &Pubkey, -) -> Result { - let anchor_client = settings.mk_anchor_client(Keypair::void())?; - let program = anchor_client.program(mpl_bubblegum::ID)?; +) -> Result { let kta = kta::for_entity_key(hotspot_key).await?; - let (asset, asset_proof) = asset::for_kta_with_proof(settings, &kta).await?; + let (asset, asset_proof) = asset::for_kta_with_proof(client, &kta).await?; let leaf_delegate = asset.ownership.delegate.unwrap_or(asset.ownership.owner); let merkle_tree = asset_proof.tree_id; - let remaining_accounts = asset_proof.proof_for_tree(settings, &merkle_tree).await?; + let remaining_accounts = asset_proof.proof_for_tree(client, &merkle_tree).await?; let transfer = mpl_bubblegum::instructions::Transfer { leaf_owner: (asset.ownership.owner, false), @@ -463,27 +465,25 @@ pub async fn transfer_transaction( let ixs = &[ compute_budget_instruction(200_000), - compute_price_instruction( - priority_fee::get_estimate(&program.async_rpc(), &priority_fee_accounts).await?, - ), + compute_price_instruction_for_accounts(client, &priority_fee_accounts).await?, transfer_ix, ]; - let tx = - solana_sdk::transaction::Transaction::new_with_payer(ixs, Some(&asset.ownership.owner)); + let tx = Transaction::new_with_payer(ixs, Some(&asset.ownership.owner)); Ok(tx) } -pub async fn transfer>( - settings: &Settings, +pub async fn transfer + AsRef>( + client: &C, hotspot_key: &helium_crypto::PublicKey, recipient: &Pubkey, - keypair: C, -) -> Result { - let mut tx = transfer_transaction(settings, hotspot_key, recipient).await?; - let solana_client = settings.mk_solana_client()?; - let blockhash = solana_client.get_latest_blockhash().await?; - tx.try_sign(&[&*keypair], blockhash)?; + keypair: &Keypair, +) -> Result { + let mut tx = transfer_transaction(client, hotspot_key, recipient).await?; + let blockhash = AsRef::::as_ref(client) + .get_latest_blockhash() + .await?; + tx.try_sign(&[keypair], blockhash)?; Ok(tx) } @@ -494,14 +494,13 @@ pub mod dataonly { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, TOKEN_METADATA_PROGRAM_ID, }; use helium_proto::{BlockchainTxnAddGatewayV1, Message}; - use priority_fee::SetPriorityFees; - pub async fn onboard + GetPubkey>( - settings: &Settings, + pub async fn onboard + AsRef + GetAnchorAccount>( + client: &C, hotspot_key: &helium_crypto::PublicKey, assertion: HotspotInfoUpdate, - keypair: C, - ) -> Result { + keypair: &Keypair, + ) -> Result { use helium_entity_manager::accounts::OnboardDataOnlyIotHotspotV0; fn mk_accounts( config_account: helium_entity_manager::DataOnlyConfigV0, @@ -511,7 +510,6 @@ pub mod dataonly { let dao = Dao::Hnt; let entity_key = hotspot_key.as_entity_key(); let data_only_config_key = dao.dataonly_config_key(); - OnboardDataOnlyIotHotspotV0 { payer: owner, dc_fee_payer: owner, @@ -535,57 +533,57 @@ pub mod dataonly { } } - let anchor_client = settings.mk_anchor_client(keypair.clone())?; - let solana_client = settings.mk_solana_client()?; - let program = anchor_client.program(helium_entity_manager::id())?; - let config_account = program - .account::(Dao::Hnt.dataonly_config_key()) + let config_account = client + .anchor_account::( + &Dao::Hnt.dataonly_config_key(), + ) .await?; - let kta = kta::for_entity_key(hotspot_key).await?; - let (asset, asset_proof) = asset::for_kta_with_proof(settings, &kta).await?; - let onboard_accounts = mk_accounts(config_account, program.payer(), hotspot_key); - - let mut ixs = program - .request() - .compute_budget(300_000) - .compute_price(priority_fee::get_estimate(&solana_client, &onboard_accounts).await?) - .args( - helium_entity_manager::instruction::OnboardDataOnlyIotHotspotV0 { - _args: helium_entity_manager::OnboardDataOnlyIotHotspotArgsV0 { - data_hash: asset.compression.data_hash, - creator_hash: asset.compression.creator_hash, - index: asset.compression.leaf_id()?, - root: asset_proof.root.to_bytes(), - elevation: *assertion.elevation(), - gain: assertion.gain_i32(), - location: assertion.location_u64(), - }, + let (asset, asset_proof) = asset::for_kta_with_proof(client, &kta).await?; + let mut onboard_accounts = + mk_accounts(config_account, keypair.pubkey(), hotspot_key).to_account_metas(None); + onboard_accounts.extend_from_slice(&asset_proof.proof(Some(3))?); + + let onboard_ix = solana_sdk::instruction::Instruction { + program_id: helium_entity_manager::id(), + accounts: onboard_accounts, + data: helium_entity_manager::instruction::OnboardDataOnlyIotHotspotV0 { + _args: helium_entity_manager::OnboardDataOnlyIotHotspotArgsV0 { + data_hash: asset.compression.data_hash, + creator_hash: asset.compression.creator_hash, + index: asset.compression.leaf_id()?, + root: asset_proof.root.to_bytes(), + elevation: *assertion.elevation(), + gain: assertion.gain_i32(), + location: assertion.location_u64(), }, - ) - .accounts(onboard_accounts) - .instructions()?; - ixs[2] - .accounts - .extend_from_slice(&asset_proof.proof(Some(3))?); - - let mut tx = - solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&keypair.pubkey())); - let blockhash = program.rpc().get_latest_blockhash()?; + } + .data(), + }; - tx.try_sign(&[&*keypair], blockhash)?; + let ixs = &[ + priority_fee::compute_budget_instruction(300_000), + priority_fee::compute_price_instruction_for_accounts(client, &onboard_ix.accounts) + .await?, + onboard_ix, + ]; + let blockhash = AsRef::::as_ref(client) + .get_latest_blockhash() + .await?; + let tx = + Transaction::new_signed_with_payer(ixs, Some(&keypair.pubkey()), &[keypair], blockhash); Ok(tx) } - pub async fn issue + GetPubkey>( - settings: &Settings, + pub async fn issue + GetAnchorAccount>( + client: &C, verifier: &str, add_tx: &mut BlockchainTxnAddGatewayV1, - keypair: C, - ) -> Result { + keypair: &Keypair, + ) -> Result { use helium_entity_manager::accounts::IssueDataOnlyEntityV0; - fn mk_issue_accounts( + fn mk_accounts( config_account: helium_entity_manager::DataOnlyConfigV0, owner: Pubkey, entity_key: &[u8], @@ -616,31 +614,35 @@ pub mod dataonly { } } - let anchor_client = settings.mk_anchor_client(keypair.clone())?; - let solana_client = settings.mk_solana_client()?; - let program = anchor_client.program(helium_entity_manager::id())?; - let config_account = program - .account::(Dao::Hnt.dataonly_config_key()) + let config_account = client + .anchor_account::( + &Dao::Hnt.dataonly_config_key(), + ) .await?; let hotspot_key = helium_crypto::PublicKey::from_bytes(&add_tx.gateway)?; let entity_key = hotspot_key.as_entity_key(); - let accounts = mk_issue_accounts(config_account, program.payer(), &entity_key); + let accounts = mk_accounts(config_account, keypair.pubkey(), &entity_key); - let ix = program - .request() - .compute_budget(300_000) - .compute_price(priority_fee::get_estimate(&solana_client, &accounts).await?) - .args(helium_entity_manager::instruction::IssueDataOnlyEntityV0 { + let issue_ix = Instruction { + program_id: helium_entity_manager::id(), + accounts: accounts.to_account_metas(None), + data: helium_entity_manager::instruction::IssueDataOnlyEntityV0 { _args: helium_entity_manager::IssueDataOnlyEntityArgsV0 { entity_key }, - }) - .accounts(accounts) - .instructions()?; + } + .data(), + }; - let mut tx = - solana_sdk::transaction::Transaction::new_with_payer(&ix, Some(&keypair.pubkey())); - let blockhash = program.rpc().get_latest_blockhash()?; + let ixs = &[ + priority_fee::compute_budget_instruction(300_000), + priority_fee::compute_price_instruction_for_accounts(client, &accounts).await?, + issue_ix, + ]; + let mut tx = Transaction::new_with_payer(ixs, Some(&keypair.pubkey())); - tx.try_partial_sign(&[&*keypair], blockhash)?; + let blockhash = AsRef::::as_ref(client) + .get_latest_blockhash() + .await?; + tx.try_partial_sign(&[keypair], blockhash)?; let sig = add_tx.gateway_signature.clone(); add_tx.gateway_signature = vec![]; @@ -654,8 +656,8 @@ pub mod dataonly { verifier: &str, msg: &[u8], signature: &[u8], - tx: solana_sdk::transaction::Transaction, - ) -> Result { + tx: Transaction, + ) -> Result { #[derive(Deserialize, Serialize, Default)] struct VerifyRequest<'a> { // hex encoded solana transaction @@ -670,8 +672,7 @@ pub mod dataonly { // hex encoded solana transaction pub transaction: String, } - - let client = Settings::mk_rest_client()?; + let client = reqwest::Client::new(); let serialized_tx = hex::encode(bincode::serialize(&tx).map_err(EncodeError::from)?); let response = client .post(format!("{}/verify", verifier)) @@ -726,7 +727,7 @@ pub struct HotspotPage { } impl HotspotPage { - pub async fn from_asset_page(asset_page: asset::AssetPage) -> Result { + pub async fn from_asset_page(asset_page: asset::AssetPage) -> Result { let kta_keys: Vec = asset_page .items .iter() @@ -773,7 +774,7 @@ impl Hotspot { } } - pub async fn from_asset(asset: asset::Asset) -> Result { + pub async fn from_asset(asset: asset::Asset) -> Result { let kta = asset.get_kta().await?; Self::from_asset_with_kta(kta, asset) } @@ -781,7 +782,7 @@ impl Hotspot { pub fn from_asset_with_kta( kta: helium_entity_manager::KeyToAssetV0, asset: asset::Asset, - ) -> Result { + ) -> Result { let hotspot_key = key_from_kta(kta)?; Ok(Self::with_hotspot_key(hotspot_key, asset.ownership.owner)) } @@ -827,14 +828,14 @@ impl From for u64 { impl TryFrom for HotspotLocation { type Error = h3o::error::InvalidCellIndex; - fn try_from(value: u64) -> StdResult { + fn try_from(value: u64) -> Result { h3o::CellIndex::try_from(value).map(Into::into) } } impl FromStr for HotspotLocation { type Err = h3o::error::InvalidCellIndex; - fn from_str(s: &str) -> StdResult { + fn from_str(s: &str) -> Result { s.parse::().map(Into::into) } } @@ -966,7 +967,7 @@ impl HotspotInfoUpdate { self } - pub fn set_geo(self, lat: Option, lon: Option) -> StdResult { + pub fn set_geo(self, lat: Option, lon: Option) -> Result { let location: Option = match (lat, lon) { (Some(lat), Some(lon)) => Some( h3o::LatLng::new(lat, lon) diff --git a/helium-lib/src/keypair.rs b/helium-lib/src/keypair.rs index a9806fc3..f328c949 100644 --- a/helium-lib/src/keypair.rs +++ b/helium-lib/src/keypair.rs @@ -1,13 +1,16 @@ -use crate::result::{DecodeError, Result}; -use solana_sdk::signature::{Signer, SignerError}; +use crate::{ + error::{DecodeError, Error}, + solana_sdk::signature::SignerError, +}; use std::sync::Arc; #[derive(PartialEq, Debug)] pub struct Keypair(solana_sdk::signer::keypair::Keypair); +#[derive(Debug, Clone)] pub struct VoidKeypair; pub use solana_sdk::pubkey; -pub use solana_sdk::{pubkey::Pubkey, pubkey::PUBKEY_BYTES, signature::Signature}; +pub use solana_sdk::{pubkey::Pubkey, pubkey::PUBKEY_BYTES, signature::Signature, signer::Signer}; pub mod serde_pubkey { use super::*; @@ -57,17 +60,17 @@ pub mod serde_opt_pubkey { } } -pub fn to_pubkey(key: &helium_crypto::PublicKey) -> Result { +pub fn to_pubkey(key: &helium_crypto::PublicKey) -> Result { match key.key_type() { helium_crypto::KeyType::Ed25519 => { let bytes = key.to_vec(); Ok(Pubkey::try_from(&bytes[1..]).map_err(DecodeError::from)?) } - _ => Err(DecodeError::other("unsupported key type").into()), + _ => Err(DecodeError::other("unsupported key type")), } } -pub fn to_helium_pubkey(key: &Pubkey) -> Result { +pub fn to_helium_pubkey(key: &Pubkey) -> Result { use helium_crypto::ReadFrom; let mut input = std::io::Cursor::new(key.as_ref()); let helium_key = helium_crypto::ed25519::PublicKey::read_from(&mut input)?; @@ -96,22 +99,6 @@ impl TryFrom<&[u8; 64]> for Keypair { } } -pub trait GetPubkey { - fn pubkey(&self) -> solana_sdk::pubkey::Pubkey; -} - -impl GetPubkey for Keypair { - fn pubkey(&self) -> solana_sdk::pubkey::Pubkey { - self.0.pubkey() - } -} - -impl GetPubkey for Arc { - fn pubkey(&self) -> solana_sdk::pubkey::Pubkey { - self.0.pubkey() - } -} - impl Keypair { pub fn generate() -> Self { Keypair(solana_sdk::signer::keypair::Keypair::new()) @@ -121,7 +108,7 @@ impl Keypair { Arc::new(VoidKeypair) } - pub fn generate_from_entropy(entropy: &[u8]) -> Result { + pub fn generate_from_entropy(entropy: &[u8]) -> Result { Ok(Keypair( solana_sdk::signer::keypair::keypair_from_seed(entropy) .map_err(|e| DecodeError::other(format!("invalid entropy: {e}")))?, @@ -130,11 +117,11 @@ impl Keypair { pub fn secret(&self) -> Vec { let mut result = self.0.secret().to_bytes().to_vec(); - result.extend_from_slice(GetPubkey::pubkey(self).as_ref()); + result.extend_from_slice(self.pubkey().as_ref()); result } - pub fn sign(&self, msg: &[u8]) -> Result { + pub fn sign(&self, msg: &[u8]) -> Result { Ok(self.try_sign_message(msg)?) } @@ -142,13 +129,13 @@ impl Keypair { /// This function is implemented here to avoid passing the secret between /// too many modules. #[cfg(feature = "mnemonic")] - pub fn phrase(&self) -> Result { + pub fn phrase(&self) -> Result { let words = helium_mnemonic::entropy_to_mnemonic(self.0.secret().as_bytes())?; Ok(words.join(" ")) } #[cfg(feature = "mnemonic")] - pub fn from_words(words: Vec) -> Result> { + pub fn from_words(words: Vec) -> Result, Error> { let entropy_bytes = helium_mnemonic::mnemonic_to_entropy(words)?; let keypair = solana_sdk::signer::keypair::keypair_from_seed(&entropy_bytes) .map_err(|_| DecodeError::other("invalid words"))?; @@ -157,7 +144,7 @@ impl Keypair { } impl VoidKeypair { - pub fn sign(&self, msg: &[u8]) -> Result { + pub fn sign(&self, msg: &[u8]) -> Result { Ok(self.try_sign_message(msg)?) } @@ -171,10 +158,7 @@ impl Signer for VoidKeypair { Err(Self::void_signer_error()) } - fn try_sign_message( - &self, - _message: &[u8], - ) -> std::result::Result { + fn try_sign_message(&self, _message: &[u8]) -> std::result::Result { Err(Self::void_signer_error()) } diff --git a/helium-lib/src/kta.rs b/helium-lib/src/kta.rs index 26cb9ab0..49610834 100644 --- a/helium-lib/src/kta.rs +++ b/helium-lib/src/kta.rs @@ -1,34 +1,31 @@ use crate::{ - dao::Dao, - entity_key::AsEntityKey, - keypair::{Keypair, Pubkey, VoidKeypair}, - result::{Error, Result}, - settings::Settings, + anchor_lang::AccountDeserialize, client::SolanaRpcClient, dao::Dao, entity_key::AsEntityKey, + error::Error, helium_entity_manager::KeyToAssetV0, keypair::Pubkey, + solana_sdk::account::Account, }; -use anchor_client::anchor_lang::AccountDeserialize; -use helium_anchor_gen::helium_entity_manager::{self, KeyToAssetV0}; +use futures::{stream, StreamExt, TryFutureExt, TryStreamExt}; use itertools::Itertools; use std::{ collections::HashMap, sync::{Arc, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, }; -pub fn init(settings: &Settings) -> Result<()> { - let _ = CACHE.set(KtaCache::new(settings)?); +pub fn init(solana_client: Arc) -> Result<(), Error> { + let _ = CACHE.set(KtaCache::new(solana_client)?); Ok(()) } -pub async fn get(kta_key: &Pubkey) -> Result { +pub async fn get(kta_key: &Pubkey) -> Result { let cache = CACHE.get().ok_or_else(Error::account_not_found)?; cache.get(kta_key).await } -pub async fn get_many(kta_keys: &[Pubkey]) -> Result> { +pub async fn get_many(kta_keys: &[Pubkey]) -> Result, Error> { let cache = CACHE.get().ok_or_else(Error::account_not_found)?; cache.get_many(kta_keys).await } -pub async fn for_entity_key(entity_key: &E) -> Result +pub async fn for_entity_key(entity_key: &E) -> Result where E: AsEntityKey, { @@ -40,16 +37,17 @@ static CACHE: OnceLock = OnceLock::new(); type KtaCacheMap = HashMap; struct KtaCache { - program: anchor_client::Program>, + solana_client: Arc, cache: RwLock, } impl KtaCache { - fn new(settings: &Settings) -> Result { - let anchor_client = settings.mk_anchor_client(Keypair::void())?; - let program = anchor_client.program(helium_entity_manager::id())?; + fn new(solana_client: Arc) -> Result { let cache = RwLock::new(KtaCacheMap::new()); - Ok(Self { program, cache }) + Ok(Self { + solana_client, + cache, + }) } fn cache_read(&self) -> RwLockReadGuard<'_, KtaCacheMap> { @@ -60,14 +58,18 @@ impl KtaCache { self.cache.write().expect("cache write lock poisoned") } - async fn get(&self, kta_key: &Pubkey) -> Result { + async fn get(&self, kta_key: &Pubkey) -> Result { if let Some(account) = self.cache_read().get(kta_key) { return Ok(account.clone()); } let kta = self - .program - .account::(*kta_key) + .solana_client + .get_account(kta_key) + .map_err(Error::from) + .and_then(|acc| async move { + KeyToAssetV0::try_deserialize(&mut acc.data.as_ref()).map_err(Error::from) + }) .await?; // NOTE: Holding lock across an await will not work with std::sync // Since sync::RwLock is much faster than sync options we take the hit @@ -76,10 +78,7 @@ impl KtaCache { Ok(kta) } - async fn get_many( - &self, - kta_keys: &[Pubkey], - ) -> Result> { + async fn get_many(&self, kta_keys: &[Pubkey]) -> Result, Error> { let missing_keys: Vec = { let cache = self.cache_read(); kta_keys @@ -88,11 +87,21 @@ impl KtaCache { .copied() .collect() }; - let mut missing_accounts = self - .program - .async_rpc() - .get_multiple_accounts(&missing_keys) - .await?; + + let mut missing_accounts = stream::iter(missing_keys.clone()) + // Chunk into documented max keys to pass to getMultipleAccounts + .chunks(100) + .map(|key_chunk| async move { + self.solana_client + .get_multiple_accounts(key_chunk.as_slice()) + .await + }) + .buffered(5) + .try_collect::>>>() + .await? + .into_iter() + .flatten() + .collect_vec(); { let mut cache = self.cache_write(); missing_keys @@ -102,14 +111,14 @@ impl KtaCache { let Some(account) = maybe_account.as_mut() else { return Err(Error::account_not_found()); }; - helium_entity_manager::KeyToAssetV0::try_deserialize(&mut account.data.as_ref()) + KeyToAssetV0::try_deserialize(&mut account.data.as_ref()) .map_err(Error::from) .map(|kta| (key, kta)) }) .map_ok(|(key, kta)| { cache.insert(key, kta); }) - .try_collect()?; + .try_collect::<_, (), _>()?; } { let cache = self.cache_read(); diff --git a/helium-lib/src/lib.rs b/helium-lib/src/lib.rs index 8d8e8d53..53c3e1d6 100644 --- a/helium-lib/src/lib.rs +++ b/helium-lib/src/lib.rs @@ -1,21 +1,26 @@ pub mod asset; pub mod b64; +pub mod client; + pub mod dao; pub mod dc; pub mod entity_key; +pub mod error; pub mod hotspot; pub mod keypair; pub mod kta; pub mod onboarding; pub mod priority_fee; pub mod programs; -pub mod result; pub mod reward; -pub mod settings; pub mod token; -pub use anchor_client::{self, solana_client}; -pub use helium_anchor_gen; +pub use anchor_client; +pub use anchor_client::solana_client; +pub use helium_anchor_gen::{ + anchor_lang, circuit_breaker, data_credits, helium_entity_manager, helium_sub_daos, + lazy_distributor, +}; pub use solana_sdk; pub use solana_sdk::bs58; @@ -37,6 +42,8 @@ where value == &T::ZERO } -pub fn init(settings: &settings::Settings) -> result::Result<()> { - kta::init(settings) +use std::sync::Arc; + +pub fn init(solana_client: Arc) -> Result<(), error::Error> { + kta::init(solana_client) } diff --git a/helium-lib/src/priority_fee.rs b/helium-lib/src/priority_fee.rs index 1c21ea23..79d188ab 100644 --- a/helium-lib/src/priority_fee.rs +++ b/helium-lib/src/priority_fee.rs @@ -1,22 +1,22 @@ -use crate::{result::Error, solana_client::nonblocking::rpc_client::RpcClient as SolanaRpcClient}; -use anchor_client::RequestBuilder; -use helium_anchor_gen::anchor_lang::ToAccountMetas; +use crate::{ + anchor_client::RequestBuilder, anchor_lang::ToAccountMetas, client::SolanaRpcClient, + error::Error, solana_sdk::signer::Signer, +}; use itertools::Itertools; -use solana_sdk::signer::Signer; use std::ops::Deref; pub const MAX_RECENT_PRIORITY_FEE_ACCOUNTS: usize = 128; pub const MIN_PRIORITY_FEE: u64 = 1; -pub async fn get_estimate( - client: &SolanaRpcClient, +pub async fn get_estimate>( + client: &C, accounts: &impl ToAccountMetas, ) -> Result { get_estimate_with_min(client, accounts, MIN_PRIORITY_FEE).await } -pub async fn get_estimate_with_min( - client: &SolanaRpcClient, +pub async fn get_estimate_with_min>( + client: &C, accounts: &impl ToAccountMetas, min_priority_fee: u64, ) -> Result { @@ -27,7 +27,10 @@ pub async fn get_estimate_with_min( .unique() .take(MAX_RECENT_PRIORITY_FEE_ACCOUNTS) .collect(); - let recent_fees = client.get_recent_prioritization_fees(&account_keys).await?; + let recent_fees = client + .as_ref() + .get_recent_prioritization_fees(&account_keys) + .await?; let mut max_per_slot = Vec::new(); for (slot, fees) in &recent_fees.into_iter().group_by(|x| x.slot) { let Some(maximum) = fees.map(|x| x.prioritization_fee).max() else { @@ -67,6 +70,14 @@ pub fn compute_price_instruction(priority_fee: u64) -> solana_sdk::instruction:: solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price(priority_fee) } +pub async fn compute_price_instruction_for_accounts>( + client: &C, + accounts: &impl ToAccountMetas, +) -> Result { + let priority_fee = get_estimate(client, accounts).await?; + Ok(compute_price_instruction(priority_fee)) +} + impl<'a, C: Deref + Clone> SetPriorityFees for RequestBuilder<'a, C> { fn compute_budget(self, compute_limit: u32) -> Self { self.instruction(compute_budget_instruction(compute_limit)) diff --git a/helium-lib/src/reward.rs b/helium-lib/src/reward.rs index c9833dec..78c09964 100644 --- a/helium-lib/src/reward.rs +++ b/helium-lib/src/reward.rs @@ -1,26 +1,27 @@ use crate::{ - asset, + anchor_lang::{InstructionData, ToAccountMetas}, + asset, circuit_breaker, + client::{DasClient, GetAnchorAccount, SolanaRpcClient}, dao::SubDao, entity_key::{self, AsEntityKey, KeySerialization}, - keypair::{GetPubkey, Keypair, Pubkey}, + error::{DecodeError, Error}, + helium_entity_manager, + keypair::{Keypair, Pubkey}, kta, + lazy_distributor::{self, OracleConfigV0}, + priority_fee, programs::SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - result::{DecodeError, Error, Result}, - settings::Settings, + solana_sdk::{instruction::Instruction, transaction::Transaction}, token::TokenAmount, }; use futures::{ stream::{self, StreamExt, TryStreamExt}, TryFutureExt, }; -use helium_anchor_gen::{ - circuit_breaker, helium_entity_manager, - lazy_distributor::{self, OracleConfigV0}, -}; +use itertools::Itertools; use serde::{Deserialize, Serialize}; -use solana_program::{instruction::Instruction, system_program}; use solana_sdk::signer::Signer; -use std::{collections::HashMap, ops::Deref}; +use std::collections::HashMap; #[derive(Debug, Serialize, Clone)] pub struct OracleReward { @@ -30,16 +31,13 @@ pub struct OracleReward { reward: TokenAmount, } -pub async fn lazy_distributor( - settings: &Settings, +pub async fn lazy_distributor( + client: &C, subdao: &SubDao, -) -> Result { - let client = settings.mk_anchor_client(Keypair::void())?; - let ld_program = client.program(lazy_distributor::id())?; - let ld_account = ld_program - .account::(subdao.lazy_distributor()) - .await?; - Ok(ld_account) +) -> Result { + client + .anchor_account::(&subdao.lazy_distributor()) + .await } pub fn lazy_distributor_circuit_breaker( @@ -55,121 +53,116 @@ pub fn lazy_distributor_circuit_breaker( circuit_breaker } -pub const MAX_CLAIM_BONES: u64 = 207020547945205; - -pub async fn claim, E>( - settings: &Settings, - subdao: &SubDao, - entity_key_string: &str, - entity_key_encoding: KeySerialization, - keypair: C, -) -> Result -where - E: AsEntityKey, -{ - let entity_key = entity_key::from_string(entity_key_string.to_string(), entity_key_encoding)?; - let rewards = current(settings, subdao, &entity_key).await?; - let pending = pending( - settings, - subdao, - &[entity_key_string.to_string()], - entity_key_encoding, - ) - .await?; - - let oracle_reward = pending.get(entity_key_string).ok_or_else(|| { - DecodeError::other(format!( - "entity key: {entity_key_string} has no pending rewards" - )) - })?; - - let client = settings.mk_anchor_client(keypair.clone())?; - let program = client.program(lazy_distributor::id())?; - let kta = kta::for_entity_key(&entity_key).await?; - let ld_account = lazy_distributor(settings, subdao).await?; - - let mut ixs: Vec = rewards - .into_iter() - .map(|reward| { - let _args = lazy_distributor::SetCurrentRewardsArgsV0 { - current_rewards: reward.reward.amount, - oracle_index: reward.index, - }; - let accounts = lazy_distributor::accounts::SetCurrentRewardsV0 { - lazy_distributor: subdao.lazy_distributor(), - payer: program.payer(), - recipient: subdao.receipient_key_from_kta(&kta), - oracle: oracle_reward.oracle, - system_program: solana_sdk::system_program::id(), - }; - program - .request() - .args(lazy_distributor::instruction::SetCurrentRewardsV0 { _args }) - .accounts(accounts) - .instructions() - .map_err(Error::from) - }) - .collect::>>>()? - .into_iter() - .flatten() - .collect(); - - let (asset, asset_proof) = asset::for_kta_with_proof(settings, &kta).await?; - - let _args = lazy_distributor::DistributeCompressionRewardsArgsV0 { - data_hash: asset.compression.data_hash, - creator_hash: asset.compression.creator_hash, - root: asset_proof.root.to_bytes(), - index: asset.compression.leaf_id()?, - }; - let accounts = lazy_distributor::accounts::DistributeCompressionRewardsV0 { - DistributeCompressionRewardsV0common: - lazy_distributor::accounts::DistributeCompressionRewardsV0Common { - payer: program.payer(), - lazy_distributor: lazy_distributor::id(), - associated_token_program: spl_associated_token_account::id(), - rewards_mint: *subdao.mint(), - rewards_escrow: ld_account.rewards_escrow, - system_program: system_program::ID, - token_program: anchor_spl::token::ID, - circuit_breaker_program: circuit_breaker::id(), - owner: asset.ownership.owner, - circuit_breaker: lazy_distributor_circuit_breaker(&ld_account), - recipient: subdao.receipient_key_from_kta(&kta), - destination_account: subdao - .token() - .associated_token_adress(&asset.ownership.owner), - }, - compression_program: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - merkle_tree: asset.compression.tree, - token_program: anchor_spl::token::ID, - }; - let mut distribute_ixs = program - .request() - .args(lazy_distributor::instruction::DistributeCompressionRewardsV0 { _args }) - .accounts(accounts) - .instructions()?; - - distribute_ixs[0] - .accounts - .extend_from_slice(&asset_proof.proof(Some(3))?); - ixs.push(distribute_ixs[0].clone()); - - let mut tx = solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&program.payer())); - - let blockhash = program.rpc().get_latest_blockhash()?; - tx.try_sign(&[&*keypair], blockhash)?; - - Ok(tx) -} - -pub async fn current( - settings: &Settings, +// pub const MAX_CLAIM_BONES: u64 = 207020547945205; + +// pub async fn claim + GetAnchorAccount>( +// client: &C, +// subdao: &SubDao, +// entity_key_string: &str, +// entity_key_encoding: KeySerialization, +// keypair: &Keypair, +// ) -> Result { +// let entity_key = entity_key::from_string(entity_key_string.to_string(), entity_key_encoding)?; +// let rewards = current(client, subdao, &entity_key).await?; +// let pending = pending( +// client, +// subdao, +// &[entity_key_string.to_string()], +// entity_key_encoding, +// ) +// .await?; + +// let oracle_reward = pending.get(entity_key_string).ok_or_else(|| { +// DecodeError::other(format!( +// "entity key: {entity_key_string} has no pending rewards" +// )) +// })?; + +// let kta = kta::for_entity_key(&entity_key).await?; +// let ld_account = lazy_distributor(client, subdao).await?; + +// let mut ixs: Vec = rewards +// .into_iter() +// .map(|reward| { +// let _args = lazy_distributor::SetCurrentRewardsArgsV0 { +// current_rewards: reward.reward.amount, +// oracle_index: reward.index, +// }; +// let accounts = lazy_distributor::accounts::SetCurrentRewardsV0 { +// lazy_distributor: subdao.lazy_distributor(), +// payer: keypair.pubkey(), +// recipient: subdao.receipient_key_from_kta(&kta), +// oracle: oracle_reward.oracle, +// system_program: solana_sdk::system_program::id(), +// }; +// program +// .request() +// .args(lazy_distributor::instruction::SetCurrentRewardsV0 { _args }) +// .accounts(accounts) +// .instructions() +// .map_err(Error::from) +// }) +// .collect::>>>()? +// .into_iter() +// .flatten() +// .collect(); + +// let (asset, asset_proof) = asset::for_kta_with_proof(client, &kta).await?; + +// let _args = lazy_distributor::DistributeCompressionRewardsArgsV0 { +// data_hash: asset.compression.data_hash, +// creator_hash: asset.compression.creator_hash, +// root: asset_proof.root.to_bytes(), +// index: asset.compression.leaf_id()?, +// }; +// let accounts = lazy_distributor::accounts::DistributeCompressionRewardsV0 { +// DistributeCompressionRewardsV0common: +// lazy_distributor::accounts::DistributeCompressionRewardsV0Common { +// payer: program.payer(), +// lazy_distributor: lazy_distributor::id(), +// associated_token_program: spl_associated_token_account::id(), +// rewards_mint: *subdao.mint(), +// rewards_escrow: ld_account.rewards_escrow, +// system_program: system_program::ID, +// token_program: anchor_spl::token::ID, +// circuit_breaker_program: circuit_breaker::id(), +// owner: asset.ownership.owner, +// circuit_breaker: lazy_distributor_circuit_breaker(&ld_account), +// recipient: subdao.receipient_key_from_kta(&kta), +// destination_account: subdao +// .token() +// .associated_token_adress(&asset.ownership.owner), +// }, +// compression_program: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, +// merkle_tree: asset.compression.tree, +// token_program: anchor_spl::token::ID, +// }; +// let mut distribute_ixs = program +// .request() +// .args(lazy_distributor::instruction::DistributeCompressionRewardsV0 { _args }) +// .accounts(accounts) +// .instructions()?; + +// distribute_ixs[0] +// .accounts +// .extend_from_slice(&asset_proof.proof(Some(3))?); +// ixs.push(distribute_ixs[0].clone()); + +// let mut tx = solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&program.payer())); + +// let blockhash = program.rpc().get_latest_blockhash()?; +// tx.try_sign(&[&*keypair], blockhash)?; + +// Ok(tx) +// } + +pub async fn current + GetAnchorAccount>( + client: &C, subdao: &SubDao, entity_key: &E, -) -> Result> { - let ld_account = lazy_distributor(settings, subdao).await?; - let asset = asset::for_entity_key(settings, entity_key).await?; +) -> Result, Error> { + let ld_account = lazy_distributor(client, subdao).await?; + let asset = asset::for_entity_key(client, entity_key).await?; stream::iter( ld_account .oracles @@ -195,13 +188,13 @@ async fn current_from_oracle( subdao: &SubDao, oracle: &str, asset_id: &Pubkey, -) -> Result { +) -> Result { #[derive(Debug, Deserialize)] struct OracleRewardsResponse { #[serde(rename = "currentRewards")] current_rewards: serde_json::Value, } - let client = Settings::mk_rest_client()?; + let client = reqwest::Client::new(); let response = client .get(format!("{oracle}?assetId={asset_id}")) .send() @@ -211,12 +204,12 @@ async fn current_from_oracle( value_to_token_amount(subdao, response.current_rewards) } -pub async fn pending( - settings: &Settings, +pub async fn pending( + client: &C, subdao: &SubDao, entity_key_strings: &[String], entity_key_encoding: KeySerialization, -) -> Result> { +) -> Result, Error> { fn for_entity_key( bulk_rewards: &HashMap>, entity_key_string: &str, @@ -227,15 +220,13 @@ pub async fn pending( Some(sorted_oracle_rewards[sorted_oracle_rewards.len() / 2].to_owned()) } - let bulk_rewards = bulk(settings, subdao, entity_key_strings).await?; + let bulk_rewards = bulk(client, subdao, entity_key_strings).await?; let entity_key_rewards = stream::iter(entity_key_strings) .map(Ok::<&String, Error>) .and_then(|entity_key_string| async { - let entity_key = - entity_key::from_string(entity_key_string.clone(), entity_key_encoding)?; - let client = settings.mk_anchor_client(Keypair::void())?; + let entity_key = entity_key::from_str(&entity_key_string.clone(), entity_key_encoding)?; let kta = kta::for_entity_key(&entity_key).await?; - recipient::for_kta(&client, subdao, &kta) + recipient::for_kta(client, subdao, &kta) .and_then(|maybe_recipient| async move { maybe_recipient.ok_or_else(Error::account_not_found) }) @@ -259,12 +250,12 @@ pub async fn pending( Ok(entity_key_rewards) } -pub async fn bulk( - settings: &Settings, +pub async fn bulk( + client: &C, subdao: &SubDao, entity_keys: &[String], -) -> Result>> { - let ld_account = lazy_distributor(settings, subdao).await?; +) -> Result>, Error> { + let ld_account = lazy_distributor(client, subdao).await?; stream::iter(ld_account.oracles) .enumerate() .map(Ok) @@ -293,7 +284,7 @@ async fn bulk_from_oracle( subdao: &SubDao, oracle: &str, entity_keys: &[String], -) -> Result> { +) -> Result, Error> { #[derive(Debug, Serialize, Deserialize)] struct OracleBulkRewardRequest { #[serde(rename = "entityKeys")] @@ -306,7 +297,7 @@ async fn bulk_from_oracle( current_rewards: HashMap, } - let client = Settings::mk_rest_client()?; + let client = reqwest::Client::new(); let oracle_rewards_response = client .post(format!("{oracle}/bulk-rewards")) .json(&OracleBulkRewardRequest { @@ -322,84 +313,88 @@ async fn bulk_from_oracle( .map(|(entity_key_string, value)| { value_to_token_amount(subdao, value).map(|amount| (entity_key_string, amount)) }) - .collect::>>() - .into_iter() - .collect() + .try_collect() } pub mod recipient { use super::*; - pub async fn for_kta>( - client: &anchor_client::Client, + pub async fn for_kta( + client: &C, subdao: &SubDao, kta: &helium_entity_manager::KeyToAssetV0, - ) -> Result> { - let program = client.program(lazy_distributor::id())?; + ) -> Result, Error> { let recipient_key = subdao.receipient_key_from_kta(kta); - match program - .account::(recipient_key) - .await - { - Ok(receipient) => Ok(Some(receipient)), - Err(anchor_client::ClientError::AccountNotFound) => Ok(None), - Err(err) => Err(err.into()), - } + Ok(client.anchor_account(&recipient_key).await.ok()) } - pub async fn init + GetPubkey, E>( - settings: &Settings, + pub async fn init + AsRef>( + client: &C, subdao: &SubDao, entity_key: &E, - keypair: C, - ) -> Result - where - E: AsEntityKey, - { - let client = settings.mk_anchor_client(keypair.clone())?; - let program = client.program(lazy_distributor::id())?; - let kta = kta::for_entity_key(entity_key).await?; - let (asset, asset_proof) = asset::for_kta_with_proof(settings, &kta).await?; - - let _args = lazy_distributor::InitializeCompressionRecipientArgsV0 { - data_hash: asset.compression.data_hash, - creator_hash: asset.compression.creator_hash, - root: asset_proof.root.to_bytes(), - index: asset.compression.leaf_id()?, - }; + keypair: &Keypair, + ) -> Result { + fn mk_accounts( + payer: Pubkey, + owner: Pubkey, + tree: Pubkey, + subdao: &SubDao, + kta: &helium_entity_manager::KeyToAssetV0, + ) -> impl ToAccountMetas { + lazy_distributor::accounts::InitializeCompressionRecipientV0 { + payer, + lazy_distributor: subdao.lazy_distributor(), + recipient: subdao.receipient_key_from_kta(kta), + merkle_tree: tree, + owner, + delegate: owner, + compression_program: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + system_program: solana_sdk::system_program::id(), + } + } - let accounts = lazy_distributor::accounts::InitializeCompressionRecipientV0 { - payer: program.payer(), - lazy_distributor: subdao.lazy_distributor(), - recipient: subdao.receipient_key_from_kta(&kta), - merkle_tree: asset.compression.tree, - owner: asset.ownership.owner, - delegate: asset.ownership.owner, - compression_program: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - system_program: solana_sdk::system_program::id(), + let kta = kta::for_entity_key(entity_key).await?; + let (asset, asset_proof) = asset::for_kta_with_proof(client, &kta).await?; + let mut accounts = mk_accounts( + keypair.pubkey(), + asset.ownership.owner, + asset.compression.tree, + subdao, + &kta, + ) + .to_account_metas(None); + accounts.extend_from_slice(&asset_proof.proof(Some(3))?); + + let init_ix = Instruction { + program_id: lazy_distributor::id(), + accounts: accounts.to_account_metas(None), + data: lazy_distributor::instruction::InitializeCompressionRecipientV0 { + _args: lazy_distributor::InitializeCompressionRecipientArgsV0 { + data_hash: asset.compression.data_hash, + creator_hash: asset.compression.creator_hash, + root: asset_proof.root.to_bytes(), + index: asset.compression.leaf_id()?, + }, + } + .data(), }; - let mut ixs = program - .request() - .args(lazy_distributor::instruction::InitializeCompressionRecipientV0 { _args }) - .accounts(accounts) - .instructions()?; - - ixs[0] - .accounts - .extend_from_slice(&asset_proof.proof(Some(3))?); - - let mut tx = - solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&keypair.pubkey())); - let blockhash = program.rpc().get_latest_blockhash()?; - - tx.try_sign(&[&*keypair], blockhash)?; - + let ixs = &[ + priority_fee::compute_budget_instruction(270_000), + priority_fee::compute_price_instruction_for_accounts(client, &init_ix.accounts).await?, + init_ix, + ]; + + let blockhash = AsRef::::as_ref(client) + .get_latest_blockhash() + .await?; + let tx = + Transaction::new_signed_with_payer(ixs, Some(&keypair.pubkey()), &[keypair], blockhash); Ok(tx) } } -fn value_to_token_amount(subdao: &SubDao, value: serde_json::Value) -> Result { +fn value_to_token_amount(subdao: &SubDao, value: serde_json::Value) -> Result { let value = match value { serde_json::Value::String(s) => s .parse::() diff --git a/helium-lib/src/token.rs b/helium-lib/src/token.rs index 5b942e95..1e350f5a 100644 --- a/helium-lib/src/token.rs +++ b/helium-lib/src/token.rs @@ -1,13 +1,16 @@ use crate::{ - keypair::{serde_pubkey, GetPubkey, Pubkey}, - result::{DecodeError, Result}, - settings::Settings, + client::SolanaRpcClient, + error::{DecodeError, Error}, + keypair::{serde_pubkey, Keypair, Pubkey}, + solana_sdk::{ + commitment_config::CommitmentConfig, signer::Signer, system_instruction, + transaction::Transaction, + }, }; use chrono::{DateTime, Utc}; use futures::stream::{self, StreamExt, TryStreamExt}; use helium_anchor_gen::circuit_breaker; -use solana_sdk::{signer::Signer, system_instruction}; -use std::{collections::HashMap, ops::Deref, result::Result as StdResult, str::FromStr}; +use std::{collections::HashMap, result::Result as StdResult, str::FromStr}; #[derive(Debug, thiserror::Error)] pub enum TokenError { @@ -32,37 +35,33 @@ lazy_static::lazy_static! { static ref SOL_MINT: Pubkey = solana_sdk::system_program::ID; } -pub async fn transfer + GetPubkey>( - settings: &Settings, +pub async fn transfer>( + client: &C, transfers: &[(Pubkey, TokenAmount)], - keypair: C, -) -> Result { - let client = settings.mk_anchor_client(keypair.clone())?; - let spl_program = client.program(anchor_spl::token::spl_token::id())?; - + keypair: &Keypair, +) -> Result { let wallet_public_key = keypair.pubkey(); - let mut builder = spl_program.request(); + let mut ixs = vec![]; for (payee, token_amount) in transfers { match token_amount.token.mint() { spl_mint if spl_mint == Token::Sol.mint() => { let ix = system_instruction::transfer(&wallet_public_key, payee, token_amount.amount); - builder = builder.instruction(ix); + ixs.push(ix); } spl_mint => { let source_pubkey = token_amount .token .associated_token_adress(&wallet_public_key); let destination_pubkey = token_amount.token.associated_token_adress(payee); - let ix = - spl_associated_token_account::instruction::create_associated_token_account_idempotent( - &wallet_public_key, - payee, - spl_mint, - &anchor_spl::token::spl_token::id(), - ); - builder = builder.instruction(ix); + let ix = spl_associated_token_account::instruction::create_associated_token_account_idempotent( + &wallet_public_key, + payee, + spl_mint, + &anchor_spl::token::spl_token::id(), + ); + ixs.push(ix); let ix = anchor_spl::token::spl_token::instruction::transfer_checked( &anchor_spl::token::spl_token::id(), @@ -74,23 +73,24 @@ pub async fn transfer + GetPubkey>( token_amount.amount, token_amount.token.decimals(), )?; - builder = builder.instruction(ix); + ixs.push(ix); } } } - let tx = builder.signed_transaction().await?; + let blockhash = client.as_ref().get_latest_blockhash().await?; + let tx = + Transaction::new_signed_with_payer(&ixs, Some(&wallet_public_key), &[keypair], blockhash); Ok(tx) } -pub async fn balance_for_address( - settings: &Settings, +pub async fn balance_for_address>( + client: &C, pubkey: &Pubkey, -) -> Result> { - let client = settings.mk_solana_client()?; - +) -> Result, Error> { match client - .get_account_with_commitment(pubkey, client.commitment()) + .as_ref() + .get_account_with_commitment(pubkey, CommitmentConfig::confirmed()) .await? .value { @@ -109,12 +109,12 @@ pub async fn balance_for_address( } } -pub async fn balance_for_addresses( - settings: &Settings, +pub async fn balance_for_addresses>( + client: &C, pubkeys: &[Pubkey], -) -> Result> { +) -> Result, Error> { stream::iter(pubkeys) - .map(|pubkey| balance_for_address(settings, pubkey)) + .map(|pubkey| balance_for_address(client, pubkey)) .buffered(10) .filter_map(|result| async { result.transpose() }) .try_collect() @@ -123,7 +123,6 @@ pub async fn balance_for_addresses( pub mod price { use super::*; - use crate::solana_client::nonblocking::rpc_client::RpcClient as SolanaRpcClient; use pyth_solana_receiver_sdk::price_update::{self, PriceUpdateV2}; use rust_decimal::prelude::*; @@ -153,17 +152,17 @@ pub mod price { pub token: super::Token, } - pub fn feed_from_hex(str: &str) -> Result { + pub fn feed_from_hex(str: &str) -> Result { let feed_id = price_update::get_feed_id_from_hex(str).map_err(|_| PriceError::InvalidFeed)?; Ok(feed_id) } - pub async fn get(solana_client: &SolanaRpcClient, token: Token) -> Result { + pub async fn get>(client: &C, token: Token) -> Result { use helium_anchor_gen::anchor_lang::AccountDeserialize; let price_key = token.price_key().ok_or(PriceError::InvalidToken(token))?; let price_feed = token.price_feed().ok_or(PriceError::InvalidToken(token))?; - let account = solana_client.get_account(price_key).await?; + let account = client.as_ref().get_account(price_key).await?; let PriceUpdateV2 { price_message, .. } = PriceUpdateV2::try_deserialize(&mut account.data.as_slice())?; diff --git a/helium-wallet/src/cmd/balance.rs b/helium-wallet/src/cmd/balance.rs index d7503298..c78ffce1 100644 --- a/helium-wallet/src/cmd/balance.rs +++ b/helium-wallet/src/cmd/balance.rs @@ -19,10 +19,10 @@ impl Cmd { let wallet = opts.load_wallet()?; wallet.public_key }; - let settings = opts.try_into()?; + let client = opts.client()?; let balances = - token::balance_for_addresses(&settings, &Token::associated_token_adresses(&address)) + token::balance_for_addresses(&client, &Token::associated_token_adresses(&address)) .await?; let json = json!({ "address": address.to_string(), diff --git a/helium-wallet/src/cmd/dc/burn.rs b/helium-wallet/src/cmd/dc/burn.rs index 91c9368e..af2b82b4 100644 --- a/helium-wallet/src/cmd/dc/burn.rs +++ b/helium-wallet/src/cmd/dc/burn.rs @@ -16,9 +16,9 @@ impl Cmd { pub async fn run(&self, opts: Opts) -> Result { let password = get_wallet_password(false)?; let keypair = opts.load_keypair(password.as_bytes())?; - let settings = opts.try_into()?; + let client = opts.client()?; - let tx = dc::burn(&settings, self.dc, keypair).await?; - print_json(&self.commit.maybe_commit(&tx, &settings).await?.to_json()) + let tx = dc::burn(&client, self.dc, &keypair).await?; + print_json(&self.commit.maybe_commit(&tx, &client).await?.to_json()) } } diff --git a/helium-wallet/src/cmd/dc/delegate.rs b/helium-wallet/src/cmd/dc/delegate.rs index 25eebacc..75e925b4 100644 --- a/helium-wallet/src/cmd/dc/delegate.rs +++ b/helium-wallet/src/cmd/dc/delegate.rs @@ -22,9 +22,9 @@ impl Cmd { pub async fn run(&self, opts: Opts) -> Result { let password = get_wallet_password(false)?; let keypair = opts.load_keypair(password.as_bytes())?; - let settings = opts.try_into()?; - let tx = dc::delegate(&settings, self.subdao, &self.payer, self.dc, keypair).await?; - print_json(&self.commit.maybe_commit(&tx, &settings).await?.to_json()) + let client = opts.client()?; + let tx = dc::delegate(&client, self.subdao, &self.payer, self.dc, &keypair).await?; + print_json(&self.commit.maybe_commit(&tx, &client).await?.to_json()) } } diff --git a/helium-wallet/src/cmd/dc/mint.rs b/helium-wallet/src/cmd/dc/mint.rs index 440111f4..320dcfa0 100644 --- a/helium-wallet/src/cmd/dc/mint.rs +++ b/helium-wallet/src/cmd/dc/mint.rs @@ -33,8 +33,8 @@ impl Cmd { pub async fn run(&self, opts: Opts) -> Result { let password = get_wallet_password(false)?; let wallet = opts.load_wallet()?; - let settings = opts.try_into()?; + let client = opts.client()?; let payee = self.payee.as_ref().unwrap_or(&wallet.public_key); let amount = match (self.hnt, self.dc) { (Some(hnt), None) => TokenAmount::from_f64(Token::Hnt, hnt), @@ -43,7 +43,7 @@ impl Cmd { }; let keypair = wallet.decrypt(password.as_bytes())?; - let tx = dc::mint(&settings, amount, payee, keypair).await?; - print_json(&self.commit.maybe_commit(&tx, &settings).await?.to_json()) + let tx = dc::mint(&client, amount, payee, &keypair).await?; + print_json(&self.commit.maybe_commit(&tx, &client).await?.to_json()) } } diff --git a/helium-wallet/src/cmd/dc/price.rs b/helium-wallet/src/cmd/dc/price.rs index 09894035..c8c82feb 100644 --- a/helium-wallet/src/cmd/dc/price.rs +++ b/helium-wallet/src/cmd/dc/price.rs @@ -13,9 +13,8 @@ pub struct Cmd { impl Cmd { pub async fn run(&self, opts: Opts) -> Result { - let settings: Settings = opts.try_into()?; - let solana_client = settings.mk_solana_client()?; - let price = token::price::get(&solana_client, Token::Hnt).await?; + let client = opts.client()?; + let price = token::price::get(&client, Token::Hnt).await?; let hnt_price = price.price; let usd_amount = diff --git a/helium-wallet/src/cmd/export.rs b/helium-wallet/src/cmd/export.rs index 0a22d04d..e32dd482 100644 --- a/helium-wallet/src/cmd/export.rs +++ b/helium-wallet/src/cmd/export.rs @@ -1,5 +1,5 @@ use crate::{cmd::*, pwhash::*}; -use helium_lib::keypair::GetPubkey; +use helium_lib::keypair::Signer; use qr2term::print_qr; use serde::{Deserialize, Serialize}; use serde_json::json; diff --git a/helium-wallet/src/cmd/hotspots/add.rs b/helium-wallet/src/cmd/hotspots/add.rs index b468d5b7..f959b199 100644 --- a/helium-wallet/src/cmd/hotspots/add.rs +++ b/helium-wallet/src/cmd/hotspots/add.rs @@ -1,9 +1,9 @@ use crate::{cmd::*, txn_envelope::TxnEnvelope}; use helium_lib::{ asset, + client::{VERIFIER_URL_DEVNET, VERIFIER_URL_MAINNET}, dao::SubDao, hotspot::{self, HotspotInfoUpdate, HotspotMode}, - settings::{VERIFIER_URL_DEVNET, VERIFIER_URL_MAINNET}, }; use helium_proto::BlockchainTxnAddGatewayV1; @@ -83,8 +83,8 @@ impl Cmd { let keypair = opts.load_keypair(password.as_bytes())?; let hotspot_key = helium_crypto::PublicKey::from_bytes(&txn.gateway)?; let verifier_key = self.verifier.as_ref().unwrap_or(&opts.url); - let settings = opts.clone().try_into()?; - let hotspot_issued = asset::for_entity_key(&settings, &hotspot_key).await.is_ok(); + let client = opts.client()?; + let hotspot_issued = asset::for_entity_key(&client, &hotspot_key).await.is_ok(); let verifier = match verifier_key.as_str() { "m" | "mainnet-beta" => VERIFIER_URL_MAINNET, "d" | "devnet" => VERIFIER_URL_DEVNET, @@ -92,9 +92,8 @@ impl Cmd { }; if !hotspot_issued { - let tx = - hotspot::dataonly::issue(&settings, verifier, &mut txn, keypair.clone()).await?; - let _ = self.commit.maybe_commit(&tx, &settings).await?; + let tx = hotspot::dataonly::issue(&client, verifier, &mut txn, &keypair).await?; + let _ = self.commit.maybe_commit(&tx, &client).await?; } // Only assert the hotspot if either (a) it has already been issued before this cli was run or (b) `commit` is enabled, // which means the previous command should have created it. @@ -105,8 +104,8 @@ impl Cmd { .set_gain(self.gain) .set_elevation(self.elevation) .set_geo(self.lat, self.lon)?; - let tx = hotspot::dataonly::onboard(&settings, &hotspot_key, update, keypair).await?; - print_json(&self.commit.maybe_commit(&tx, &settings).await?.to_json()) + let tx = hotspot::dataonly::onboard(&client, &hotspot_key, update, &keypair).await?; + print_json(&self.commit.maybe_commit(&tx, &client).await?.to_json()) } else { Ok(()) } diff --git a/helium-wallet/src/cmd/hotspots/info.rs b/helium-wallet/src/cmd/hotspots/info.rs index e0115ad1..c75f8006 100644 --- a/helium-wallet/src/cmd/hotspots/info.rs +++ b/helium-wallet/src/cmd/hotspots/info.rs @@ -9,8 +9,8 @@ pub struct Cmd { impl Cmd { pub async fn run(&self, opts: Opts) -> Result { - let settings = opts.try_into()?; - let hotspot = hotspot::get_with_info(&settings, &SubDao::all(), &self.address).await?; + let client = opts.client()?; + let hotspot = hotspot::get_with_info(&client, &SubDao::all(), &self.address).await?; print_json(&hotspot) } } diff --git a/helium-wallet/src/cmd/hotspots/list.rs b/helium-wallet/src/cmd/hotspots/list.rs index 5ab8a337..3984f4c9 100644 --- a/helium-wallet/src/cmd/hotspots/list.rs +++ b/helium-wallet/src/cmd/hotspots/list.rs @@ -16,8 +16,8 @@ impl Cmd { let wallet = opts.load_wallet()?; wallet.public_key }; - let settings = opts.try_into()?; - let hotspots = hotspot::for_owner(&settings, &owner).await?; + let client = opts.client()?; + let hotspots = hotspot::for_owner(&client, &owner).await?; let json = json!( { "address": owner.to_string(), "hotspots": hotspots, diff --git a/helium-wallet/src/cmd/hotspots/rewards.rs b/helium-wallet/src/cmd/hotspots/rewards.rs index 090b86b0..c5c46fcc 100644 --- a/helium-wallet/src/cmd/hotspots/rewards.rs +++ b/helium-wallet/src/cmd/hotspots/rewards.rs @@ -40,10 +40,10 @@ pub struct PendingCmd { impl PendingCmd { pub async fn run(&self, opts: Opts) -> Result { - let settings: Settings = opts.try_into()?; + let client = opts.client()?; let entity_key_strings = hotspots_to_entity_key_strings(&self.hotspots); let pending = reward::pending( - &settings, + &client, &self.subdao, &entity_key_strings, KeySerialization::B58, diff --git a/helium-wallet/src/cmd/hotspots/transfer.rs b/helium-wallet/src/cmd/hotspots/transfer.rs index cd1c469d..7080b8e2 100644 --- a/helium-wallet/src/cmd/hotspots/transfer.rs +++ b/helium-wallet/src/cmd/hotspots/transfer.rs @@ -1,7 +1,7 @@ use crate::cmd::*; use helium_lib::{ hotspot, - keypair::{GetPubkey, Pubkey}, + keypair::{Pubkey, Signer}, }; #[derive(Clone, Debug, clap::Args)] @@ -23,8 +23,8 @@ impl Cmd { if keypair.pubkey() == self.recipient { bail!("recipient already owner of hotspot"); } - let settings = opts.clone().try_into()?; - let tx = hotspot::transfer(&settings, &self.address, &self.recipient, keypair).await?; - print_json(&self.commit.maybe_commit(&tx, &settings).await?.to_json()) + let client = opts.client()?; + let tx = hotspot::transfer(&client, &self.address, &self.recipient, &keypair).await?; + print_json(&self.commit.maybe_commit(&tx, &client).await?.to_json()) } } diff --git a/helium-wallet/src/cmd/hotspots/update.rs b/helium-wallet/src/cmd/hotspots/update.rs index 2908a012..002fc14b 100644 --- a/helium-wallet/src/cmd/hotspots/update.rs +++ b/helium-wallet/src/cmd/hotspots/update.rs @@ -1,8 +1,8 @@ use crate::cmd::*; use helium_lib::{ + client::{ONBOARDING_URL_DEVNET, ONBOARDING_URL_MAINNET}, dao::SubDao, hotspot::{self, HotspotInfoUpdate}, - settings::{ONBOARDING_URL_DEVNET, ONBOARDING_URL_MAINNET}, }; #[derive(Debug, Clone, clap::Args)] @@ -64,7 +64,6 @@ impl Cmd { let password = get_wallet_password(false)?; let keypair = opts.load_keypair(password.as_bytes())?; - let settings = opts.clone().try_into()?; let server = self.onboarding.as_ref().map(|value| { match value.as_str() { "m" | "mainnet-beta" => ONBOARDING_URL_MAINNET, @@ -79,8 +78,9 @@ impl Cmd { .set_elevation(self.elevation) .set_geo(self.lat, self.lon)?; - let tx = hotspot::update(&settings, server, &self.gateway, update, keypair).await?; + let client = opts.client()?; + let tx = hotspot::update(&client, server, &self.gateway, update, &keypair).await?; - print_json(&self.commit.maybe_commit(&tx, &settings).await.to_json()) + print_json(&self.commit.maybe_commit(&tx, &client).await.to_json()) } } diff --git a/helium-wallet/src/cmd/hotspots/updates.rs b/helium-wallet/src/cmd/hotspots/updates.rs index 2ce2bdbf..547cb144 100644 --- a/helium-wallet/src/cmd/hotspots/updates.rs +++ b/helium-wallet/src/cmd/hotspots/updates.rs @@ -21,14 +21,14 @@ pub struct Cmd { impl Cmd { pub async fn run(&self, opts: Opts) -> Result { - let settings = opts.try_into()?; + let client = opts.client()?; let params = hotspot::info::HotspotInfoUpdateParams { before: self.before, until: self.until, ..Default::default() }; let info_key = self.subdao.info_key_for_helium_key(&self.address)?; - let txns = hotspot::info::updates(&settings, &info_key, params).await?; + let txns = hotspot::info::updates(&client, &info_key, params).await?; print_json(&txns) } } diff --git a/helium-wallet/src/cmd/mod.rs b/helium-wallet/src/cmd/mod.rs index 81148a27..8840180e 100644 --- a/helium-wallet/src/cmd/mod.rs +++ b/helium-wallet/src/cmd/mod.rs @@ -3,9 +3,8 @@ use crate::{ wallet::Wallet, }; use helium_lib::{ - b64, + b64, client, keypair::Keypair, - settings::Settings, solana_client::{ self, rpc_request::RpcResponseErrorData, rpc_response::RpcSimulateTransactionResult, }, @@ -71,12 +70,9 @@ impl Opts { let wallet = self.load_wallet()?; wallet.decrypt(password) } -} -impl TryFrom for Settings { - type Error = Error; - fn try_from(value: Opts) -> Result { - Ok(Settings::try_from(value.url.as_str())?) + pub fn client(&self) -> Result { + Ok(client::Client::try_from(self.url.as_str())?) } } @@ -88,10 +84,10 @@ pub struct CommitOpts { } impl CommitOpts { - pub async fn maybe_commit( + pub async fn maybe_commit>( &self, tx: &helium_lib::solana_sdk::transaction::Transaction, - settings: &Settings, + client: &C, ) -> Result { fn context_err(client_err: solana_client::client_error::ClientError) -> Error { let mut captured_logs: Option> = None; @@ -116,15 +112,16 @@ impl CommitOpts { mapped } - let client = settings.mk_solana_client()?; if self.commit { client + .as_ref() .send_transaction(tx) .await .map(Into::into) .map_err(context_err) } else { client + .as_ref() .simulate_transaction(tx) .await .map_err(context_err)? diff --git a/helium-wallet/src/cmd/price.rs b/helium-wallet/src/cmd/price.rs index 816fb420..a12635eb 100644 --- a/helium-wallet/src/cmd/price.rs +++ b/helium-wallet/src/cmd/price.rs @@ -11,9 +11,8 @@ pub struct Cmd { impl Cmd { pub async fn run(&self, opts: Opts) -> Result { - let settings: Settings = opts.try_into()?; - let solana_client = settings.mk_solana_client()?; - let price = token::price::get(&solana_client, self.token).await?; + let client = opts.client()?; + let price = token::price::get(&client, self.token).await?; print_json(&price) } diff --git a/helium-wallet/src/cmd/router/balance.rs b/helium-wallet/src/cmd/router/balance.rs index 997fc9f3..7df142fd 100644 --- a/helium-wallet/src/cmd/router/balance.rs +++ b/helium-wallet/src/cmd/router/balance.rs @@ -12,10 +12,10 @@ pub struct Cmd { impl Cmd { pub async fn run(&self, opts: Opts) -> Result { - let settings = opts.try_into()?; let delegated_dc_key = self.subdao.delegated_dc_key(&self.router_key); let escrow_key = self.subdao.escrow_key(&delegated_dc_key); - let balance = token::balance_for_address(&settings, &escrow_key) + let client = opts.client()?; + let balance = token::balance_for_address(&client, &escrow_key) .await? .map(|balance| balance.amount); let json = json!({ diff --git a/helium-wallet/src/cmd/transfer.rs b/helium-wallet/src/cmd/transfer.rs index 38f99b6c..0704e4af 100644 --- a/helium-wallet/src/cmd/transfer.rs +++ b/helium-wallet/src/cmd/transfer.rs @@ -84,11 +84,11 @@ impl PayCmd { let payments = self.collect_payments()?; let password = get_wallet_password(false)?; let keypair = opts.load_keypair(password.as_bytes())?; - let settings = opts.try_into()?; - let tx = token::transfer(&settings, &payments, keypair).await?; + let client = opts.client()?; + let tx = token::transfer(&client, &payments, &keypair).await?; - print_json(&self.commit().maybe_commit(&tx, &settings).await?.to_json()) + print_json(&self.commit().maybe_commit(&tx, &client).await?.to_json()) } fn collect_payments(&self) -> Result> { diff --git a/helium-wallet/src/main.rs b/helium-wallet/src/main.rs index 7b2b7a22..858cda4b 100644 --- a/helium-wallet/src/main.rs +++ b/helium-wallet/src/main.rs @@ -47,7 +47,8 @@ async fn main() -> Result { } async fn run(cli: Cli) -> Result { - helium_lib::init(&cli.opts.clone().try_into()?)?; + let client = cli.opts.client()?; + helium_lib::init(client.solana_client)?; match cli.cmd { Cmd::Info(cmd) => cmd.run(cli.opts).await, Cmd::Balance(cmd) => cmd.run(cli.opts).await, diff --git a/helium-wallet/src/read_write.rs b/helium-wallet/src/read_write.rs index a2dad247..b696ddab 100644 --- a/helium-wallet/src/read_write.rs +++ b/helium-wallet/src/read_write.rs @@ -60,7 +60,7 @@ impl ReadWrite for helium_lib::keypair::Keypair { #[cfg(test)] mod tests { use super::*; - use helium_lib::keypair::{GetPubkey, Keypair, Pubkey}; + use helium_lib::keypair::{Keypair, Pubkey, Signer}; use std::{io::Cursor, str::FromStr}; #[test] diff --git a/helium-wallet/src/wallet.rs b/helium-wallet/src/wallet.rs index b12cc5ac..14bb61f2 100644 --- a/helium-wallet/src/wallet.rs +++ b/helium-wallet/src/wallet.rs @@ -6,7 +6,7 @@ use crate::{ }; use aes_gcm::{aead::generic_array::GenericArray, AeadInPlace, Aes256Gcm, KeyInit}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use helium_lib::keypair::{to_helium_pubkey, GetPubkey, Keypair, Pubkey, PUBKEY_BYTES}; +use helium_lib::keypair::{to_helium_pubkey, Keypair, Pubkey, Signer, PUBKEY_BYTES}; use sodiumoxide::randombytes; use std::io::{self, Cursor}; use std::{ diff --git a/src/traits/mod.rs b/src/traits/mod.rs deleted file mode 100644 index 946a0e99..00000000 --- a/src/traits/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub use self::read_write::ReadWrite; -pub use self::txn_envelope::TxnEnvelope; -// pub use self::txn_sign::TxnSign; - -pub mod read_write; -pub mod txn_envelope; -// pub mod txn_sign; diff --git a/src/traits/txn_sign.rs b/src/traits/txn_sign.rs deleted file mode 100644 index ff6cba83..00000000 --- a/src/traits/txn_sign.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::keypair::{Keypair, PublicKey, Verify}; -use crate::result::Result; -use helium_proto::*; - -pub trait TxnSign: Message + std::clone::Clone { - fn sign(&self, keypair: &Keypair) -> Result> - where - Self: std::marker::Sized; - fn verify(&self, pubkey: &PublicKey, signature: &[u8]) -> Result; -} - -macro_rules! impl_sign { - ($txn_type:ty, $( $sig: ident ),+ ) => { - impl TxnSign for $txn_type { - fn sign(&self, keypair: &Keypair) -> Result> { - let mut buf = vec![]; - let mut txn = self.clone(); - $(txn.$sig = vec![];)+ - txn.encode(& mut buf)?; - keypair.sign(&buf) - } - - fn verify(&self, pubkey: &PublicKey, signature: &[u8]) -> Result { - let mut buf = vec![]; - let mut txn = self.clone(); - $(txn.$sig = vec![];)+ - txn.encode(& mut buf)?; - pubkey.verify(&buf, &signature).map_err(|err| err.into()) - } - } - } -} - -impl_sign!(BlockchainTxnPriceOracleV1, signature); -impl_sign!(BlockchainTxnPaymentV1, signature); -impl_sign!(BlockchainTxnPaymentV2, signature); -impl_sign!(BlockchainTxnCreateHtlcV1, signature); -impl_sign!(BlockchainTxnRedeemHtlcV1, signature); -impl_sign!( - BlockchainTxnAddGatewayV1, - owner_signature, - payer_signature, - gateway_signature -); -impl_sign!( - BlockchainTxnAssertLocationV1, - owner_signature, - payer_signature, - gateway_signature -); -impl_sign!( - BlockchainTxnAssertLocationV2, - owner_signature, - payer_signature -); -impl_sign!(BlockchainTxnOuiV1, owner_signature, payer_signature); -impl_sign!(BlockchainTxnSecurityExchangeV1, signature); -impl_sign!(BlockchainTxnTokenBurnV1, signature); -impl_sign!( - BlockchainTxnVarsV1, - proof, - key_proof, - multi_proofs, - multi_key_proofs -); -impl_sign!( - BlockchainTxnTransferHotspotV1, - buyer_signature, - seller_signature -); -impl_sign!(BlockchainTxnTransferHotspotV2, owner_signature); -impl_sign!(BlockchainTxnStakeValidatorV1, owner_signature); -impl_sign!(BlockchainTxnUnstakeValidatorV1, owner_signature); -impl_sign!( - BlockchainTxnTransferValidatorStakeV1, - old_owner_signature, - new_owner_signature -); -impl_sign!(BlockchainTxnRoutingV1, signature);