From 07e8b3bea839ddf17f225aea3dcb4ca3329d2be8 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Tue, 14 Oct 2025 23:59:20 +0900 Subject: [PATCH 1/4] Add swap result to TransactionSwapMetadata --- crates/chain_primitives/src/balance_diff.rs | 1 + crates/gem_evm/src/rpc/swap_mapper.rs | 1 + crates/gem_solana/src/provider/transaction_mapper.rs | 4 ++++ crates/primitives/src/explorers/near_intents.rs | 2 +- crates/primitives/src/transaction_metadata_types.rs | 4 +++- 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/chain_primitives/src/balance_diff.rs b/crates/chain_primitives/src/balance_diff.rs index 42f25030b..14aad1a98 100644 --- a/crates/chain_primitives/src/balance_diff.rs +++ b/crates/chain_primitives/src/balance_diff.rs @@ -49,6 +49,7 @@ impl SwapMapper { to_asset: received_diff.asset_id.clone(), to_value: to_value.to_string(), provider, + swap_result: None, }) } diff --git a/crates/gem_evm/src/rpc/swap_mapper.rs b/crates/gem_evm/src/rpc/swap_mapper.rs index 6cd8ed257..d1bbe588a 100644 --- a/crates/gem_evm/src/rpc/swap_mapper.rs +++ b/crates/gem_evm/src/rpc/swap_mapper.rs @@ -250,6 +250,7 @@ impl SwapMapper { from_value, to_value, provider: Some(provider.to_string()), + swap_result: None, }); } None diff --git a/crates/gem_solana/src/provider/transaction_mapper.rs b/crates/gem_solana/src/provider/transaction_mapper.rs index b2a483f26..b24facfd9 100644 --- a/crates/gem_solana/src/provider/transaction_mapper.rs +++ b/crates/gem_solana/src/provider/transaction_mapper.rs @@ -162,6 +162,7 @@ pub fn map_transaction(transaction: &BlockTransaction, block_time: i64) -> Optio to_asset: to_asset.clone(), to_value: to_value.clone().to_string(), provider: Some(SwapProvider::Jupiter.id().to_owned()), + swap_result: None, }; let transaction = Transaction::new( @@ -205,6 +206,7 @@ mod tests { to_asset: Chain::Solana.as_asset_id(), to_value: "139512057".to_string(), provider: Some(SwapProvider::Jupiter.id().to_owned()), + swap_result: None, }; assert_eq!(transaction.metadata, Some(serde_json::to_value(expected).unwrap())); @@ -222,6 +224,7 @@ mod tests { to_asset: AssetId::from_token(Chain::Solana, "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), to_value: "999932".to_string(), provider: Some(SwapProvider::Jupiter.id().to_owned()), + swap_result: None, }; assert_eq!(transaction.metadata, Some(serde_json::to_value(expected).unwrap())); @@ -239,6 +242,7 @@ mod tests { to_asset: AssetId::from_token(Chain::Solana, "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), to_value: "1678930".to_string(), provider: Some(SwapProvider::Jupiter.id().to_owned()), + swap_result: None, }; assert_eq!(transaction.metadata, Some(serde_json::to_value(expected).unwrap())); diff --git a/crates/primitives/src/explorers/near_intents.rs b/crates/primitives/src/explorers/near_intents.rs index 7f76d28aa..37358bbee 100644 --- a/crates/primitives/src/explorers/near_intents.rs +++ b/crates/primitives/src/explorers/near_intents.rs @@ -1,5 +1,5 @@ -use crate::block_explorer::BlockExplorer; use super::near::NEAR_BLOCKS_BASE_URL; +use crate::block_explorer::BlockExplorer; pub struct NearIntents; diff --git a/crates/primitives/src/transaction_metadata_types.rs b/crates/primitives/src/transaction_metadata_types.rs index dbc739d7d..1077b7f33 100644 --- a/crates/primitives/src/transaction_metadata_types.rs +++ b/crates/primitives/src/transaction_metadata_types.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::{AssetId, NFTAssetId, PerpetualDirection, PerpetualProvider}; +use crate::{AssetId, NFTAssetId, PerpetualDirection, PerpetualProvider, swap::SwapResult}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[typeshare(swift = "Sendable")] @@ -22,6 +22,8 @@ pub struct TransactionSwapMetadata { pub to_asset: AssetId, pub to_value: String, pub provider: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub swap_result: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] From 94988db32616212ed6f88724519b1b6a122f69fa Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:18:32 +0900 Subject: [PATCH 2/4] expose mode, fix across deposit log index --- crates/swapper/src/across/api.rs | 19 ++++++++++++++++--- crates/swapper/src/models.rs | 23 ++++++++++++++--------- crates/swapper/src/proxy/provider.rs | 2 +- crates/swapper/src/swapper.rs | 14 +++++++------- crates/swapper/src/swapper_trait.rs | 2 +- gemstone/src/gem_swapper/remote_types.rs | 1 + 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/crates/swapper/src/across/api.rs b/crates/swapper/src/across/api.rs index 4d2cb6f55..b549ccbd8 100644 --- a/crates/swapper/src/across/api.rs +++ b/crates/swapper/src/across/api.rs @@ -7,6 +7,8 @@ use primitives::{Chain, swap::SwapStatus}; use serde::{Deserialize, Serialize}; use std::sync::Arc; +const FUNDS_DEPOSITED_TOPIC: &str = "0x32ed1a409ef04c7b0227189c3a103dc5ac10e775a15b785dcc510201f7c25ad3"; + #[derive(Debug, Clone)] pub struct AcrossApi { pub url: String, @@ -50,11 +52,22 @@ impl AcrossApi { .await .map_err(SwapperError::from)?; - if receipt.logs.len() < 2 || receipt.logs[1].topics.len() < 3 { - return Err(SwapperError::NetworkError("invalid tx receipt".into())); + let deposit_log = receipt + .logs + .iter() + .find(|log| { + log.topics + .first() + .map(|topic| topic.eq_ignore_ascii_case(FUNDS_DEPOSITED_TOPIC)) + .unwrap_or(false) + }) + .ok_or_else(|| SwapperError::NetworkError("FundsDeposited event not found".into()))?; + + if deposit_log.topics.len() < 3 { + return Err(SwapperError::NetworkError("invalid FundsDeposited topics".into())); } // The deposit ID is in topics[2] (topics[0] is event signature, topics[1] is destination chain ID) - let deposit_id_hex = receipt.logs[1].topics[2].clone(); + let deposit_id_hex = deposit_log.topics[2].clone(); // Convert hex deposit ID to decimal string let deposit_id = if let Some(stripped) = deposit_id_hex.strip_prefix("0x") { diff --git a/crates/swapper/src/models.rs b/crates/swapper/src/models.rs index 952a6bb78..058eb103d 100644 --- a/crates/swapper/src/models.rs +++ b/crates/swapper/src/models.rs @@ -1,10 +1,13 @@ use super::permit2_data::Permit2Data; use crate::{ - SwapperMode, SwapperProvider, SwapperProviderMode, SwapperQuoteAsset, SwapperSlippage, + SwapperMode, SwapperProvider, SwapperQuoteAsset, SwapperSlippage, config::{DEFAULT_SLIPPAGE_BPS, ReferralFees}, }; pub use primitives::swap::SwapResult; -use primitives::{AssetId, Chain, swap::ApprovalData}; +use primitives::{ + AssetId, Chain, + swap::{ApprovalData, SwapProviderMode}, +}; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] @@ -13,6 +16,7 @@ pub struct ProviderType { pub name: String, pub protocol: String, pub protocol_id: String, + pub mode: SwapProviderMode, } impl ProviderType { @@ -22,11 +26,12 @@ impl ProviderType { name: id.name().to_string(), protocol: id.protocol_name().to_string(), protocol_id: id.id().to_string(), + mode: ProviderType::mode(id), } } - pub fn mode(&self) -> SwapperProviderMode { - match self.id { + pub fn mode(id: SwapperProvider) -> SwapProviderMode { + match id { SwapperProvider::UniswapV3 | SwapperProvider::UniswapV4 | SwapperProvider::PancakeswapV3 @@ -39,11 +44,11 @@ impl ProviderType { | SwapperProvider::StonfiV2 | SwapperProvider::Reservoir | SwapperProvider::Aerodrome - | SwapperProvider::Orca => SwapperProviderMode::OnChain, - SwapperProvider::Mayan | SwapperProvider::Chainflip | SwapperProvider::NearIntents => SwapperProviderMode::CrossChain, - SwapperProvider::Thorchain => SwapperProviderMode::OmniChain(vec![Chain::Thorchain, Chain::Tron]), - SwapperProvider::Relay => SwapperProviderMode::OmniChain(vec![Chain::Hyperliquid, Chain::Manta, Chain::Berachain]), - SwapperProvider::Across | SwapperProvider::Hyperliquid => SwapperProviderMode::Bridge, + | SwapperProvider::Orca => SwapProviderMode::OnChain, + SwapperProvider::Mayan | SwapperProvider::Chainflip | SwapperProvider::NearIntents => SwapProviderMode::CrossChain, + SwapperProvider::Thorchain => SwapProviderMode::OmniChain(vec![Chain::Thorchain, Chain::Tron]), + SwapperProvider::Relay => SwapProviderMode::OmniChain(vec![Chain::Hyperliquid, Chain::Manta, Chain::Berachain]), + SwapperProvider::Across | SwapperProvider::Hyperliquid => SwapProviderMode::Bridge, } } } diff --git a/crates/swapper/src/proxy/provider.rs b/crates/swapper/src/proxy/provider.rs index 843717fc7..6e695b9ca 100644 --- a/crates/swapper/src/proxy/provider.rs +++ b/crates/swapper/src/proxy/provider.rs @@ -259,7 +259,7 @@ where } // For OnChain providers, use the default implementation _ => { - if self.provider.mode() == SwapperProviderMode::OnChain { + if self.provider.mode == SwapperProviderMode::OnChain { Ok(self.get_onchain_swap_status(chain, transaction_hash)) } else { Err(SwapperError::NotImplemented) diff --git a/crates/swapper/src/swapper.rs b/crates/swapper/src/swapper.rs index 646960d82..b27367c22 100644 --- a/crates/swapper/src/swapper.rs +++ b/crates/swapper/src/swapper.rs @@ -15,7 +15,7 @@ pub struct GemSwapper { impl GemSwapper { // filter provider types that does not support cross chain / bridge swaps - fn filter_by_provider_mode(mode: SwapperProviderMode, from_chain: Chain, to_chain: Chain) -> bool { + fn filter_by_provider_mode(mode: &SwapperProviderMode, from_chain: Chain, to_chain: Chain) -> bool { match mode { SwapperProviderMode::OnChain => from_chain == to_chain, SwapperProviderMode::Bridge | SwapperProviderMode::CrossChain => from_chain != to_chain, @@ -145,7 +145,7 @@ impl GemSwapper { let providers = self .swappers .iter() - .filter(|x| Self::filter_by_provider_mode(x.provider().mode(), from_chain, to_chain)) + .filter(|x| Self::filter_by_provider_mode(&x.provider().mode, from_chain, to_chain)) .filter(|x| Self::filter_by_supported_chains(x.supported_chains(), from_chain, to_chain)) .filter(|x| Self::filter_by_preferred_providers(preferred_providers, &x.provider().id)) .collect::>(); @@ -256,7 +256,7 @@ mod tests { // Cross chain swaps (same chain will be filtered out) let filtered = providers .iter() - .filter(|x| GemSwapper::filter_by_provider_mode(ProviderType::new(**x).mode(), Chain::Ethereum, Chain::Optimism)) + .filter(|x| GemSwapper::filter_by_provider_mode(&ProviderType::new(**x).mode, Chain::Ethereum, Chain::Optimism)) .cloned() .collect::>(); @@ -278,7 +278,7 @@ mod tests { let filtered = swappers .iter() - .filter(|x| GemSwapper::filter_by_provider_mode(x.provider().mode(), from_chain, to_chain)) + .filter(|x| GemSwapper::filter_by_provider_mode(&x.provider().mode, from_chain, to_chain)) .filter(|x| GemSwapper::filter_by_supported_chains(x.supported_chains(), from_chain, to_chain)) .collect::>(); @@ -289,7 +289,7 @@ mod tests { let filtered = swappers .iter() - .filter(|x| GemSwapper::filter_by_provider_mode(x.provider().mode(), from_chain, to_chain)) + .filter(|x| GemSwapper::filter_by_provider_mode(&x.provider().mode, from_chain, to_chain)) .filter(|x| GemSwapper::filter_by_supported_chains(x.supported_chains(), from_chain, to_chain)) .collect::>(); @@ -304,7 +304,7 @@ mod tests { let filtered = swappers .iter() - .filter(|x| GemSwapper::filter_by_provider_mode(x.provider().mode(), from_chain, to_chain)) + .filter(|x| GemSwapper::filter_by_provider_mode(&x.provider().mode, from_chain, to_chain)) .filter(|x| GemSwapper::filter_by_supported_chains(x.supported_chains(), from_chain, to_chain)) .collect::>(); @@ -316,7 +316,7 @@ mod tests { let filtered = swappers .iter() - .filter(|x| GemSwapper::filter_by_provider_mode(x.provider().mode(), from_chain, to_chain)) + .filter(|x| GemSwapper::filter_by_provider_mode(&x.provider().mode, from_chain, to_chain)) .filter(|x| GemSwapper::filter_by_supported_chains(x.supported_chains(), from_chain, to_chain)) .collect::>(); diff --git a/crates/swapper/src/swapper_trait.rs b/crates/swapper/src/swapper_trait.rs index 6cdd2808d..d67bc0fa4 100644 --- a/crates/swapper/src/swapper_trait.rs +++ b/crates/swapper/src/swapper_trait.rs @@ -18,7 +18,7 @@ pub trait Swapper: Send + Sync + Debug { } async fn fetch_quote_data(&self, quote: &Quote, data: FetchQuoteData) -> Result; async fn get_swap_result(&self, chain: Chain, transaction_hash: &str) -> Result { - if self.provider().mode() == SwapperProviderMode::OnChain { + if self.provider().mode == SwapperProviderMode::OnChain { Ok(self.get_onchain_swap_status(chain, transaction_hash)) } else { Err(SwapperError::NotImplemented) diff --git a/gemstone/src/gem_swapper/remote_types.rs b/gemstone/src/gem_swapper/remote_types.rs index 8af5613e1..9beb2f3e7 100644 --- a/gemstone/src/gem_swapper/remote_types.rs +++ b/gemstone/src/gem_swapper/remote_types.rs @@ -54,6 +54,7 @@ pub struct SwapperProviderType { pub name: String, pub protocol: String, pub protocol_id: String, + pub mode: SwapperProviderMode, } #[uniffi::remote(Record)] From ebd28cdf3e5fcb6e413a4bfdef323027343032f3 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:40:49 +0900 Subject: [PATCH 3/4] remote not used slippage --- crates/swapper/src/near_intents/model.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/swapper/src/near_intents/model.rs b/crates/swapper/src/near_intents/model.rs index 6844d1f3b..9d8c072da 100644 --- a/crates/swapper/src/near_intents/model.rs +++ b/crates/swapper/src/near_intents/model.rs @@ -116,8 +116,6 @@ pub struct SwapDetails { #[serde(default)] pub amount_out: Option, #[serde(default)] - pub slippage: Option, - #[serde(default)] pub origin_chain_tx_hashes: Vec, #[serde(default)] pub destination_chain_tx_hashes: Vec, From 4502dd2749516b1c16f637c40c4fcfbb7f855ad8 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:46:34 +0900 Subject: [PATCH 4/4] implement get_swap_result for hyperliquid builit in bridge --- crates/swapper/src/hyperliquid/provider.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/swapper/src/hyperliquid/provider.rs b/crates/swapper/src/hyperliquid/provider.rs index a14023436..9f7e14129 100644 --- a/crates/swapper/src/hyperliquid/provider.rs +++ b/crates/swapper/src/hyperliquid/provider.rs @@ -4,7 +4,10 @@ use async_trait::async_trait; use gem_hypercore::core::{HYPE_SYSTEM_ADDRESS, HYPERCORE_HYPE_TOKEN, actions::user::spot_send::SpotSend, hypercore::transfer_to_hyper_evm_typed_data}; use number_formatter::BigNumberFormatter; -use primitives::Chain; +use primitives::{ + Chain, + swap::{SwapResult, SwapStatus}, +}; use crate::{ FetchQuoteData, ProviderData, ProviderType, Quote, QuoteRequest, Route, Swapper, SwapperChainAsset, SwapperError, SwapperProvider, SwapperQuoteData, @@ -93,4 +96,16 @@ impl Swapper for HyperCoreBridge { _ => Err(SwapperError::NotSupportedChain), } } + + async fn get_swap_result(&self, chain: Chain, transaction_hash: &str) -> Result { + let from_chain = chain; + let to_chain = if chain == Chain::HyperCore { Chain::Hyperliquid } else { Chain::HyperCore }; + Ok(SwapResult { + status: SwapStatus::Completed, + from_chain, + from_tx_hash: transaction_hash.to_string(), + to_chain: Some(to_chain), + to_tx_hash: None, + }) + } }