diff --git a/Cargo.lock b/Cargo.lock index 3340f628..9bd175f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -878,7 +878,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -1121,7 +1121,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", "syn_derive", ] @@ -1267,7 +1267,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -1435,7 +1435,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -1807,7 +1807,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -1829,7 +1829,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core 0.20.9", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -1962,7 +1962,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -1985,15 +1985,9 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - [[package]] name = "eager" version = "0.1.0" @@ -2123,7 +2117,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -2170,6 +2164,15 @@ dependencies = [ "anchor-lang 0.30.0", ] +[[package]] +name = "fast-math" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2465292146cdfc2011350fe3b1c616ac83cf0faeedb33463ba1c332ed8948d66" +dependencies = [ + "ieee754", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -2291,7 +2294,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -2590,7 +2593,7 @@ dependencies = [ "jsonrpc_client", "lazy_static", "mpl-bubblegum", - "pyth-sdk-solana", + "pyth-solana-receiver-sdk", "reqwest", "rust_decimal", "serde", @@ -2655,7 +2658,6 @@ dependencies = [ "qr2term", "rand 0.8.5", "rust_decimal", - "rust_decimal_macros", "serde", "serde_json", "sha2 0.10.8", @@ -2860,6 +2862,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ieee754" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9007da9cacbd3e6343da136e98b0d2df013f553d35bdec8b518f07bea768e19c" + [[package]] name = "im" version = "15.1.0" @@ -3438,7 +3446,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -3520,7 +3528,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -3532,7 +3540,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -3786,12 +3794,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -3857,9 +3865,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -3904,7 +3912,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.66", + "syn 2.0.58", "tempfile", ] @@ -3918,7 +3926,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -3951,32 +3959,35 @@ dependencies = [ ] [[package]] -name = "pyth-sdk" -version = "0.8.0" +name = "pyth-solana-receiver-sdk" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7aeef4d5f0a9c98ff5af2ddd84a8b89919c512188305b497a9eb9afa97a949" +checksum = "91e6559643f0b377b6f293269251f6a804ae7332c37f7310371f50c833453cd0" dependencies = [ - "borsh 0.10.3", - "borsh-derive 0.10.3", - "getrandom 0.2.15", + "anchor-lang 0.30.0", "hex", - "schemars", - "serde", + "pythnet-sdk", + "solana-program", ] [[package]] -name = "pyth-sdk-solana" -version = "0.10.1" +name = "pythnet-sdk" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f913de6eb29d8def199af3beaee645e84c5281327d58777eff3fdd9f1d37105" +checksum = "3bbbc0456f9f27c9ad16b6c3bf1b2a7fea61eebf900f4d024a0468b9a84fe0c1" dependencies = [ + "anchor-lang 0.30.0", + "bincode", "borsh 0.10.3", - "borsh-derive 0.10.3", "bytemuck", - "num-derive 0.3.3", - "num-traits", - "pyth-sdk", + "byteorder", + "fast-math", + "hex", + "proc-macro2", + "rustc_version", "serde", + "sha3 0.10.8", + "slow_primes", "solana-program", "thiserror", ] @@ -4017,7 +4028,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -4421,16 +4432,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "rust_decimal_macros" -version = "1.34.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e418701588729bef95e7a655f2b483ad64bb97c46e8e79fde83efd92aaab6d82" -dependencies = [ - "quote", - "rust_decimal", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4557,30 +4558,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "schemars" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.66", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -4604,7 +4581,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -4690,18 +4667,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -4723,7 +4689,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -4757,7 +4723,7 @@ dependencies = [ "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -4926,6 +4892,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slow_primes" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58267dd2fbaa6dceecba9e3e106d2d90a2b02497c0e8b01b8759beccf5113938" +dependencies = [ + "num", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -5099,7 +5074,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -5479,7 +5454,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -5793,7 +5768,7 @@ checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" dependencies = [ "quote", "spl-discriminator-syn 0.1.2", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -5804,7 +5779,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn 0.2.0", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -5816,7 +5791,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.66", + "syn 2.0.58", "thiserror", ] @@ -5829,7 +5804,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.66", + "syn 2.0.58", "thiserror", ] @@ -5912,7 +5887,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -5924,7 +5899,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -6167,9 +6142,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -6185,7 +6160,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -6296,7 +6271,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -6400,7 +6375,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -6504,7 +6479,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -6763,7 +6738,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -6797,7 +6772,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7104,7 +7079,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] @@ -7124,7 +7099,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.58", ] [[package]] diff --git a/helium-lib/Cargo.toml b/helium-lib/Cargo.toml index 818ed118..dcc5b7c6 100644 --- a/helium-lib/Cargo.toml +++ b/helium-lib/Cargo.toml @@ -12,7 +12,6 @@ mnemonic = ["helium-mnemonic"] hex = "0.4" chrono = {version = "0", features = ["serde"]} thiserror = "1" -pyth-sdk-solana = "0" async-trait = "0" anchor-client = {version = "0.30.0", features = ["async"] } anchor-spl = { version = "0.30.0", features = ["mint", "token"] } @@ -33,11 +32,11 @@ 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"] } mpl-bubblegum = "1" +pyth-solana-receiver-sdk = "0" solana-program = "*" solana-transaction-status = "*" serde = {workspace = true} serde_json = {workspace = true} -# serde_with = "2" lazy_static = "1" rust_decimal = {workspace = true} helium-proto = {workspace= true} diff --git a/helium-lib/src/result.rs b/helium-lib/src/result.rs index 9620e28b..36edf829 100644 --- a/helium-lib/src/result.rs +++ b/helium-lib/src/result.rs @@ -1,7 +1,7 @@ use std::{array::TryFromSliceError, num::TryFromIntError}; use thiserror::Error; -use crate::{onboarding, settings}; +use crate::{onboarding, settings, token}; pub type Result = std::result::Result; @@ -18,8 +18,8 @@ pub enum Error { AnchorLang(#[from] helium_anchor_gen::anchor_lang::error::Error), #[error("DAS client: {0}")] Das(#[from] settings::DasClientError), - #[error("pyth client: {0}")] - Pyth(#[from] pyth_sdk_solana::PythError), + #[error("price client: {0}")] + Price(#[from] token::price::PriceError), #[error("rest client: {0}")] Rest(#[from] reqwest::Error), #[error("system time: {0}")] diff --git a/helium-lib/src/token.rs b/helium-lib/src/token.rs index 3910cfe3..5b942e95 100644 --- a/helium-lib/src/token.rs +++ b/helium-lib/src/token.rs @@ -1,8 +1,9 @@ use crate::{ keypair::{serde_pubkey, GetPubkey, Pubkey}, - result::{DecodeError, Error, Result}, + result::{DecodeError, Result}, settings::Settings, }; +use chrono::{DateTime, Utc}; use futures::stream::{self, StreamExt, TryStreamExt}; use helium_anchor_gen::circuit_breaker; use solana_sdk::{signer::Signer, system_instruction}; @@ -16,10 +17,17 @@ pub enum TokenError { lazy_static::lazy_static! { static ref HNT_MINT: Pubkey = Pubkey::from_str("hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux").unwrap(); - static ref HNT_PRICE_KEY: Pubkey = Pubkey::from_str("7moA1i5vQUpfDwSpK6Pw9s56ahB7WFGidtbL2ujWrVvm").unwrap(); + static ref HNT_PRICE_KEY: Pubkey = Pubkey::from_str("4DdmDswskDxXGpwHrXUfn2CNUm9rt21ac79GHNTN3J33").unwrap(); + static ref HNT_PRICE_FEED: price::FeedId = price::feed_from_hex("649fdd7ec08e8e2a20f425729854e90293dcbe2376abc47197a14da6ff339756").unwrap(); static ref MOBILE_MINT: Pubkey = Pubkey::from_str("mb1eu7TzEc71KxDpsmsKoucSSuuoGLv1drys1oP2jh6").unwrap(); + static ref MOBILE_PRICE_KEY: Pubkey = Pubkey::from_str("DQ4C1tzvu28cwo1roN1Wm6TW35sfJEjLh517k3ZeWevx").unwrap(); + static ref MOBILE_PRICE_FEED: price::FeedId = price::feed_from_hex("ff4c53361e36a9b837433c87d290c229e1f01aec5ef98d9f3f70953a20a629ce").unwrap(); + static ref IOT_MINT: Pubkey = Pubkey::from_str("iotEVVZLEywoTn1QdwNPddxPWszn3zFhEot3MfL9fns").unwrap(); + static ref IOT_PRICE_KEY: Pubkey = Pubkey::from_str("8UYEn5Weq7toHwgcmctvcAxaNJo3SJxXEayM57rpoXr9").unwrap(); + static ref IOT_PRICE_FEED: price::FeedId = price::feed_from_hex("6b701e292e0836d18a5904a08fe94534f9ab5c3d4ff37dc02c74dd0f4901944d").unwrap(); + static ref DC_MINT: Pubkey = Pubkey::from_str("dcuc8Amr83Wz27ZkQ2K9NS6r8zRpf1J6cvArEBDZDmm").unwrap(); static ref SOL_MINT: Pubkey = solana_sdk::system_program::ID; } @@ -113,27 +121,79 @@ pub async fn balance_for_addresses( .await } -pub async fn pyth_price(settings: &Settings, token: Token) -> Result { - let price_key = token - .price_key() - .ok_or_else(|| DecodeError::other(format!("No pyth price key for {token}")))?; - let client = settings.mk_solana_client()?; - let mut price_account = client.get_account(price_key).await?; - let price_feed = - pyth_sdk_solana::state::SolanaPriceAccount::account_to_feed(price_key, &mut price_account)?; - - use std::time::{SystemTime, UNIX_EPOCH}; - let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?; - price_feed - .get_ema_price_no_older_than( - current_time - .as_secs() - .try_into() - .map_err(DecodeError::from)?, - 10 * 60, - ) - .ok_or_else(|| DecodeError::other("No token price found")) - .map_err(Error::from) +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::*; + + pub use pyth_solana_receiver_sdk::price_update::FeedId; + pub const DC_PER_USD: i64 = 100_000; + + #[derive(Debug, thiserror::Error)] + pub enum PriceError { + #[error("invalid or unsupported token: {0}")] + InvalidToken(super::Token), + #[error("invalid price feed")] + InvalidFeed, + #[error("price too old")] + TooOld, + #[error("price below 0")] + Negative, + #[error("invalid price timestamp: {0}")] + InvalidTimestamp(i64), + #[error("unsupported positive price exponent")] + PositiveExponent, + } + + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] + pub struct Price { + pub timestamp: DateTime, + pub price: Decimal, + pub token: super::Token, + } + + 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 { + 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 PriceUpdateV2 { price_message, .. } = + PriceUpdateV2::try_deserialize(&mut account.data.as_slice())?; + + if (price_message.publish_time.saturating_add(10 * 60)) < Utc::now().timestamp() { + return Err(PriceError::TooOld.into()); + } + if price_message.ema_price < 0 { + return Err(PriceError::Negative.into()); + } + if price_message.exponent > 0 { + return Err(PriceError::PositiveExponent.into()); + } + if price_message.feed_id != *price_feed { + return Err(PriceError::InvalidFeed.into()); + } + let scale = price_message.exponent.unsigned_abs(); + // Remove the confidence interval from the price to get the most optimistic price: + let mut price = Decimal::new(price_message.ema_price, scale) + + Decimal::new(price_message.ema_conf as i64, scale) * Decimal::new(2, 0); + // ensure we use only up to 6 decimals, this rounds using `MidpointAwayFromZero` + price.rescale(6); + let timestamp = DateTime::from_timestamp(price_message.publish_time, 0) + .ok_or(PriceError::InvalidTimestamp(price_message.publish_time))?; + + Ok(Price { + timestamp, + price, + token, + }) + } } #[derive( @@ -182,15 +242,22 @@ impl Token { vec![Self::Hnt, Self::Iot, Self::Mobile, Self::Dc, Self::Sol] } - pub fn transferrable_value_parser(s: &str) -> StdResult { - let transferrable = [Self::Iot, Self::Mobile, Self::Hnt, Self::Sol]; + fn from_allowed(s: &str, allowed: &[Self]) -> StdResult { let result = Self::from_str(s)?; - if !transferrable.contains(&result) { + if !allowed.contains(&result) { return Err(TokenError::InvalidToken(s.to_string())); } Ok(result) } + pub fn transferrable_value_parser(s: &str) -> StdResult { + Self::from_allowed(s, &[Self::Iot, Self::Mobile, Self::Hnt, Self::Sol]) + } + + pub fn pricekey_value_parser(s: &str) -> StdResult { + Self::from_allowed(s, &[Self::Iot, Self::Mobile, Self::Hnt]) + } + pub fn associated_token_adress(&self, address: &Pubkey) -> Pubkey { match self { Self::Sol => *address, @@ -322,6 +389,17 @@ impl Token { pub fn price_key(&self) -> Option<&Pubkey> { match self { Self::Hnt => Some(&HNT_PRICE_KEY), + Self::Iot => Some(&IOT_PRICE_KEY), + Self::Mobile => Some(&MOBILE_PRICE_KEY), + _ => None, + } + } + + pub fn price_feed(&self) -> Option<&price::FeedId> { + match self { + Self::Hnt => Some(&HNT_PRICE_FEED), + Self::Iot => Some(&IOT_PRICE_FEED), + Self::Mobile => Some(&MOBILE_PRICE_FEED), _ => None, } } diff --git a/helium-wallet/Cargo.toml b/helium-wallet/Cargo.toml index 92f2f3f5..203f2bbd 100644 --- a/helium-wallet/Cargo.toml +++ b/helium-wallet/Cargo.toml @@ -28,7 +28,6 @@ serde_json = {workspace = true} clap = { workspace = true } qr2term = "0.2" rust_decimal = {workspace = true} -rust_decimal_macros = "1" tokio = {version = "1.0", features = ["full"]} helium-lib = { path = "../helium-lib", features = ["clap", "mnemonic"] } helium-mnemonic = { path = "../helium-mnemonic" } diff --git a/helium-wallet/src/cmd/dc/price.rs b/helium-wallet/src/cmd/dc/price.rs index bae5985b..09894035 100644 --- a/helium-wallet/src/cmd/dc/price.rs +++ b/helium-wallet/src/cmd/dc/price.rs @@ -1,7 +1,6 @@ use crate::cmd::*; use helium_lib::token::{self, Token}; use rust_decimal::prelude::*; -use rust_decimal_macros::dec; use serde_json::json; #[derive(Clone, Debug, clap::Args)] @@ -14,18 +13,14 @@ pub struct Cmd { impl Cmd { pub async fn run(&self, opts: Opts) -> Result { - let settings = opts.try_into()?; - let price = token::pyth_price(&settings, Token::Hnt).await?; - let decimals = price.expo.unsigned_abs(); - - // Remove the confidence from the price to use the most conservative price - // https://docs.pyth.network/pythnet-price-feeds/best-practices - let hnt_price = Decimal::new(price.price, decimals) - - (Decimal::new(price.conf as i64, decimals) * dec!(2)); + let settings: Settings = opts.try_into()?; + let solana_client = settings.mk_solana_client()?; + let price = token::price::get(&solana_client, Token::Hnt).await?; + let hnt_price = price.price; let usd_amount = Decimal::from_f64(self.usd).ok_or_else(|| anyhow!("Invalid USD amount"))?; - let dc_amount = (usd_amount * dec!(100_000)) + let dc_amount = (usd_amount * Decimal::new(token::price::DC_PER_USD, 0)) .to_u64() .ok_or_else(|| anyhow!("Invalid USD amount"))?; let hnt_amount = (usd_amount / hnt_price).round_dp(Token::Hnt.decimals().into()); @@ -35,6 +30,7 @@ impl Cmd { "hnt": hnt_amount, "dc": dc_amount, "hnt_price": hnt_price, + "timestamp": price.timestamp, }); print_json(&json) } diff --git a/helium-wallet/src/cmd/mod.rs b/helium-wallet/src/cmd/mod.rs index 964fb579..81148a27 100644 --- a/helium-wallet/src/cmd/mod.rs +++ b/helium-wallet/src/cmd/mod.rs @@ -24,6 +24,7 @@ pub mod dc; pub mod export; pub mod hotspots; pub mod info; +pub mod price; pub mod router; pub mod sign; pub mod transfer; diff --git a/helium-wallet/src/cmd/price.rs b/helium-wallet/src/cmd/price.rs new file mode 100644 index 00000000..816fb420 --- /dev/null +++ b/helium-wallet/src/cmd/price.rs @@ -0,0 +1,20 @@ +use crate::cmd::*; +use helium_lib::token; + +#[derive(Clone, Debug, clap::Args)] +/// Get the current price from the pyth price feed for the given token +pub struct Cmd { + /// Token to look up + #[arg(value_parser = token::Token::pricekey_value_parser)] + token: token::Token, +} + +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?; + + print_json(&price) + } +} diff --git a/helium-wallet/src/main.rs b/helium-wallet/src/main.rs index cd56b2b1..7b2b7a22 100644 --- a/helium-wallet/src/main.rs +++ b/helium-wallet/src/main.rs @@ -1,6 +1,8 @@ use clap::Parser; use helium_wallet::{ - cmd::{balance, create, dc, export, hotspots, info, router, sign, transfer, upgrade, Opts}, + cmd::{ + balance, create, dc, export, hotspots, info, price, router, sign, transfer, upgrade, Opts, + }, result::Result, }; @@ -30,6 +32,7 @@ pub enum Cmd { Create(create::Cmd), Hotspots(Box), Dc(dc::Cmd), + Price(price::Cmd), Transfer(transfer::Cmd), Export(export::Cmd), Sign(sign::Cmd), @@ -53,6 +56,7 @@ async fn run(cli: Cli) -> Result { Cmd::Create(cmd) => cmd.run(cli.opts).await, Cmd::Hotspots(cmd) => cmd.run(cli.opts).await, Cmd::Dc(cmd) => cmd.run(cli.opts).await, + Cmd::Price(cmd) => cmd.run(cli.opts).await, Cmd::Transfer(cmd) => cmd.run(cli.opts).await, Cmd::Export(cmd) => cmd.run(cli.opts).await, Cmd::Sign(cmd) => cmd.run(cli.opts).await,