diff --git a/Cargo.lock b/Cargo.lock index ff0ea46a..f6fcb8d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2705,7 +2705,7 @@ dependencies = [ [[package]] name = "helium-proto" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=master#c5c67f5631b69ef696961995045c0a41f9a5b936" +source = "git+https://github.com/helium/proto?branch=master#4085e00c6f4d82c3da798ae1bc97324bc9cada2e" dependencies = [ "bytes", "prost", diff --git a/Cargo.toml b/Cargo.toml index 48ba6c76..f6d87469 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,4 @@ helium-crypto = { version = "0.8" } helium-proto = { git = "https://github.com/helium/proto", branch = "master", features = [ "services", ] } -clap = { version = "4", features = ["derive"] } +clap = { version = "4", features = ["derive"] } \ No newline at end of file diff --git a/helium-lib/Cargo.toml b/helium-lib/Cargo.toml index b402ab4a..2f6a309c 100644 --- a/helium-lib/Cargo.toml +++ b/helium-lib/Cargo.toml @@ -28,7 +28,7 @@ bincode = "1.3.3" reqwest = { version = "0", default-features = false, features = [ "rustls-tls", ] } -helium-anchor-gen = {git = "https://github.com/helium/helium-anchor-gen.git" } +helium-anchor-gen = { git = "https://github.com/helium/helium-anchor-gen.git" } spl-associated-token-account = { version = "*", features = ["no-entrypoint"] } spl-account-compression = { version = "0.3", features = ["no-entrypoint"] } spl-memo = "4" @@ -41,11 +41,11 @@ serde = {workspace = true} serde_json = {workspace = true} lazy_static = "1" rust_decimal = {workspace = true} -helium-proto = {workspace= true} +helium-proto = {workspace = true} angry-purple-tiger = "0" sha2 = {workspace = true} clap = {workspace = true, optional = true} helium-mnemonic = { path = "../helium-mnemonic", optional = true } [dev-dependencies] -rand = "0.8" +rand = "0.8" \ No newline at end of file diff --git a/helium-lib/src/asset.rs b/helium-lib/src/asset.rs index 8bac04f6..699dd5d3 100644 --- a/helium-lib/src/asset.rs +++ b/helium-lib/src/asset.rs @@ -8,7 +8,9 @@ use crate::{ keypair::{serde_opt_pubkey, serde_pubkey, Keypair, Pubkey}, kta, mk_transaction_with_blockhash, priority_fee::{compute_budget_instruction, compute_price_instruction_for_accounts}, - programs::{SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID}, + programs::{ + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, TOKEN_METADATA_PROGRAM_ID, + }, solana_sdk::{instruction::AccountMeta, transaction::Transaction}, TransactionOpts, }; @@ -16,6 +18,47 @@ use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, result::Result as StdResult, str::FromStr}; +pub fn bubblegum_signer_key() -> Pubkey { + Pubkey::find_program_address(&[b"collection_cpi"], &mpl_bubblegum::ID).0 +} + +pub fn collection_metadata_key(collection_key: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[ + b"metadata", + TOKEN_METADATA_PROGRAM_ID.as_ref(), + collection_key.as_ref(), + ], + &TOKEN_METADATA_PROGRAM_ID, + ) + .0 +} + +pub fn collection_master_edition_key(collection_key: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[ + b"metadata", + TOKEN_METADATA_PROGRAM_ID.as_ref(), + collection_key.as_ref(), + b"edition", + ], + &TOKEN_METADATA_PROGRAM_ID, + ) + .0 +} + +pub fn merkle_tree_authority_key(merkle_tree: &Pubkey) -> Pubkey { + Pubkey::find_program_address(&[merkle_tree.as_ref()], &mpl_bubblegum::ID).0 +} + +pub fn shared_merkle_key(proof_size: u8) -> Pubkey { + Pubkey::find_program_address( + &[b"shared_merkle", &[proof_size]], + &helium_entity_manager::ID, + ) + .0 +} + pub async fn for_entity_key>( client: &C, entity_key: &E, @@ -53,6 +96,7 @@ pub async fn get_with_proof>( let (asset, asset_proof) = futures::try_join!(get(client, pubkey), proof::get(client, pubkey))?; Ok((asset, asset_proof)) } + pub mod canopy { use super::*; use spl_account_compression::state::{merkle_tree_get_size, ConcurrentMerkleTreeHeader}; diff --git a/helium-lib/src/client.rs b/helium-lib/src/client.rs index 7c43ed64..c9d35fa3 100644 --- a/helium-lib/src/client.rs +++ b/helium-lib/src/client.rs @@ -3,15 +3,22 @@ use crate::{ asset, error::{DecodeError, Error}, is_zero, - keypair::{self, Pubkey}, + keypair::{self, Keypair, Pubkey}, + priority_fee::auto_compute_limit_and_price, solana_client, + solana_sdk::{commitment_config::CommitmentConfig, signer::Signer}, }; + +use anchor_client::solana_client::send_and_confirm_transactions_in_parallel; use futures::{stream, StreamExt, TryStreamExt}; use itertools::Itertools; use jsonrpc_client::{JsonRpcError, SendRequest}; +use solana_sdk::{instruction::Instruction, message::Message, transaction::Transaction}; use std::{marker::Send, sync::Arc}; use tracing::instrument; +pub use solana_client::nonblocking::rpc_client::RpcClient as SolanaRpcClient; + 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,17 +30,153 @@ pub static SOLANA_URL_DEVNET: &str = "https://solana-rpc.web.test-helium.com?ses pub static SOLANA_URL_MAINNET_ENV: &str = "SOLANA_MAINNET_URL"; pub static SOLANA_URL_DEVNET_ENV: &str = "SOLANA_DEVNET_URL"; -pub use solana_client::nonblocking::rpc_client::RpcClient as SolanaRpcClient; +static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); + +#[derive(Clone)] +pub struct SolanaClient { + pub inner: Arc, + pub base_url: String, + pub keypair: Option>, +} + +impl Default for SolanaClient { + fn default() -> Self { + // safe to unwrap + Self::new(SOLANA_URL_MAINNET, None).unwrap() + } +} + +impl SolanaClient { + pub fn new(url: &str, keypair: Option>) -> Result { + let client = Arc::new( + solana_client::nonblocking::rpc_client::RpcClient::new_with_commitment( + url.to_string(), + CommitmentConfig::finalized(), + ), + ); + + // Add debug print for keypair + if let Some(kp) = &keypair { + println!("Initializing SolanaClient with keypair: {:?}", kp.pubkey()); + } else { + println!("Initializing SolanaClient without keypair"); + } + + Ok(Self { + inner: client, + base_url: url.to_string(), + keypair, + }) + } + + pub fn ws_url(&self) -> String { + self.base_url + .replace("https", "wss") + .replace("http", "ws") + .replace("127.0.0.1:8899", "127.0.0.1:8900") + } + + pub fn pubkey(&self) -> Result { + self.keypair + .as_ref() + .map(|keypair| keypair.pubkey()) + .ok_or_else(|| Error::KeypairUnconfigured) + } + + pub async fn send_instructions( + &self, + ixs: &[Instruction], + extra_signers: &[Keypair], + ) -> Result<(), Error> { + let keypair = self + .keypair + .as_ref() + .ok_or_else(|| Error::KeypairUnconfigured)?; + + if ixs.is_empty() { + return Ok(()); + } + + let signers: Vec<&dyn Signer> = std::iter::once(keypair.as_ref() as &dyn Signer) + .chain(extra_signers.iter().map(|k| k as &dyn Signer)) + .collect(); + + let (recent_blockhash, _) = self + .inner + .get_latest_blockhash_with_commitment(CommitmentConfig::finalized()) + .await?; + + let optimized_ixs = auto_compute_limit_and_price( + &self.inner, + ixs, + &signers, + 1.2, + Some(&keypair.pubkey()), + Some(recent_blockhash), + ) + .await?; + + let message = Message::new(&optimized_ixs, Some(&keypair.pubkey())); + let transaction = Transaction::new(&signers, message, recent_blockhash); + self.inner + .send_and_confirm_transaction_with_spinner(&transaction) + .await?; + + Ok(()) + } +} #[derive(Clone)] pub struct Client { - pub solana_client: Arc, + pub solana_client: Arc, pub das_client: Arc, } +impl TryFrom<&str> for Client { + type Error = Error; + fn try_from(value: &str) -> Result { + fn env_or(key: &str, default: &str) -> String { + std::env::var(key).unwrap_or_else(|_| default.to_string()) + } + let url = match value { + "m" | "mainnet-beta" => &env_or(SOLANA_URL_MAINNET_ENV, SOLANA_URL_MAINNET), + "d" | "devnet" => &env_or(SOLANA_URL_DEVNET_ENV, SOLANA_URL_DEVNET), + url => url, + }; + let das_client = Arc::new(DasClient::with_base_url(url)?); + let solana_client = Arc::new(SolanaClient::new(url, None)?); + + Ok(Self { + solana_client, + das_client, + }) + } +} + +impl AsRef for SolanaClient { + fn as_ref(&self) -> &SolanaRpcClient { + &self.inner + } +} + +impl AsRef for Client { + fn as_ref(&self) -> &SolanaRpcClient { + &self.solana_client.inner + } +} + +impl AsRef for Client { + fn as_ref(&self) -> &DasClient { + &self.das_client + } +} + #[async_trait::async_trait] pub trait GetAnchorAccount { - async fn anchor_account(&self, pubkey: &Pubkey) -> Result; + async fn anchor_account( + &self, + pubkey: &Pubkey, + ) -> Result, Error>; async fn anchor_accounts( &self, pubkeys: &[Pubkey], @@ -42,10 +185,13 @@ pub trait GetAnchorAccount { #[async_trait::async_trait] impl GetAnchorAccount for SolanaRpcClient { - async fn anchor_account(&self, pubkey: &Pubkey) -> Result { + async fn anchor_account( + &self, + pubkey: &Pubkey, + ) -> Result, Error> { let account = self.get_account(pubkey).await?; let decoded = T::try_deserialize(&mut account.data.as_ref())?; - Ok(decoded) + Ok(Some(decoded)) } async fn anchor_accounts( @@ -53,7 +199,7 @@ impl GetAnchorAccount for SolanaRpcClient { pubkeys: &[Pubkey], ) -> Result>, Error> { async fn get_accounts( - client: &SolanaRpcClient, + client: &solana_client::nonblocking::rpc_client::RpcClient, pubkeys: &[Pubkey], ) -> Result>, Error> { let accounts = client.get_multiple_accounts(pubkeys).await?; @@ -86,46 +232,14 @@ impl GetAnchorAccount for Client { async fn anchor_account( &self, pubkey: &keypair::Pubkey, - ) -> Result { - self.solana_client.anchor_account(pubkey).await + ) -> Result, Error> { + self.solana_client.inner.anchor_account(pubkey).await } async fn anchor_accounts( &self, pubkeys: &[Pubkey], ) -> Result>, Error> { - self.solana_client.anchor_accounts(pubkeys).await - } -} - -impl TryFrom<&str> for Client { - type Error = Error; - fn try_from(value: &str) -> Result { - fn env_or(key: &str, default: &str) -> String { - std::env::var(key).unwrap_or_else(|_| default.to_string()) - } - let url = match value { - "m" | "mainnet-beta" => &env_or(SOLANA_URL_MAINNET_ENV, SOLANA_URL_MAINNET), - "d" | "devnet" => &env_or(SOLANA_URL_DEVNET_ENV, SOLANA_URL_DEVNET), - url => 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 AsRef for Client { - fn as_ref(&self) -> &SolanaRpcClient { - &self.solana_client - } -} - -impl AsRef for Client { - fn as_ref(&self) -> &DasClient { - &self.das_client + self.solana_client.inner.anchor_accounts(pubkeys).await } } @@ -199,8 +313,6 @@ impl DasClientError { #[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 { diff --git a/helium-lib/src/dao.rs b/helium-lib/src/dao.rs index 7b362190..3c655624 100644 --- a/helium-lib/src/dao.rs +++ b/helium-lib/src/dao.rs @@ -1,8 +1,8 @@ use crate::{ - data_credits, entity_key::AsEntityKey, helium_entity_manager, helium_sub_daos, keypair::Pubkey, - lazy_distributor, programs::TOKEN_METADATA_PROGRAM_ID, rewards_oracle, token::Token, + asset, data_credits, entity_key::AsEntityKey, get_current_epoch, helium_entity_manager, + helium_sub_daos, keypair::Pubkey, lazy_distributor, rewards_oracle, token::Token, }; -use chrono::Timelike; + use sha2::{Digest, Sha256}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] @@ -24,79 +24,66 @@ impl Dao { let mint = match self { Self::Hnt => Token::Hnt.mint(), }; - let (dao_key, _) = - Pubkey::find_program_address(&[b"dao", mint.as_ref()], &helium_sub_daos::id()); - dao_key + + Pubkey::find_program_address(&[b"dao", mint.as_ref()], &helium_sub_daos::ID).0 + } + + pub fn program_approval_key(&self, program: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[b"program_approval", &self.key().as_ref(), program.as_ref()], + &helium_entity_manager::ID, + ) + .0 } pub fn dataonly_config_key(&self) -> Pubkey { - let (key, _) = Pubkey::find_program_address( - &[b"data_only_config", self.key().as_ref()], - &helium_entity_manager::id(), - ); - key + Pubkey::find_program_address( + &[b"data_only_config", &self.key().as_ref()], + &helium_entity_manager::ID, + ) + .0 } pub fn dataonly_escrow_key(&self) -> Pubkey { - let (data_only_escrow, _doe_bump) = Pubkey::find_program_address( - &[b"data_only_escrow", self.dataonly_config_key().as_ref()], - &helium_entity_manager::id(), - ); - data_only_escrow + Pubkey::find_program_address( + &[b"data_only_escrow", &self.dataonly_config_key().as_ref()], + &helium_entity_manager::ID, + ) + .0 } pub fn collection_metadata_key(&self, collection_key: &Pubkey) -> Pubkey { - let (collection_metadata, _bump) = Pubkey::find_program_address( - &[ - b"metadata", - TOKEN_METADATA_PROGRAM_ID.as_ref(), - collection_key.as_ref(), - ], - &TOKEN_METADATA_PROGRAM_ID, - ); - collection_metadata + asset::collection_metadata_key(collection_key) } pub fn collection_master_edition_key(&self, collection_key: &Pubkey) -> Pubkey { - let (collection_master_edition, _cme_bump) = Pubkey::find_program_address( - &[ - b"metadata", - TOKEN_METADATA_PROGRAM_ID.as_ref(), - collection_key.as_ref(), - b"edition", - ], - &TOKEN_METADATA_PROGRAM_ID, - ); - collection_master_edition + asset::collection_master_edition_key(collection_key) } pub fn merkle_tree_authority(&self, merkle_tree: &Pubkey) -> Pubkey { - let (tree_authority, _ta_bump) = - Pubkey::find_program_address(&[merkle_tree.as_ref()], &mpl_bubblegum::ID); - tree_authority + asset::merkle_tree_authority_key(merkle_tree) } pub fn bubblegum_signer(&self) -> Pubkey { - let (bubblegum_signer, _bs_bump) = - Pubkey::find_program_address(&[b"collection_cpi"], &mpl_bubblegum::ID); - bubblegum_signer + asset::bubblegum_signer_key() } pub fn entity_creator_key(&self) -> Pubkey { - let (key, _) = Pubkey::find_program_address( - &[b"entity_creator", self.key().as_ref()], - &helium_entity_manager::id(), - ); - key + Pubkey::find_program_address( + &[b"entity_creator", &self.key().as_ref()], + &helium_entity_manager::ID, + ) + .0 } pub fn entity_key_to_kta_key(&self, entity_key: &E) -> Pubkey { let hash = Sha256::digest(entity_key.as_entity_key()); - let (key, _) = Pubkey::find_program_address( - &[b"key_to_asset", self.key().as_ref(), hash.as_ref()], - &helium_entity_manager::id(), - ); - key + + Pubkey::find_program_address( + &[b"key_to_asset", &self.key().as_ref(), &hash], + &helium_entity_manager::ID, + ) + .0 } pub fn oracle_signer_key() -> Pubkey { @@ -114,6 +101,18 @@ impl Dao { Pubkey::find_program_address(&[b"dc", Token::Dc.mint().as_ref()], &data_credits::id()); key } + + pub fn epoch_info_key(&self) -> Pubkey { + let dao = self.key(); + let unix_time = chrono::Utc::now().timestamp() as u64; + let epoch = get_current_epoch(unix_time); + let b_u64 = epoch.to_le_bytes(); + Pubkey::find_program_address( + &[b"dao_epoch_info", &dao.as_ref(), &b_u64], + &helium_sub_daos::ID, + ) + .0 + } } #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] @@ -141,9 +140,7 @@ impl SubDao { pub fn key(&self) -> Pubkey { let mint = self.mint(); - let (subdao_key, _) = - Pubkey::find_program_address(&[b"sub_dao", mint.as_ref()], &helium_sub_daos::id()); - subdao_key + Pubkey::find_program_address(&[b"sub_dao", mint.as_ref()], &helium_sub_daos::ID).0 } pub fn mint(&self) -> &Pubkey { @@ -186,29 +183,32 @@ impl SubDao { } pub fn rewardable_entity_config_key(&self) -> Pubkey { + let sub_dao = self.key(); let suffix = match self { Self::Iot => b"IOT".as_ref(), Self::Mobile => b"MOBILE".as_ref(), }; - let (key, _) = Pubkey::find_program_address( - &[b"rewardable_entity_config", self.key().as_ref(), suffix], + + Pubkey::find_program_address( + &[b"rewardable_entity_config", &sub_dao.as_ref(), suffix], &helium_entity_manager::id(), - ); - key + ) + .0 } pub fn info_key(&self, entity_key: &E) -> Pubkey { - let hash = Sha256::digest(entity_key.as_entity_key()); let config_key = self.rewardable_entity_config_key(); + let hash = Sha256::digest(&entity_key.as_entity_key()); let prefix = match self { Self::Iot => "iot_info", Self::Mobile => "mobile_info", }; - let (key, _) = Pubkey::find_program_address( - &[prefix.as_bytes(), config_key.as_ref(), &hash], + + Pubkey::find_program_address( + &[prefix.as_bytes(), &config_key.as_ref(), &hash], &helium_entity_manager::id(), - ); - key + ) + .0 } pub fn lazy_distributor_key(&self) -> Pubkey { @@ -232,29 +232,28 @@ impl SubDao { } pub fn config_key(&self) -> Pubkey { + let sub_dao = self.key(); let prefix = match self { Self::Iot => "iot_config", Self::Mobile => "mobile_config", }; - let (key, _) = Pubkey::find_program_address( - &[prefix.as_bytes(), self.key().as_ref()], + + Pubkey::find_program_address( + &[prefix.as_bytes(), &sub_dao.as_ref()], &helium_entity_manager::id(), - ); - key + ) + .0 } pub fn epoch_info_key(&self) -> Pubkey { - const EPOCH_LENGTH: u32 = 60 * 60 * 24; - let epoch = chrono::Utc::now().second() / EPOCH_LENGTH; - - let (key, _) = Pubkey::find_program_address( - &[ - "sub_dao_epoch_info".as_bytes(), - self.key().as_ref(), - &epoch.to_le_bytes(), - ], + let sub_dao = self.key(); + let unix_time = chrono::Utc::now().timestamp() as u64; + let epoch = get_current_epoch(unix_time); + let b_u64 = epoch.to_le_bytes(); + Pubkey::find_program_address( + &[b"sub_dao_epoch_info", &sub_dao.as_ref(), &b_u64], &helium_sub_daos::ID, - ); - key + ) + .0 } } diff --git a/helium-lib/src/dc.rs b/helium-lib/src/dc.rs index a2db68cf..c3fff18c 100644 --- a/helium-lib/src/dc.rs +++ b/helium-lib/src/dc.rs @@ -66,6 +66,7 @@ pub async fn mint_transaction>( .as_ref() .anchor_account::(&Dao::dc_key()) .await? + .ok_or_else(|| Error::account_not_found())? .hnt_price_oracle; let ix = Instruction { diff --git a/helium-lib/src/entity_key.rs b/helium-lib/src/entity_key.rs index c69e4cd4..dc6ab61b 100644 --- a/helium-lib/src/entity_key.rs +++ b/helium-lib/src/entity_key.rs @@ -39,7 +39,7 @@ impl AsEntityKey for helium_crypto::PublicKey { } } -pub use helium_anchor_gen::helium_entity_manager::KeySerialization; +pub use crate::helium_entity_manager::KeySerialization; pub fn from_str(str: &str, encoding: KeySerialization) -> Result, DecodeError> { let entity_key = match encoding { diff --git a/helium-lib/src/error.rs b/helium-lib/src/error.rs index f31d2931..473dbb7a 100644 --- a/helium-lib/src/error.rs +++ b/helium-lib/src/error.rs @@ -13,6 +13,10 @@ pub enum Error { Anchor(#[from] anchor_client::ClientError), #[error("anchor lang: {0}")] AnchorLang(#[from] helium_anchor_gen::anchor_lang::error::Error), + #[error("Account already exists")] + AccountExists, + #[error("Account non existent: {0}")] + AccountAbsent(String), #[error("DAS client: {0}")] Das(#[from] client::DasClientError), #[error("grpc: {0}")] @@ -37,6 +41,16 @@ pub enum Error { Decode(#[from] DecodeError), #[error("encode: {0}")] Encode(#[from] EncodeError), + #[error("Keypair is not configured")] + KeypairUnconfigured, + #[error("encode: {0}")] + Error(String), +} + +impl Error { + pub fn other(reason: S) -> Self { + Self::Error(reason.to_string()) + } } impl From for Error { @@ -50,6 +64,10 @@ impl Error { anchor_client::ClientError::AccountNotFound.into() } + pub fn account_exists() -> Self { + Self::AccountExists.into() + } + pub fn is_account_not_found(&self) -> bool { use solana_client::{ client_error::{ diff --git a/helium-lib/src/hotspot/dataonly.rs b/helium-lib/src/hotspot/dataonly.rs index 67f204ea..29d0c662 100644 --- a/helium-lib/src/hotspot/dataonly.rs +++ b/helium-lib/src/hotspot/dataonly.rs @@ -70,7 +70,9 @@ mod iot { .anchor_account::( &Dao::Hnt.dataonly_config_key(), ) - .await?; + .await? + .ok_or_else(|| Error::account_not_found())?; + let kta = kta::for_entity_key(hotspot_key).await?; let (asset, asset_proof) = asset::for_kta_with_proof(client, &kta).await?; let mut onboard_accounts = @@ -160,7 +162,9 @@ mod mobile { .anchor_account::( &Dao::Hnt.dataonly_config_key(), ) - .await?; + .await? + .ok_or_else(|| Error::account_not_found())?; + let kta = kta::for_entity_key(hotspot_key).await?; let (asset, asset_proof) = asset::for_kta_with_proof(client, &kta).await?; let mut onboard_accounts = @@ -277,7 +281,9 @@ pub async fn issue_transaction + GetAnchorAccount>( let config_account = client .anchor_account::(&Dao::Hnt.dataonly_config_key()) - .await?; + .await? + .ok_or_else(|| Error::account_not_found())?; + let hotspot_key = helium_crypto::PublicKey::from_bytes(&add_tx.gateway)?; let entity_key = hotspot_key.as_entity_key(); let accounts = mk_accounts(config_account, owner, &entity_key); diff --git a/helium-lib/src/hotspot/info.rs b/helium-lib/src/hotspot/info.rs index dc032cf6..4601b02d 100644 --- a/helium-lib/src/hotspot/info.rs +++ b/helium-lib/src/hotspot/info.rs @@ -7,7 +7,16 @@ use crate::{ dao::SubDao, entity_key::AsEntityKey, error::{DecodeError, Error}, - helium_entity_manager, + helium_entity_manager::{ + self, + instruction::{ + OnboardDataOnlyIotHotspotV0, OnboardDataOnlyMobileHotspotV0, OnboardIotHotspotV0, + OnboardMobileHotspotV0, UpdateIotInfoV0, UpdateMobileInfoV0, + }, + OnboardDataOnlyIotHotspotArgsV0, OnboardDataOnlyMobileHotspotArgsV0, + OnboardIotHotspotArgsV0, OnboardMobileHotspotArgsV0, UpdateIotInfoArgsV0, + UpdateMobileInfoArgsV0, + }, hotspot::{CommittedHotspotInfoUpdate, HotspotInfo, HotspotInfoUpdate}, keypair::Pubkey, solana_sdk::{commitment_config::CommitmentConfig, signature::Signature}, @@ -17,14 +26,6 @@ use futures::{ stream::{self, StreamExt, TryStreamExt}, TryFutureExt, }; -use helium_anchor_gen::helium_entity_manager::{ - instruction::{ - OnboardDataOnlyIotHotspotV0, OnboardDataOnlyMobileHotspotV0, OnboardIotHotspotV0, - OnboardMobileHotspotV0, UpdateIotInfoV0, UpdateMobileInfoV0, - }, - OnboardDataOnlyIotHotspotArgsV0, OnboardDataOnlyMobileHotspotArgsV0, OnboardIotHotspotArgsV0, - OnboardMobileHotspotArgsV0, UpdateIotInfoArgsV0, UpdateMobileInfoArgsV0, -}; use serde::{Deserialize, Serialize}; use solana_transaction_status::{ EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, UiInstruction, UiMessage, @@ -40,14 +41,14 @@ pub async fn get( let hotspot_info = match subdao { SubDao::Iot => client .anchor_account::(info_key) - .await + .await? .map(Into::into), SubDao::Mobile => client .anchor_account::(info_key) - .await + .await? .map(Into::into), - } - .ok(); + }; + Ok(hotspot_info) } diff --git a/helium-lib/src/iot.rs b/helium-lib/src/iot.rs new file mode 100644 index 00000000..11df8508 --- /dev/null +++ b/helium-lib/src/iot.rs @@ -0,0 +1,495 @@ +use crate::{ + anchor_lang::{InstructionData, ToAccountMetas}, + helium_entity_manager, iot_routing_manager, + keypair::Pubkey, + programs::TOKEN_METADATA_PROGRAM_ID, +}; + +use sha2::{Digest, Sha256}; +use solana_sdk::instruction::Instruction; +use spl_associated_token_account::get_associated_token_address; +use std::result::Result; + +pub fn routing_manager_key(sub_dao: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[b"routing_manager", sub_dao.as_ref()], + &iot_routing_manager::ID, + ) + .0 +} + +pub fn organization_key(routing_manager: &Pubkey, oui: u64) -> Pubkey { + Pubkey::find_program_address( + &[ + b"organization", + routing_manager.as_ref(), + &oui.to_le_bytes(), + ], + &iot_routing_manager::ID, + ) + .0 +} + +pub fn devaddr_constraint_key(organization: &Pubkey, start_addr: u64) -> Pubkey { + Pubkey::find_program_address( + &[ + b"devaddr_constraint", + organization.as_ref(), + &start_addr.to_le_bytes(), + ], + &iot_routing_manager::ID, + ) + .0 +} + +pub fn net_id_key(routing_manager: &Pubkey, net_id: u32) -> Pubkey { + Pubkey::find_program_address( + &[b"net_id", routing_manager.as_ref(), &net_id.to_le_bytes()], + &iot_routing_manager::ID, + ) + .0 +} + +pub fn organization_delegate_key(organization: &Pubkey, delegate: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[ + b"organization_delegate", + organization.as_ref(), + delegate.as_ref(), + ], + &iot_routing_manager::ID, + ) + .0 +} + +pub fn organization_collection_key(routing_manager: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[b"collection", routing_manager.as_ref()], + &iot_routing_manager::ID, + ) + .0 +} + +pub fn organization_collection_metadata_key(collection: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[ + b"metadata", + TOKEN_METADATA_PROGRAM_ID.as_ref(), + collection.as_ref(), + ], + &TOKEN_METADATA_PROGRAM_ID, + ) + .0 +} + +pub fn organization_collection_master_edition_key(collection: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[ + b"metadata", + TOKEN_METADATA_PROGRAM_ID.as_ref(), + collection.as_ref(), + b"edition", + ], + &TOKEN_METADATA_PROGRAM_ID, + ) + .0 +} + +pub fn organization_key_to_asset(dao: &Pubkey, oui: u64) -> Pubkey { + let seed_str = format!("OUI_{}", oui); + let hash = Sha256::digest(seed_str.as_bytes()); + Pubkey::find_program_address( + &[b"key_to_asset", dao.as_ref(), &hash], + &helium_entity_manager::ID, + ) + .0 +} + +pub mod organization { + use super::*; + + use crate::{ + asset, + client::{GetAnchorAccount, SolanaRpcClient}, + dao::{Dao, SubDao}, + error::Error, + helium_entity_manager, iot_routing_manager, + programs::{SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID}, + token::Token, + }; + + pub enum OrgIdentifier { + Oui(u64), + Pubkey(Pubkey), + } + + pub async fn ensure_exists>( + client: &C, + identifier: OrgIdentifier, + ) -> Result<(Pubkey, iot_routing_manager::OrganizationV0), Error> { + let sub_dao = SubDao::Iot.key(); + let routing_manager_key = routing_manager_key(&sub_dao); + let organization_key = match identifier { + OrgIdentifier::Oui(oui) => organization_key(&routing_manager_key, oui), + OrgIdentifier::Pubkey(pubkey) => pubkey, + }; + + match client + .as_ref() + .anchor_account::(&organization_key) + .await? + { + Some(organization) => Ok((organization_key, organization)), + None => Err(Error::account_not_found()), + } + } + + pub async fn create>( + client: &C, + payer: Pubkey, + net_id_key: Pubkey, + authority: Option, + recipient: Option, + ) -> Result<(Pubkey, Instruction), Error> { + let payer_iot_ata_key = get_associated_token_address(&payer, Token::Iot.mint()); + let dao_key = Dao::Hnt.key(); + let sub_dao = SubDao::Iot.key(); + let program_approval_key = Dao::Hnt.program_approval_key(&iot_routing_manager::ID); + + client + .as_ref() + .get_account(&payer_iot_ata_key) + .await + .map_err(|_| Error::AccountAbsent(format!("Payer IOT token account.")))?; + + client + .as_ref() + .anchor_account::(&program_approval_key) + .await? + .ok_or_else(|| Error::account_not_found())?; + + let shared_merkle_key = asset::shared_merkle_key(3); + let shared_merkle = client + .as_ref() + .anchor_account::(&shared_merkle_key) + .await? + .ok_or_else(|| Error::account_not_found())?; + + let routing_manager_key = routing_manager_key(&sub_dao); + let routing_manager = client + .as_ref() + .anchor_account::(&routing_manager_key) + .await? + .ok_or_else(|| Error::account_not_found())?; + + client + .as_ref() + .anchor_account::(&net_id_key) + .await? + .ok_or_else(|| Error::account_not_found())?; + + let oui = routing_manager.next_oui_id; + let organization_key = organization_key(&routing_manager_key, oui); + let collection_key = organization_collection_key(&routing_manager_key); + + Ok(( + organization_key, + Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::InitializeOrganizationV0 { + payer, + program_approval: program_approval_key, + routing_manager: routing_manager_key, + net_id: net_id_key, + iot_mint: Token::Iot.mint().clone(), + payer_iot_account: payer_iot_ata_key, + iot_price_oracle: Token::Iot.price_key().unwrap().clone(), + authority: authority.unwrap_or(payer.clone()), + bubblegum_signer: asset::bubblegum_signer_key(), + shared_merkle: shared_merkle_key, + helium_entity_manager_program: helium_entity_manager::ID, + dao: dao_key, + sub_dao: routing_manager.sub_dao, + organization: organization_key, + collection: collection_key, + collection_metadata: organization_collection_metadata_key(&collection_key), + collection_master_edition: organization_collection_master_edition_key( + &collection_key, + ), + entity_creator: Dao::Hnt.entity_creator_key(), + key_to_asset: organization_key_to_asset(&dao_key, oui), + tree_authority: asset::merkle_tree_authority_key(&shared_merkle.merkle_tree), + recipient: recipient.unwrap_or(payer.clone()), + merkle_tree: shared_merkle.merkle_tree, + bubblegum_program: mpl_bubblegum::ID, + token_metadata_program: TOKEN_METADATA_PROGRAM_ID, + log_wrapper: SPL_NOOP_PROGRAM_ID, + compression_program: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + token_program: anchor_spl::token::ID, + system_program: solana_sdk::system_program::ID, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::InitializeOrganizationV0 {}.data(), + }, + )) + } + + pub async fn approve>( + _client: &C, + authority: Pubkey, + organization_key: Pubkey, + net_id_key: Pubkey, + ) -> Result { + Ok(Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::ApproveOrganizationV0 { + authority, + organization: organization_key, + net_id: net_id_key, + system_program: solana_sdk::system_program::ID, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::ApproveOrganizationV0 {}.data(), + }) + } + + pub async fn update>( + _client: &C, + authority: Pubkey, + organization_key: Pubkey, + args: iot_routing_manager::UpdateOrganizationArgsV0, + ) -> Result { + Ok(Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::UpdateOrganizationV0 { + authority, + organization: organization_key, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::UpdateOrganizationV0 { _args: args }.data(), + }) + } +} + +pub mod organization_delegate { + use super::*; + + use crate::{client::SolanaRpcClient, error::Error, iot_routing_manager}; + + pub async fn create>( + _client: &C, + payer: Pubkey, + delegate: Pubkey, + organization_key: Pubkey, + authority: Option, + ) -> Result<(Pubkey, Instruction), Error> { + let organization_delegate_key = organization_delegate_key(&organization_key, &delegate); + + Ok(( + organization_delegate_key, + Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::InitializeOrganizationDelegateV0 { + payer, + authority: authority.unwrap_or(payer.clone()), + delegate, + organization: organization_key, + organization_delegate: organization_delegate_key, + system_program: solana_sdk::system_program::ID, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::InitializeOrganizationDelegateV0 {}.data(), + }, + )) + } + + pub async fn remove>( + _client: &C, + authority: Pubkey, + delegate: Pubkey, + organization_key: Pubkey, + ) -> Result { + let organization_delegate_key = organization_delegate_key(&organization_key, &delegate); + + Ok(Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::RemoveOrganizationDelegateV0 { + authority, + rent_refund: authority, + organization: organization_key, + organization_delegate: organization_delegate_key, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::RemoveOrganizationDelegateV0 {}.data(), + }) + } +} + +pub mod net_id { + use super::*; + + use crate::{ + client::{GetAnchorAccount, SolanaRpcClient}, + dao::SubDao, + error::Error, + iot_routing_manager, + }; + + pub enum NetIdIdentifier { + Id(u32), + Pubkey(Pubkey), + } + + pub async fn ensure_exists>( + client: &C, + identifier: NetIdIdentifier, + ) -> Result<(Pubkey, iot_routing_manager::NetIdV0), Error> { + let sub_dao = SubDao::Iot.key(); + let routing_manager_key = routing_manager_key(&sub_dao); + let net_id_key = match identifier { + NetIdIdentifier::Id(id) => net_id_key(&routing_manager_key, id), + NetIdIdentifier::Pubkey(pubkey) => pubkey, + }; + + match client + .as_ref() + .anchor_account::(&net_id_key) + .await? + { + Some(net_id) => Ok((net_id_key, net_id)), + None => Err(Error::account_not_found()), + } + } + + pub async fn create>( + client: &C, + payer: Pubkey, + args: iot_routing_manager::InitializeNetIdArgsV0, + authority: Option, + ) -> Result<(Pubkey, Instruction), Error> { + let sub_dao = SubDao::Iot.key(); + let routing_manager_key = routing_manager_key(&sub_dao); + let routing_manager = client + .as_ref() + .anchor_account::(&routing_manager_key) + .await? + .ok_or_else(|| Error::account_not_found())?; + + let net_id_key = net_id_key(&routing_manager_key, args.net_id); + let net_id_exists = client + .as_ref() + .anchor_account::(&net_id_key) + .await + .is_ok(); + + if net_id_exists { + return Err(Error::account_exists()); + } + + Ok(( + net_id_key, + Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::InitializeNetIdV0 { + payer, + routing_manager: routing_manager_key, + net_id_authority: routing_manager.net_id_authority, + authority: authority.unwrap_or(payer.clone()), + net_id: net_id_key, + system_program: solana_sdk::system_program::ID, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::InitializeNetIdV0 { _args: args }.data(), + }, + )) + } +} + +pub mod devaddr_constraint { + use super::*; + + use crate::{ + client::{GetAnchorAccount, SolanaRpcClient}, + dao::SubDao, + error::Error, + iot_routing_manager, + token::Token, + }; + + pub async fn create>( + client: &C, + payer: Pubkey, + args: iot_routing_manager::InitializeDevaddrConstraintArgsV0, + organization_key: Pubkey, + net_id_key: Pubkey, + authority: Option, + ) -> Result<(Pubkey, Instruction), Error> { + let payer_iot_ata_key = get_associated_token_address(&payer, Token::Iot.mint()); + let sub_dao = SubDao::Iot.key(); + let routing_manager_key = routing_manager_key(&sub_dao); + + client + .as_ref() + .get_account(&payer_iot_ata_key) + .await + .map_err(|_| Error::AccountAbsent(format!("Payer IOT token account.")))?; + + let net_id = client + .as_ref() + .anchor_account::(&&net_id_key) + .await? + .ok_or_else(|| Error::account_not_found())?; + + let devaddr_constarint_key = + devaddr_constraint_key(&organization_key, net_id.current_addr_offset); + + Ok(( + devaddr_constarint_key, + Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::InitializeDevaddrConstraintV0 { + payer, + authority: authority.unwrap_or(payer.clone()), + net_id: net_id_key, + routing_manager: routing_manager_key, + organization: organization_key, + iot_mint: Token::Iot.mint().clone(), + payer_iot_account: payer_iot_ata_key, + iot_price_oracle: Token::Iot.price_key().unwrap().clone(), + devaddr_constraint: devaddr_constarint_key, + token_program: anchor_spl::token::ID, + system_program: solana_sdk::system_program::ID, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::InitializeDevaddrConstraintV0 { + _args: args, + } + .data(), + }, + )) + } + + pub async fn remove>( + client: &C, + authority: Pubkey, + devaddr_constraint_key: Pubkey, + ) -> Result { + let devaddr_constraint = client + .as_ref() + .anchor_account::(&devaddr_constraint_key) + .await? + .ok_or_else(|| Error::account_not_found())?; + + Ok(Instruction { + program_id: iot_routing_manager::ID, + accounts: iot_routing_manager::accounts::RemoveDevaddrConstraintV0 { + authority, + rent_refund: authority, + net_id: devaddr_constraint.net_id, + devaddr_constraint: devaddr_constraint_key, + } + .to_account_metas(None), + data: iot_routing_manager::instruction::RemoveDevaddrConstraintV0 {}.data(), + }) + } +} diff --git a/helium-lib/src/keypair.rs b/helium-lib/src/keypair.rs index f328c949..0f64f46f 100644 --- a/helium-lib/src/keypair.rs +++ b/helium-lib/src/keypair.rs @@ -2,7 +2,7 @@ use crate::{ error::{DecodeError, Error}, solana_sdk::signature::SignerError, }; -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; #[derive(PartialEq, Debug)] pub struct Keypair(solana_sdk::signer::keypair::Keypair); @@ -10,6 +10,7 @@ pub struct Keypair(solana_sdk::signer::keypair::Keypair); pub struct VoidKeypair; pub use solana_sdk::pubkey; +use solana_sdk::signer::EncodableKey; pub use solana_sdk::{pubkey::Pubkey, pubkey::PUBKEY_BYTES, signature::Signature, signer::Signer}; pub mod serde_pubkey { @@ -141,6 +142,13 @@ impl Keypair { .map_err(|_| DecodeError::other("invalid words"))?; Ok(Self(keypair).into()) } + + pub fn from_path(path: PathBuf) -> Result { + let keypair = solana_sdk::signer::keypair::Keypair::read_from_file(path).map_err(|e| { + Error::Decode(DecodeError::other(format!("failed to read keypair: {}", e))) + })?; + Ok(Self(keypair).into()) + } } impl VoidKeypair { diff --git a/helium-lib/src/lib.rs b/helium-lib/src/lib.rs index f5c70ebd..96fa4672 100644 --- a/helium-lib/src/lib.rs +++ b/helium-lib/src/lib.rs @@ -8,6 +8,7 @@ pub mod dc; pub mod entity_key; pub mod error; pub mod hotspot; +pub mod iot; pub mod keypair; pub mod kta; pub mod memo; @@ -22,7 +23,7 @@ pub use anchor_client::solana_client; pub use anchor_spl; pub use helium_anchor_gen::{ anchor_lang, circuit_breaker, data_credits, helium_entity_manager, helium_sub_daos, - hexboosting, lazy_distributor, rewards_oracle, + hexboosting, iot_routing_manager, lazy_distributor, rewards_oracle, }; pub use solana_sdk; pub use solana_sdk::bs58; @@ -60,8 +61,8 @@ use keypair::Pubkey; use solana_sdk::{instruction::Instruction, transaction::Transaction}; use std::sync::Arc; -pub fn init(solana_client: Arc) -> Result<(), error::Error> { - kta::init(solana_client) +pub fn init(solana_client: Arc) -> Result<(), error::Error> { + kta::init(solana_client.inner.clone()) } pub struct TransactionOpts { @@ -76,6 +77,11 @@ impl Default for TransactionOpts { } } +const EPOCH_LENGTH: u64 = 60 * 60 * 24; +pub fn get_current_epoch(unix_time: u64) -> u64 { + unix_time / EPOCH_LENGTH +} + pub async fn mk_transaction_with_blockhash>( client: &C, ixs: &[Instruction], diff --git a/helium-lib/src/priority_fee.rs b/helium-lib/src/priority_fee.rs index 4e9d376c..c6c06860 100644 --- a/helium-lib/src/priority_fee.rs +++ b/helium-lib/src/priority_fee.rs @@ -1,6 +1,15 @@ use crate::{ - anchor_lang::ToAccountMetas, client::SolanaRpcClient, error::Error, keypair::Pubkey, + anchor_lang::ToAccountMetas, + client::SolanaRpcClient, + error::Error, + keypair::Pubkey, solana_client, + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + message::Message, + signers::Signers, + transaction::Transaction, + }, }; use itertools::Itertools; @@ -119,3 +128,121 @@ pub async fn compute_price_instruction_for_accounts>( let priority_fee = get_estimate_with_min(client, accounts, min_priority_fee).await?; Ok(compute_price_instruction(priority_fee)) } + +pub async fn compute_budget_for_instructions, T: Signers + ?Sized>( + client: &C, + instructions: &[Instruction], + signers: &T, + compute_multiplier: f32, + payer: Option<&Pubkey>, + blockhash: Option, +) -> Result { + const DEFAULT_COMPUTE_UNITS: u32 = 1_900_000; + // Check for existing compute unit limit instruction and replace it if found + let mut updated_instructions = instructions.to_vec(); + + let has_compute_limit = updated_instructions.iter().any(|ix| { + ix.program_id == solana_sdk::compute_budget::id() + && ix.data.first() + == solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(0) + .data + .first() + }); + + if !has_compute_limit { + updated_instructions.insert(0, compute_budget_instruction(DEFAULT_COMPUTE_UNITS)); + } else { + // Replace existing compute unit limit instruction + for ix in &mut updated_instructions { + if ix.program_id == solana_sdk::compute_budget::id() + && ix.data.first() + == solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit( + 0, + ) + .data + .first() + { + ix.data = + solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit( + DEFAULT_COMPUTE_UNITS, + ) + .data; + } + } + } + + let blockhash_actual = match blockhash { + Some(hash) => hash, + None => client.as_ref().get_latest_blockhash().await?, + }; + + let snub_tx = Transaction::new( + signers, + Message::new(&updated_instructions, payer), + blockhash_actual, + ); + + // Simulate the transaction to get the actual compute used + let simulation_result = client.as_ref().simulate_transaction(&snub_tx).await?; + let actual_compute_used = simulation_result.value.units_consumed.unwrap_or(200000); + let final_compute_budget = (actual_compute_used as f32 * compute_multiplier) as u32; + Ok(compute_budget_instruction(final_compute_budget)) +} + +pub async fn auto_compute_limit_and_price, T: Signers + ?Sized>( + client: &C, + instructions: &[Instruction], + signers: &T, + compute_multiplier: f32, + payer: Option<&Pubkey>, + blockhash: Option, +) -> Result, Error> { + let mut updated_instructions = instructions.to_vec(); + + // Compute budget instruction + let compute_budget_ix = compute_budget_for_instructions( + client, + &updated_instructions, + signers, + compute_multiplier, + payer, + blockhash, + ) + .await?; + + // Compute price instruction + let accounts: Vec = instructions + .iter() + .flat_map(|i| i.accounts.iter().map(|a| a.pubkey)) + .unique() + .map(|pk| AccountMeta::new(pk, false)) + .collect(); + + let compute_price_ix = + compute_price_instruction_for_accounts(client, &accounts, MIN_PRIORITY_FEE).await?; + + insert_or_replace_compute_instructions( + &mut updated_instructions, + compute_budget_ix, + compute_price_ix, + ); + + Ok(updated_instructions) +} + +fn insert_or_replace_compute_instructions( + instructions: &mut Vec, + budget_ix: Instruction, + price_ix: Instruction, +) { + if let Some(pos) = instructions + .iter() + .position(|ix| ix.program_id == solana_sdk::compute_budget::id()) + { + instructions[pos] = budget_ix; + instructions[pos + 1] = price_ix; + } else { + instructions.insert(0, budget_ix); + instructions.insert(1, price_ix); + } +} diff --git a/helium-lib/src/reward.rs b/helium-lib/src/reward.rs index 3a447672..0096a3bb 100644 --- a/helium-lib/src/reward.rs +++ b/helium-lib/src/reward.rs @@ -54,7 +54,8 @@ pub async fn lazy_distributor( ) -> Result { client .anchor_account::(&subdao.lazy_distributor()) - .await + .await? + .ok_or_else(|| Error::account_not_found()) } pub fn lazy_distributor_circuit_breaker( @@ -98,7 +99,8 @@ pub async fn max_claim( let ld_account = lazy_distributor(client, subdao).await?; let circuit_breaker_account: circuit_breaker::AccountWindowedCircuitBreakerV0 = client .anchor_account(&lazy_distributor_circuit_breaker(&ld_account)) - .await?; + .await? + .ok_or_else(|| Error::account_not_found())?; let threshold = match circuit_breaker_account.config { circuit_breaker::WindowedCircuitBreakerConfigV0 { threshold_type: circuit_breaker::ThresholdType::Absolute, @@ -442,7 +444,7 @@ pub mod recipient { kta: &helium_entity_manager::KeyToAssetV0, ) -> Result, Error> { let recipient_key = subdao.receipient_key_from_kta(kta); - Ok(client.anchor_account(&recipient_key).await.ok()) + Ok(client.anchor_account(&recipient_key).await?) } pub async fn for_ktas( diff --git a/helium-wallet/Cargo.toml b/helium-wallet/Cargo.toml index adffe3b9..a2615e7a 100644 --- a/helium-wallet/Cargo.toml +++ b/helium-wallet/Cargo.toml @@ -29,7 +29,7 @@ serde_json = {workspace = true} clap = { workspace = true } qr2term = "0.2" rust_decimal = {workspace = true} -tokio = {version = "1.0", features = ["full"]} +tokio = { version = "1.0", features = ["full"] } helium-lib = { path = "../helium-lib", features = ["clap", "mnemonic"] } helium-mnemonic = { path = "../helium-mnemonic" } helium-proto = {workspace = true}