From 679f7facf7ecfc1aef9fe04de8db8ee0a600f50d Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Sat, 7 Mar 2026 00:05:24 +0900 Subject: [PATCH 1/2] Support Stellar token IDs with 'issuer::code' Allow Stellar token IDs to be either a 56-char issuer or an issuer::symbol pair when formatting token IDs. Update mapping to build Stellar asset IDs via AssetId::sub_token_id instead of the previous "{code}-{issuer}" string, and update the USDC constant to include the ::USDC suffix. Remove the now-unused map_token_balances_by_ids helper and add tests for the new Stellar format. Files changed: chain_primitives/src/token_id.rs, gem_stellar/src/provider/balances_mapper.rs, primitives/src/asset_constants.rs. --- crates/chain_primitives/src/token_id.rs | 26 ++++++++++++++++++- .../src/provider/balances_mapper.rs | 26 +------------------ crates/primitives/src/asset_constants.rs | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/chain_primitives/src/token_id.rs b/crates/chain_primitives/src/token_id.rs index 4c4596ef5..4d03524c6 100644 --- a/crates/chain_primitives/src/token_id.rs +++ b/crates/chain_primitives/src/token_id.rs @@ -54,7 +54,15 @@ pub fn format_token_id(chain: Chain, token_id: String) -> Option { None } } - Chain::Stellar => (token_id.len() == 56 && token_id.starts_with('G')).then_some(token_id), + Chain::Stellar => { + if let Some((issuer, symbol)) = token_id.split_once("::") { + (issuer.len() == 56 && issuer.starts_with('G') && !symbol.is_empty()).then_some(token_id) + } else if token_id.len() == 56 && token_id.starts_with('G') { + Some(token_id) + } else { + None + } + } Chain::Bitcoin | Chain::BitcoinCash | Chain::Litecoin @@ -132,4 +140,20 @@ mod tests { Some("rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz".to_string()) ); } + + #[test] + fn test_format_token_id_stellar() { + let chain = Chain::Stellar; + + assert_eq!( + format_token_id(chain, "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN::USDC".to_string()), + Some("GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN::USDC".to_string()) + ); + assert_eq!( + format_token_id(chain, "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN".to_string()), + Some("GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN".to_string()) + ); + assert_eq!(format_token_id(chain, "invalid".to_string()), None); + assert_eq!(format_token_id(chain, "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN::".to_string()), None); + } } diff --git a/crates/gem_stellar/src/provider/balances_mapper.rs b/crates/gem_stellar/src/provider/balances_mapper.rs index a38aa44a2..07d6da964 100644 --- a/crates/gem_stellar/src/provider/balances_mapper.rs +++ b/crates/gem_stellar/src/provider/balances_mapper.rs @@ -61,7 +61,7 @@ pub fn map_all_balances(chain: Chain, account: Account) -> Vec { "credit_alphanum4" | "credit_alphanum12" => { // Token balances if let (Some(asset_issuer), Some(asset_code)) = (&balance.asset_issuer, &balance.asset_code) { - let token_id = format!("{}-{}", asset_code, asset_issuer); + let token_id = AssetId::sub_token_id(&[asset_issuer.clone(), asset_code.clone()]); let asset_id = AssetId::from_token(chain, &token_id); if let Ok(value) = BigNumberFormatter::value_from_amount_biguint(&balance.balance, STELLAR_DECIMALS) { let balance_obj = Balance::coin_balance(value); @@ -78,30 +78,6 @@ pub fn map_all_balances(chain: Chain, account: Account) -> Vec { balances } -pub fn map_token_balances_by_ids(chain: Chain, account: &Account, token_ids: &[String]) -> Vec { - let mut result = Vec::new(); - for token_id in token_ids { - if let Some(balance) = account.balances.iter().find(|b| { - if let (Some(asset_issuer), Some(asset_code)) = (&b.asset_issuer, &b.asset_code) { - let balance_token_id = format!("{}-{}", asset_code, asset_issuer); - balance_token_id == *token_id && b.asset_type != "native" - } else { - false - } - }) { - if let Ok(amount) = BigNumberFormatter::value_from_amount_biguint(&balance.balance, STELLAR_DECIMALS) { - let asset_id = AssetId::from_token(chain, token_id); - let balance_obj = Balance::coin_balance(amount); - result.push(AssetBalance::new_with_active(asset_id, balance_obj, true)); - } - } else { - let asset_id = AssetId::from_token(chain, token_id); - let balance_obj = Balance::coin_balance(BigUint::from(0u32)); - result.push(AssetBalance::new_with_active(asset_id, balance_obj, false)); - } - } - result -} #[cfg(test)] mod tests { diff --git a/crates/primitives/src/asset_constants.rs b/crates/primitives/src/asset_constants.rs index 9f2ab300f..fc7696301 100644 --- a/crates/primitives/src/asset_constants.rs +++ b/crates/primitives/src/asset_constants.rs @@ -114,7 +114,7 @@ pub const USDT_BERA_ASSET_ID: &str = "berachain_0x779Ded0c9e1022225f8e0630b35a9b pub const USDT_GNOSIS_ASSET_ID: &str = "gnosis_0x4ECaBa5870353805a9F068101A40E0f32ed605C6"; // Stellar -pub const USDC_STELLAR_ASSET_ID: &str = "stellar_GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"; +pub const USDC_STELLAR_ASSET_ID: &str = "stellar_GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN::USDC"; // XLayer pub const USDC_XLAYER_ASSET_ID: &str = "xlayer_0x74b7f16337b8972027F6196A17a631aC6dE26d22"; From ea2484c74c102a4458ce866c139698065c643fa7 Mon Sep 17 00:00:00 2001 From: gemcoder21 <104884878+gemcoder21@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:36:20 +0000 Subject: [PATCH 2/2] Require Stellar token IDs include symbol Tighten Stellar token validation in format_token_id by removing the fallback that accepted bare Stellar account IDs (56 chars starting with 'G'). Now only the issuer::symbol form is considered valid. Updated unit test to reflect that a standalone Stellar public key is no longer accepted. --- crates/chain_primitives/src/token_id.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/chain_primitives/src/token_id.rs b/crates/chain_primitives/src/token_id.rs index 4d03524c6..7a80b8a34 100644 --- a/crates/chain_primitives/src/token_id.rs +++ b/crates/chain_primitives/src/token_id.rs @@ -57,8 +57,6 @@ pub fn format_token_id(chain: Chain, token_id: String) -> Option { Chain::Stellar => { if let Some((issuer, symbol)) = token_id.split_once("::") { (issuer.len() == 56 && issuer.starts_with('G') && !symbol.is_empty()).then_some(token_id) - } else if token_id.len() == 56 && token_id.starts_with('G') { - Some(token_id) } else { None } @@ -151,7 +149,7 @@ mod tests { ); assert_eq!( format_token_id(chain, "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN".to_string()), - Some("GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN".to_string()) + None ); assert_eq!(format_token_id(chain, "invalid".to_string()), None); assert_eq!(format_token_id(chain, "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN::".to_string()), None);