diff --git a/apps/api/src/auth/guard.rs b/apps/api/src/auth/guard.rs index 914bf7d08..e87d0dc08 100644 --- a/apps/api/src/auth/guard.rs +++ b/apps/api/src/auth/guard.rs @@ -7,8 +7,8 @@ use rocket::outcome::Outcome::{Error, Success}; use rocket::{Data, Request, State}; use serde::de::DeserializeOwned; use std::sync::Arc; -use storage::models::DeviceRow; use storage::Database; +use storage::models::DeviceRow; fn error_outcome<'r, T>(req: &'r Request<'_>, status: Status, message: &str) -> Outcome<'r, T, String> { req.local_cache(|| ErrorContext(message.to_string())); diff --git a/apps/api/src/referral/client.rs b/apps/api/src/referral/client.rs index 178c27ba1..6ce6e67be 100644 --- a/apps/api/src/referral/client.rs +++ b/apps/api/src/referral/client.rs @@ -40,6 +40,7 @@ impl RewardsClient { Ok(rewards) } + #[allow(dead_code)] pub fn change_username(&mut self, address: &str, new_username: &str) -> Result> { Ok(self.database.client()?.rewards().change_username(address, new_username)?) } @@ -108,7 +109,9 @@ impl RewardsClient { let daily_limit = self.database.client()?.config().get_config_i64(ConfigKey::ReferralPerIpDaily)?; let weekly_limit = self.database.client()?.config().get_config_i64(ConfigKey::ReferralPerIpWeekly)?; let global_daily_limit = self.database.client()?.config().get_config_i64(ConfigKey::ReferralUseDailyLimit)?; - self.ip_security_client.check_rate_limits(ip_address, daily_limit, weekly_limit, global_daily_limit).await?; + self.ip_security_client + .check_rate_limits(ip_address, daily_limit, weekly_limit, global_daily_limit) + .await?; Ok(()) } diff --git a/apps/api/src/referral/mod.rs b/apps/api/src/referral/mod.rs index 7c8ea16d1..e44ef64ce 100644 --- a/apps/api/src/referral/mod.rs +++ b/apps/api/src/referral/mod.rs @@ -26,6 +26,7 @@ pub async fn create_referral(request: Authenticated, client: &Stat Ok(client.lock().await.create_referral(&request.auth.address, &request.data.code).await?.into()) } +#[allow(dead_code)] #[post("/rewards/referrals/update", format = "json", data = "")] pub async fn update_referral(request: Authenticated, client: &State>) -> Result, ApiError> { Ok(client.lock().await.change_username(&request.auth.address, &request.data.code)?.into()) diff --git a/crates/gem_evm/src/across/deployment.rs b/crates/gem_evm/src/across/deployment.rs index 204f1aee6..b1f1d28b3 100644 --- a/crates/gem_evm/src/across/deployment.rs +++ b/crates/gem_evm/src/across/deployment.rs @@ -69,6 +69,10 @@ impl AcrossDeployment { chain_id, spoke_pool: "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64", }), + Chain::Monad => Some(Self { + chain_id, + spoke_pool: "0xd2ecb3afe598b746F8123CaE365a598DA831A449", + }), Chain::SmartChain => Some(Self { chain_id, spoke_pool: "0x4e8E101924eDE233C13e2D8622DC8aED2872d505", @@ -93,6 +97,8 @@ impl AcrossDeployment { 324 => "0x863859ef502F0Ee9676626ED5B418037252eFeb2".into(), // SmartChain 56 => "0xAC537C12fE8f544D712d71ED4376a502EEa944d7".into(), + // Monad + 143 => "0xeC41F75c686e376Ab2a4F18bde263ab5822c4511".into(), // HyperEvm | Plasma 999 | 9745 => "0x5E7840E06fAcCb6d1c3b5F5E0d1d3d07F2829bba".into(), _ => MULTICALL_HANDLER.into(), @@ -122,6 +128,7 @@ impl AcrossDeployment { (Chain::Blast, vec![WETH_BLAST_ASSET_ID.into()]), (Chain::Ink, vec![WETH_INK_ASSET_ID.into(), USDT_INK_ASSET_ID.into()]), (Chain::Unichain, vec![WETH_UNICHAIN_ASSET_ID.into(), USDC_UNICHAIN_ASSET_ID.into()]), + (Chain::Monad, vec![USDC_MONAD_ASSET_ID.into(), USDT_MONAD_ASSET_ID.into()]), (Chain::SmartChain, vec![ETH_SMARTCHAIN_ASSET_ID.into()]), (Chain::Plasma, vec![USDT_PLASMA_ASSET_ID.into()]), ]) @@ -166,6 +173,7 @@ impl AcrossDeployment { USDC_POLYGON_ASSET_ID.into(), USDC_UNICHAIN_ASSET_ID.into(), USDC_HYPEREVM_ASSET_ID.into(), + USDC_MONAD_ASSET_ID.into(), ]), }, // USDC on BSC decimals are 18 @@ -195,6 +203,7 @@ impl AcrossDeployment { USDT_INK_ASSET_ID.into(), USDT_HYPEREVM_ASSET_ID.into(), USDT_PLASMA_ASSET_ID.into(), + USDT_MONAD_ASSET_ID.into(), ]), }, // USDT on BSC decimals are 18 diff --git a/crates/gem_evm/src/chainlink/contract.rs b/crates/gem_evm/src/chainlink/contract.rs index 7881443c7..c7b4f1e4b 100644 --- a/crates/gem_evm/src/chainlink/contract.rs +++ b/crates/gem_evm/src/chainlink/contract.rs @@ -8,3 +8,4 @@ sol! { } pub const CHAINLINK_ETH_USD_FEED: &str = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"; +pub const CHAINLINK_MON_USD_FEED: &str = "0xBcD78f76005B7515837af6b50c7C52BCf73822fb"; diff --git a/crates/gem_evm/src/jsonrpc.rs b/crates/gem_evm/src/jsonrpc.rs index 3aac1a023..5203a989c 100644 --- a/crates/gem_evm/src/jsonrpc.rs +++ b/crates/gem_evm/src/jsonrpc.rs @@ -1,3 +1,4 @@ +use alloy_primitives::hex; use gem_jsonrpc::types::{JsonRpcRequest, JsonRpcRequestConvert}; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; @@ -24,7 +25,7 @@ impl TransactionObject { gas: None, gas_price: None, value: None, - data: format!("0x{}", hex::encode(data)), + data: hex::encode_prefixed(data), } } @@ -35,7 +36,7 @@ impl TransactionObject { gas: None, gas_price: None, value: Some(value.to_string()), - data: format!("0x{}", hex::encode(data)), + data: hex::encode_prefixed(data), } } @@ -46,7 +47,18 @@ impl TransactionObject { gas: None, gas_price: None, value: None, - data: format!("0x{}", hex::encode(data)), + data: hex::encode_prefixed(data), + } + } + + pub fn new_call_with_from_value(from: &str, to: &str, value: &str, data: Vec) -> Self { + Self { + from: Some(from.to_string()), + to: to.to_string(), + gas: None, + gas_price: None, + value: Some(value.to_string()), + data: hex::encode_prefixed(data), } } } diff --git a/crates/gem_rewards/src/transfer_provider/evm/provider.rs b/crates/gem_rewards/src/transfer_provider/evm/provider.rs index 056dfcb29..21a9f6baf 100644 --- a/crates/gem_rewards/src/transfer_provider/evm/provider.rs +++ b/crates/gem_rewards/src/transfer_provider/evm/provider.rs @@ -80,7 +80,7 @@ impl EvmTransferProvider { let load_data = client.get_transaction_load(load_input).await?; - let nonce = metadata.get_sequence()? as u64; + let nonce = metadata.get_sequence()?; let chain_id: u64 = metadata.get_chain_id()?.parse()?; let gas_limit = load_data.fee.gas_limit.to_u64().ok_or("Gas limit overflow")?; let base_fee = fee_rate.gas_price_type.gas_price().to_u128().ok_or("Base fee overflow")?; diff --git a/crates/primitives/src/rewards.rs b/crates/primitives/src/rewards.rs index 498ce61b8..4097d567d 100644 --- a/crates/primitives/src/rewards.rs +++ b/crates/primitives/src/rewards.rs @@ -60,6 +60,7 @@ impl RewardEventType { #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare(swift = "Equatable, Hashable, Sendable")] #[serde(rename_all = "camelCase")] +#[derive(Default)] pub struct Rewards { pub code: Option, pub referral_count: i32, @@ -70,19 +71,6 @@ pub struct Rewards { //pub level: Option, } -impl Default for Rewards { - fn default() -> Self { - Self { - code: None, - referral_count: 0, - points: 0, - used_referral_code: None, - is_enabled: false, - redemption_options: vec![], - } - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare(swift = "Sendable")] #[serde(rename_all = "camelCase")] diff --git a/crates/swapper/src/across/provider.rs b/crates/swapper/src/across/provider.rs index 91fed5b29..9b6c12dd3 100644 --- a/crates/swapper/src/across/provider.rs +++ b/crates/swapper/src/across/provider.rs @@ -240,10 +240,33 @@ impl Across { } .abi_encode(); let tx = TransactionObject::new_call_to_value(deployment.spoke_pool, &value, data); - let gas_limit = self.estimate_gas_transaction(chain, tx).await.unwrap_or(U256::from(DEFAULT_FILL_GAS_LIMIT)); + let gas_limit = self + .estimate_gas_transaction(chain, tx) + .await + .unwrap_or(U256::from(Self::get_default_fill_limit(chain))); Ok((gas_limit, v3_relay_data)) } + fn get_default_fill_limit(chain: Chain) -> u64 { + match chain { + Chain::Monad => DEFAULT_FILL_GAS_LIMIT * 3, + _ => DEFAULT_FILL_GAS_LIMIT, + } + } + + async fn usd_price_for_chain(&self, chain: Chain, existing_results: &[IMulticall3::Result]) -> Result { + let feed = ChainlinkPriceFeed::new_usd_feed_for_chain(chain).ok_or(SwapperError::NotSupportedChain)?; + if chain == Chain::Monad { + let results = create_eth_client(self.rpc_provider.clone(), Chain::Monad)? + .multicall3(vec![feed.latest_round_call3()]) + .await + .map_err(|e| SwapperError::NetworkError(e.to_string()))?; + ChainlinkPriceFeed::decoded_answer(&results[0]) + } else { + ChainlinkPriceFeed::decoded_answer(&existing_results[3]) + } + } + pub fn update_v3_relay_data( &self, v3_relay_data: &mut V3RelayData, @@ -310,6 +333,7 @@ impl Swapper for Across { SwapperChainAsset::Assets(Chain::World, vec![WORLD_WETH.id.clone()]), SwapperChainAsset::Assets(Chain::Ink, vec![INK_WETH.id.clone(), INK_USDT.id.clone()]), SwapperChainAsset::Assets(Chain::Unichain, vec![UNICHAIN_WETH.id.clone(), UNICHAIN_USDC.id.clone()]), + SwapperChainAsset::Assets(Chain::Monad, vec![MONAD_USDC.id.clone(), MONAD_USDT.id.clone()]), SwapperChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_ETH.id.clone()]), SwapperChainAsset::Assets(Chain::Hyperliquid, vec![HYPEREVM_USDC.id.clone(), HYPEREVM_USDT.id.clone()]), SwapperChainAsset::Assets(Chain::Plasma, vec![PLASMA_USDT.id.clone()]), @@ -373,9 +397,9 @@ impl Swapper for Across { hubpool_client.get_current_time(), ]; - let eth_price_feed = ChainlinkPriceFeed::new_eth_usd_feed(); + let gas_price_feed = ChainlinkPriceFeed::new_usd_feed_for_chain(request.to_asset.chain()).unwrap_or_else(ChainlinkPriceFeed::new_eth_usd_feed); if !input_is_native { - calls.push(eth_price_feed.latest_round_call3()); + calls.push(gas_price_feed.latest_round_call3()); } let multicall_results = self.multicall3(hubpool_client.chain, calls).await?; @@ -420,8 +444,8 @@ impl Swapper for Across { .await?; let mut gas_fee = gas_limit * gas_price; if !input_is_native { - let eth_price = ChainlinkPriceFeed::decoded_answer(&multicall_results[3])?; - gas_fee = Self::calculate_fee_in_token(&gas_fee, ð_price, 6); + let price = self.usd_price_for_chain(request.to_asset.chain(), &multicall_results).await?; + gas_fee = Self::calculate_fee_in_token(&gas_fee, &price, 6); } // Check if bridge amount is too small @@ -563,10 +587,15 @@ mod tests { let usdc_eth: AssetId = USDC_ETH_ASSET_ID.into(); let usdc_arb: AssetId = USDC_ARB_ASSET_ID.into(); + let usdc_monad: AssetId = USDC_MONAD_ASSET_ID.into(); + let usdt_eth: AssetId = USDT_ETH_ASSET_ID.into(); + let usdt_monad: AssetId = USDT_MONAD_ASSET_ID.into(); assert!(Across::is_supported_pair(&weth_eth, &weth_op)); assert!(Across::is_supported_pair(&weth_op, &weth_arb)); assert!(Across::is_supported_pair(&usdc_eth, &usdc_arb)); + assert!(Across::is_supported_pair(&usdc_monad, &usdc_eth)); + assert!(Across::is_supported_pair(&usdt_monad, &usdt_eth)); assert!(Across::is_supported_pair(&weth_eth, &weth_bsc)); assert!(!Across::is_supported_pair(&weth_eth, &usdc_eth)); @@ -652,6 +681,44 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_across_quote_eth_usdc_to_monad_usdc() -> Result<(), SwapperError> { + let network_provider = Arc::new(NativeProvider::default()); + let swap_provider = Across::boxed(network_provider.clone()); + let options = Options { + slippage: 100.into(), + fee: None, + preferred_providers: vec![], + use_max_amount: false, + }; + + let wallet = "0x9b1fe00135e0ff09389bfaeff0c8f299ec818d4a"; + let from_asset: AssetId = USDC_ETH_ASSET_ID.into(); + let to_asset: AssetId = USDC_MONAD_ASSET_ID.into(); + let request = QuoteRequest { + from_asset: from_asset.into(), + to_asset: to_asset.into(), + wallet_address: wallet.into(), + destination_address: wallet.into(), + value: "50000000".into(), // 50 USDC + mode: SwapperMode::ExactIn, + options, + }; + + let now = SystemTime::now(); + let quote = swap_provider.fetch_quote(&request).await?; + let elapsed = SystemTime::now().duration_since(now).unwrap(); + + println!("<== elapsed: {:?}", elapsed); + println!("<== quote: {:?}", quote); + assert!(quote.to_value.parse::().unwrap() > 0); + + let quote_data = swap_provider.fetch_quote_data("e, FetchQuoteData::EstimateGas).await?; + println!("<== quote_data: {:?}", quote_data); + + Ok(()) + } + #[tokio::test] async fn test_get_swap_result() -> Result<(), Box> { let network_provider = Arc::new(NativeProvider::default()); diff --git a/crates/swapper/src/chainlink.rs b/crates/swapper/src/chainlink.rs index d9df74cef..02b6f3451 100644 --- a/crates/swapper/src/chainlink.rs +++ b/crates/swapper/src/chainlink.rs @@ -3,7 +3,7 @@ use num_traits::FromBytes; use crate::SwapperError; use gem_evm::{ - chainlink::contract::{AggregatorInterface, CHAINLINK_ETH_USD_FEED}, + chainlink::contract::{AggregatorInterface, CHAINLINK_ETH_USD_FEED, CHAINLINK_MON_USD_FEED}, multicall3::{IMulticall3, create_call3, decode_call3_return}, }; @@ -18,6 +18,19 @@ impl ChainlinkPriceFeed { } } + pub fn new_usd_feed_for_chain(chain: primitives::Chain) -> Option { + match chain { + primitives::Chain::Monad => Some(Self::new_mon_usd_feed()), + _ => Some(Self::new_eth_usd_feed()), + } + } + + pub fn new_mon_usd_feed() -> ChainlinkPriceFeed { + ChainlinkPriceFeed { + contract: CHAINLINK_MON_USD_FEED.into(), + } + } + pub fn latest_round_call3(&self) -> IMulticall3::Call3 { create_call3(&self.contract, AggregatorInterface::latestRoundDataCall {}) }