diff --git a/core/crates/chain_providers/src/lib.rs b/core/crates/chain_providers/src/lib.rs index d8c67de05b5..93a4a134317 100644 --- a/core/crates/chain_providers/src/lib.rs +++ b/core/crates/chain_providers/src/lib.rs @@ -21,7 +21,7 @@ use gem_jsonrpc::grpc::ReqwestGrpcTransport; use gem_monad::MonadStakingClient; use gem_near::rpc::{NearClient, NearIndexer, NearProvider}; use gem_polkadot::rpc::{PolkadotClient, PolkadotIndexer, PolkadotProvider}; -use gem_solana::rpc::{SolanaClient, SolanaIndexer, SolanaProvider}; +use gem_solana::rpc::{SolanaClient, SolanaProvider}; use gem_stellar::rpc::client::StellarClient; use gem_sui::rpc::{SuiClient, SuiIndexer, SuiProvider}; use gem_tempo::TempoProvider; @@ -139,15 +139,7 @@ impl ProviderFactory { .with_default_headers(HashMap::from([("x-api-key".to_string(), config.indexers.subscan.key)])), )), )), - ChainType::Solana => Box::new(SolanaProvider::new( - SolanaClient::new(JsonRpcClient::new(gem_client.clone())), - Box::new(SolanaIndexer::new(JsonRpcClient::new(gem_client.with_base_url(alchemy_url( - chain, - &config.indexers.alchemy.url, - AlchemyApi::JsonRpc, - &config.indexers.alchemy.key, - ))))), - )), + ChainType::Solana => Box::new(SolanaProvider::new(SolanaClient::new(JsonRpcClient::new(gem_client)))), ChainType::Ton => Box::new(TonClient::new(gem_client)), ChainType::Tron => { let trongrid = TronGridClient::new(config.indexers.trongrid.configure_client(gem_client.clone()), config.indexers.trongrid.key); diff --git a/core/crates/gem_solana/src/jsonrpc.rs b/core/crates/gem_solana/src/jsonrpc.rs index eee9ce18d2f..08b9bbc6d08 100644 --- a/core/crates/gem_solana/src/jsonrpc.rs +++ b/core/crates/gem_solana/src/jsonrpc.rs @@ -80,6 +80,7 @@ pub enum SolanaRpc { GetProgramAccounts(String, Vec), GetProgramAccountsSlice(String, Vec, SolanaDataSlice), GetRecentPrioritizationFees(Vec), + GetSignaturesForAddress { address: String, limit: usize }, GetSlot(SolanaRpcConfig), GetSupply, GetTokenAccountsByOwner(String, SolanaTokenAccountsFilter), @@ -102,6 +103,7 @@ impl ToJsonRpcRequest for SolanaRpc { Self::GetMultipleAccounts(_) | Self::GetMultipleAccountsSlice(_, _) => method::GET_MULTIPLE_ACCOUNTS, Self::GetProgramAccounts(_, _) | Self::GetProgramAccountsSlice(_, _, _) => method::GET_PROGRAM_ACCOUNTS, Self::GetRecentPrioritizationFees(_) => method::GET_RECENT_PRIORITIZATION_FEES, + Self::GetSignaturesForAddress { .. } => method::GET_SIGNATURES_FOR_ADDRESS, Self::GetSlot(_) => method::GET_SLOT, Self::GetSupply => method::GET_SUPPLY, Self::GetTokenAccountsByOwner(_, _) => method::GET_TOKEN_ACCOUNTS_BY_OWNER, @@ -157,6 +159,7 @@ impl ToJsonRpcRequest for SolanaRpc { json!([addresses]) } } + Self::GetSignaturesForAddress { address, limit } => json!([address, confirmed_config(json!({ "limit": limit }))]), Self::GetSupply => json!([confirmed_config(json!({ "excludeNonCirculatingAccountsList": true, }))]), @@ -241,6 +244,15 @@ mod tests { ); } + #[test] + fn builds_signatures_for_address_request() { + assert_request( + SolanaRpc::GetSignaturesForAddress { address: "address".into(), limit: 2 }, + method::GET_SIGNATURES_FOR_ADDRESS, + json!(["address", {"commitment": "confirmed", "limit": 2}]), + ); + } + #[test] fn builds_configured_account_request() { assert_request( diff --git a/core/crates/gem_solana/src/method.rs b/core/crates/gem_solana/src/method.rs index d619e81b9af..a6d7b2506f3 100644 --- a/core/crates/gem_solana/src/method.rs +++ b/core/crates/gem_solana/src/method.rs @@ -8,6 +8,7 @@ pub const GET_LATEST_BLOCKHASH: &str = "getLatestBlockhash"; pub const GET_MULTIPLE_ACCOUNTS: &str = "getMultipleAccounts"; pub const GET_PROGRAM_ACCOUNTS: &str = "getProgramAccounts"; pub const GET_RECENT_PRIORITIZATION_FEES: &str = "getRecentPrioritizationFees"; +pub const GET_SIGNATURES_FOR_ADDRESS: &str = "getSignaturesForAddress"; pub const GET_SLOT: &str = "getSlot"; pub const GET_SUPPLY: &str = "getSupply"; pub const GET_TOKEN_ACCOUNTS_BY_OWNER: &str = "getTokenAccountsByOwner"; diff --git a/core/crates/gem_solana/src/models/transaction.rs b/core/crates/gem_solana/src/models/transaction.rs index 28e09065362..974dbf8ec96 100644 --- a/core/crates/gem_solana/src/models/transaction.rs +++ b/core/crates/gem_solana/src/models/transaction.rs @@ -5,6 +5,11 @@ use std::collections::{HashMap, HashSet}; use crate::models::token::{BigInt, TokenBalance, TokenBalanceChange}; +#[derive(Debug, Deserialize)] +pub struct TransactionSignature { + pub signature: String, +} + #[derive(Deserialize)] pub struct SolanaTransaction { pub meta: SolanaTransactionMeta, diff --git a/core/crates/gem_solana/src/provider/testkit.rs b/core/crates/gem_solana/src/provider/testkit.rs index eccbb3b82b3..5bcd4f1f847 100644 --- a/core/crates/gem_solana/src/provider/testkit.rs +++ b/core/crates/gem_solana/src/provider/testkit.rs @@ -1,5 +1,5 @@ #[cfg(feature = "chain_integration_tests")] -use crate::rpc::{SolanaClient, SolanaIndexer, SolanaProvider}; +use crate::rpc::{SolanaClient, SolanaProvider}; #[cfg(feature = "chain_integration_tests")] use gem_client::ReqwestClient; #[cfg(feature = "chain_integration_tests")] @@ -15,9 +15,5 @@ pub const TEST_TRANSACTION_ID: &str = "4dHnggcXjvmMJY2J6iGqse12PeCYQzuTySgwJa36K #[cfg(feature = "chain_integration_tests")] pub fn create_solana_test_client() -> SolanaProvider { let settings = get_test_settings(); - let alchemy_url = format!("{}/v2/{}", settings.indexer.alchemy.url.replace("{network}", "solana-mainnet"), settings.indexer.alchemy.key.secret); - SolanaProvider::new( - SolanaClient::new(JsonRpcClient::new(ReqwestClient::new_test_client(settings.chains.solana.url))), - Box::new(SolanaIndexer::new(JsonRpcClient::new(ReqwestClient::new_test_client(alchemy_url)))), - ) + SolanaProvider::new(SolanaClient::new(JsonRpcClient::new(ReqwestClient::new_test_client(settings.chains.solana.url)))) } diff --git a/core/crates/gem_solana/src/provider/transaction_mapper.rs b/core/crates/gem_solana/src/provider/transaction_mapper.rs index 059863f806b..8fa100fecaa 100644 --- a/core/crates/gem_solana/src/provider/transaction_mapper.rs +++ b/core/crates/gem_solana/src/provider/transaction_mapper.rs @@ -4,13 +4,14 @@ use num_bigint::{BigUint, Sign}; use crate::{ COMPUTE_BUDGET_PROGRAM_ID, JUPITER_PROGRAM_ID, MEMO_PROGRAM_ID, METAPLEX_CORE_PROGRAM, METAPLEX_PROGRAM, METEORA_DAMM_V2_PROGRAM_ID, METEORA_DLMM_PROGRAM_ID, OKX_DEX_V2_PROGRAM_ID, ORCA_WHIRLPOOL_PROGRAM_ID, PUMP_AMM_PROGRAM_ID, RAYDIUM_AMM_PROGRAM_ID, RAYDIUM_CLMM_PROGRAM_ID, RAYDIUM_CPMM_PROGRAM_ID, SYSTEM_PROGRAM_ID, SYSTEM_PROGRAMS, TOKEN_PROGRAM, TOKEN_PROGRAM_2022, WSOL_TOKEN_ADDRESS, - models::{BlockTransaction, BlockTransactions, Instruction, SingleTransaction, TokenBalanceChange}, + models::{BlockTransaction, BlockTransactions, Instruction, SingleTransaction, TokenBalanceChange, TransactionSignature}, }; use primitives::{ AssetId, Chain, NFTAssetId, OptionStringExt, SwapProvider, Transaction, TransactionNFTTransferMetadata, TransactionState, TransactionSwapMetadata, TransactionSwapReferralFee, TransactionType, swap::SOLANA_REFERRAL_ADDRESS, }; use super::parsers::ProtocolParsers; +use chain_traits::{TransactionIdRequest, TransactionsResult}; const CHAIN: Chain = Chain::Solana; const SWAP_PROGRAMS: &[(SwapProvider, &str)] = &[ @@ -174,6 +175,10 @@ fn map_referral_fee(transaction: &BlockTransaction, owner: &str) -> Option) -> TransactionsResult { + TransactionsResult::TransactionRequests(signatures.into_iter().map(|signature| TransactionIdRequest::new(CHAIN, signature.signature, None)).collect()) +} + pub fn map_block_transactions(transactions: &BlockTransactions) -> Vec { transactions.transactions.iter().filter_map(|transaction| map_transaction(transaction, transactions.block_time)).collect() } diff --git a/core/crates/gem_solana/src/provider/transactions.rs b/core/crates/gem_solana/src/provider/transactions.rs index fe14f10adf5..eee72b034ab 100644 --- a/core/crates/gem_solana/src/provider/transactions.rs +++ b/core/crates/gem_solana/src/provider/transactions.rs @@ -7,8 +7,8 @@ use primitives::Transaction; use crate::{ models::SingleTransaction, - provider::transaction_mapper::{map_block_transactions, map_single_transaction}, - rpc::{SolanaIndexer, SolanaProvider, constants::MISSING_BLOCKS_ERRORS}, + provider::transaction_mapper::{map_block_transactions, map_single_transaction, map_transaction_requests}, + rpc::{SolanaProvider, constants::MISSING_BLOCKS_ERRORS}, }; #[async_trait] @@ -35,13 +35,10 @@ impl ChainTransaction for SolanaProvider { } #[async_trait] -impl ChainTransactions for SolanaIndexer { +impl ChainTransactions for SolanaProvider { async fn get_transactions_by_address(&self, request: TransactionsRequest) -> Result> { let TransactionsRequest { address, limit, .. } = request; - let transaction_ids = self.get_transaction_ids_by_address(&address, limit).await?; - Ok(TransactionsResult::TransactionRequests( - transaction_ids.into_iter().map(|transaction_id| TransactionIdRequest::new(primitives::Chain::Solana, transaction_id, None)).collect(), - )) + Ok(map_transaction_requests(self.get_signatures_for_address(&address, limit).await?)) } } diff --git a/core/crates/gem_solana/src/rpc/client.rs b/core/crates/gem_solana/src/rpc/client.rs index 218b311a588..070e858e238 100644 --- a/core/crates/gem_solana/src/rpc/client.rs +++ b/core/crates/gem_solana/src/rpc/client.rs @@ -3,8 +3,12 @@ use crate::{ jsonrpc::{SolanaAccountEncoding, SolanaProgramAccountsFilter, SolanaRpc, SolanaRpcConfig, SolanaTokenAccountsFilter}, metaplex::{decode_metadata, metadata::Metadata}, models::{ - AccountData, EpochInfo, InflationRate, ResultTokenInfo, SupplyResult, TokenAccountInfo, ValueResult, VoteAccounts, balances::SolanaBalance, blockhash::SolanaBlockhashResult, prioritization_fee::SolanaPrioritizationFee, - simulation::SimulateTransactionResult, transaction::BlockTransactions, + AccountData, EpochInfo, InflationRate, ResultTokenInfo, SupplyResult, TokenAccountInfo, ValueResult, VoteAccounts, + balances::SolanaBalance, + blockhash::SolanaBlockhashResult, + prioritization_fee::SolanaPrioritizationFee, + simulation::SimulateTransactionResult, + transaction::{BlockTransactions, TransactionSignature}, }, }; #[cfg(feature = "rpc")] @@ -52,6 +56,10 @@ impl SolanaClient { self.client.request(SolanaRpc::GetTransaction(signature.to_string())).await } + pub async fn get_signatures_for_address(&self, address: &str, limit: usize) -> Result, JsonRpcError> { + self.client.request(SolanaRpc::GetSignaturesForAddress { address: address.to_string(), limit }).await + } + pub async fn get_genesis_hash(&self) -> Result { self.client.request(SolanaRpc::GetGenesisHash).await } diff --git a/core/crates/gem_solana/src/rpc/indexer/jsonrpc.rs b/core/crates/gem_solana/src/rpc/indexer/jsonrpc.rs deleted file mode 100644 index c9d35b7e82b..00000000000 --- a/core/crates/gem_solana/src/rpc/indexer/jsonrpc.rs +++ /dev/null @@ -1,29 +0,0 @@ -use gem_jsonrpc::types::ToJsonRpcRequest; -use serde_json::{Value, json}; - -use crate::COMMITMENT_CONFIRMED; - -pub(super) const GET_TRANSACTIONS_FOR_ADDRESS: &str = "getTransactionsForAddress"; - -pub(super) enum SolanaIndexerRpc { - GetTransactionsForAddress { address: String, limit: usize }, -} - -impl ToJsonRpcRequest for SolanaIndexerRpc { - fn method(&self) -> &'static str { - match self { - Self::GetTransactionsForAddress { .. } => GET_TRANSACTIONS_FOR_ADDRESS, - } - } - - fn params(&self) -> Value { - match self { - Self::GetTransactionsForAddress { address, limit } => json!([address, { - "transactionDetails": "signatures", - "sortOrder": "desc", - "limit": limit, - "commitment": COMMITMENT_CONFIRMED - }]), - } - } -} diff --git a/core/crates/gem_solana/src/rpc/indexer/mod.rs b/core/crates/gem_solana/src/rpc/indexer/mod.rs deleted file mode 100644 index 8d00f030253..00000000000 --- a/core/crates/gem_solana/src/rpc/indexer/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -mod jsonrpc; - -use std::error::Error; - -use gem_client::Client; -use gem_jsonrpc::client::JsonRpcClient; -use serde::Deserialize; - -use self::jsonrpc::SolanaIndexerRpc; - -#[derive(Debug, Deserialize)] -struct Transactions { - data: Vec, -} - -#[derive(Debug, Deserialize)] -struct Transaction { - signature: String, -} - -#[derive(Clone, Debug)] -pub struct SolanaIndexer { - client: JsonRpcClient, -} - -impl SolanaIndexer { - pub fn new(client: JsonRpcClient) -> Self { - Self { client } - } - - pub(crate) async fn get_transaction_ids_by_address(&self, address: &str, limit: usize) -> Result, Box> { - let transactions: Transactions = self.client.request(SolanaIndexerRpc::GetTransactionsForAddress { address: address.to_string(), limit }).await?; - Ok(transactions.data.into_iter().map(|transaction| transaction.signature).collect()) - } -} - -#[cfg(test)] -mod tests { - use gem_jsonrpc::testkit::mock_jsonrpc_client; - use primitives::testkit::json::load_json; - use serde_json::json; - - use super::jsonrpc::GET_TRANSACTIONS_FOR_ADDRESS; - use super::*; - use crate::COMMITMENT_CONFIRMED; - - #[tokio::test] - async fn test_get_transaction_ids_by_address() { - let client = mock_jsonrpc_client(|method, params| { - assert_eq!(method, GET_TRANSACTIONS_FOR_ADDRESS); - assert_eq!( - params, - &json!(["address", { - "transactionDetails": "signatures", - "sortOrder": "desc", - "limit": 2, - "commitment": COMMITMENT_CONFIRMED - }]) - ); - Ok(load_json(include_str!("../../../testdata/alchemy_get_transactions_for_address.json"))) - }); - let indexer = SolanaIndexer::new(client); - - let transaction_ids = indexer.get_transaction_ids_by_address("address", 2).await.unwrap(); - - assert_eq!(transaction_ids, vec!["signature-1", "signature-2"]); - } -} diff --git a/core/crates/gem_solana/src/rpc/mod.rs b/core/crates/gem_solana/src/rpc/mod.rs index 41ac092a1b6..2701b87e7ae 100644 --- a/core/crates/gem_solana/src/rpc/mod.rs +++ b/core/crates/gem_solana/src/rpc/mod.rs @@ -1,13 +1,9 @@ pub mod client; pub mod constants; #[cfg(feature = "rpc")] -mod indexer; -#[cfg(feature = "rpc")] mod provider; pub use client::SolanaClient; pub use constants::*; #[cfg(feature = "rpc")] -pub use indexer::SolanaIndexer; -#[cfg(feature = "rpc")] pub use provider::SolanaProvider; diff --git a/core/crates/gem_solana/src/rpc/provider.rs b/core/crates/gem_solana/src/rpc/provider.rs index fd66b7a3c76..5ae07e418d8 100644 --- a/core/crates/gem_solana/src/rpc/provider.rs +++ b/core/crates/gem_solana/src/rpc/provider.rs @@ -1,7 +1,6 @@ -use std::{error::Error, ops::Deref}; +use std::ops::Deref; -use async_trait::async_trait; -use chain_traits::{ChainAccount, ChainPerpetual, ChainProvider, ChainTransactions, EmptyTransactionsProvider, TransactionsRequest, TransactionsResult}; +use chain_traits::{ChainAccount, ChainPerpetual, ChainProvider}; use gem_client::Client; use primitives::Chain; @@ -9,16 +8,11 @@ use super::SolanaClient; pub struct SolanaProvider { client: SolanaClient, - indexer: Box, } impl SolanaProvider { - pub fn new(client: SolanaClient, indexer: Box) -> Self { - Self { client, indexer } - } - - pub fn new_rpc_only(client: SolanaClient) -> Self { - Self::new(client, Box::new(EmptyTransactionsProvider)) + pub fn new(client: SolanaClient) -> Self { + Self { client } } } @@ -30,13 +24,6 @@ impl Deref for SolanaProvider { } } -#[async_trait] -impl ChainTransactions for SolanaProvider { - async fn get_transactions_by_address(&self, request: TransactionsRequest) -> Result> { - self.indexer.get_transactions_by_address(request).await - } -} - impl ChainProvider for SolanaProvider { fn get_chain(&self) -> Chain { Chain::Solana diff --git a/core/crates/gem_solana/testdata/alchemy_get_transactions_for_address.json b/core/crates/gem_solana/testdata/alchemy_get_transactions_for_address.json deleted file mode 100644 index a1fe217a419..00000000000 --- a/core/crates/gem_solana/testdata/alchemy_get_transactions_for_address.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "data": [ - { - "signature": "signature-1" - }, - { - "signature": "signature-2" - } - ] -} diff --git a/core/gemstone/src/gateway/chain_factory.rs b/core/gemstone/src/gateway/chain_factory.rs index 9eee25dd87d..3300e6cd701 100644 --- a/core/gemstone/src/gateway/chain_factory.rs +++ b/core/gemstone/src/gateway/chain_factory.rs @@ -76,7 +76,7 @@ impl ChainClientFactory { ChainType::Polkadot => Ok(Arc::new(PolkadotProvider::new_rpc_only(PolkadotClient::new(alien_client)))), ChainType::Solana => { let client = JsonRpcClient::new(alien_client); - Ok(Arc::new(SolanaProvider::new_rpc_only(SolanaClient::new(client)))) + Ok(Arc::new(SolanaProvider::new(SolanaClient::new(client)))) } ChainType::Ethereum => { let evm_chain = EVMChain::from_chain(chain).unwrap(); diff --git a/core/gemstone/src/services/simulation.rs b/core/gemstone/src/services/simulation.rs index 031209879a2..2548fb9c1d6 100644 --- a/core/gemstone/src/services/simulation.rs +++ b/core/gemstone/src/services/simulation.rs @@ -154,7 +154,7 @@ impl GemSimulationService { let url = self.nodes.node_url(chain); let new_client = || new_alien_client(url.clone(), self.provider.clone()); match chain { - Chain::Solana => Ok(Box::new(SolanaProvider::new_rpc_only(SolanaClient::new(JsonRpcClient::new(new_client()))))), + Chain::Solana => Ok(Box::new(SolanaProvider::new(SolanaClient::new(JsonRpcClient::new(new_client()))))), Chain::Sui => { let transport = AlienGrpcTransport::new(Arc::new(AlienProviderWrapper::new(self.provider.clone()))); Ok(Box::new(SuiProvider::new_rpc_only(SuiClient::new_with_transport(url, Arc::new(transport))))) diff --git a/docs/FEATURES.md b/docs/FEATURES.md index d0450f0cde6..aecd6e07818 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -186,57 +186,57 @@ The table lists backend address-history providers by chain. Chains marked unsupp | Gem chain | Providers | | --- | --- | -| Bitcoin | [Bitcoin chain client](../core/crates/gem_bitcoin/src/rpc/client.rs) | -| Bitcoin Cash | [Bitcoin chain client](../core/crates/gem_bitcoin/src/rpc/client.rs) | -| Litecoin | [Bitcoin chain client](../core/crates/gem_bitcoin/src/rpc/client.rs) | +| Bitcoin | [Node](../core/crates/gem_bitcoin/src/rpc/client.rs) | +| Bitcoin Cash | [Node](../core/crates/gem_bitcoin/src/rpc/client.rs) | +| Litecoin | [Node](../core/crates/gem_bitcoin/src/rpc/client.rs) | | Ethereum | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | | BNB Chain | [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | -| Solana | [Alchemy](../core/crates/gem_solana/src/rpc/indexer/mod.rs) | +| Solana | [Node](../core/crates/gem_solana/src/rpc/client.rs) | | Polygon | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | -| THORChain | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | -| MayaChain | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | -| Cosmos | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | -| Osmosis | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | +| THORChain | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | +| MayaChain | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | +| Cosmos | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | +| Osmosis | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | | Arbitrum | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | -| TON | [TON chain client](../core/crates/gem_ton/src/rpc/client.rs) | -| Tron | [TronGrid account and TRC-20 transactions](../core/crates/gem_tron/src/rpc/trongrid/client.rs) | -| Dogecoin | [Bitcoin chain client](../core/crates/gem_bitcoin/src/rpc/client.rs) | -| Dash | [Bitcoin chain client](../core/crates/gem_bitcoin/src/rpc/client.rs) | -| Zcash | [Bitcoin chain client](../core/crates/gem_bitcoin/src/rpc/client.rs) | +| TON | [Node](../core/crates/gem_ton/src/rpc/client.rs) | +| Tron | [TronGrid](../core/crates/gem_tron/src/rpc/trongrid/client.rs) | +| Dogecoin | [Node](../core/crates/gem_bitcoin/src/rpc/client.rs) | +| Dash | [Node](../core/crates/gem_bitcoin/src/rpc/client.rs) | +| Zcash | [Node](../core/crates/gem_bitcoin/src/rpc/client.rs) | | Optimism | [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | -| Aptos | [Aptos chain client](../core/crates/gem_aptos/src/rpc/client.rs) | +| Aptos | [Node](../core/crates/gem_aptos/src/rpc/client.rs) | | Base | [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | | Avalanche C-Chain | [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | | Sui | [Sui GraphQL](../core/crates/gem_sui/src/rpc/indexer/mod.rs) | -| XRP Ledger | [XRP chain client](../core/crates/gem_xrp/src/rpc/client.rs) | +| XRP Ledger | [Node](../core/crates/gem_xrp/src/rpc/client.rs) | | opBNB | Unsupported | | Fantom | [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | | Gnosis | [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | -| Celestia | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | -| Injective | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | -| Sei | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | +| Celestia | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | +| Injective | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | +| Sei | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | | Sei EVM | Unsupported | | Manta | Unsupported | | Blast | [Routescan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | -| Noble | [Cosmos chain client](../core/crates/gem_cosmos/src/rpc/client.rs) | +| Noble | [Node](../core/crates/gem_cosmos/src/rpc/client.rs) | | ZKsync | [ZKsync Explorer](../core/crates/gem_evm/src/rpc/etherscan.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | | Linea | [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | | Mantle | Unsupported | | Celo | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | | NEAR | [FastNear](../core/crates/gem_near/src/rpc/indexer/mod.rs) | | World Chain | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | -| Stellar | [Stellar chain client](../core/crates/gem_stellar/src/rpc/client.rs) | +| Stellar | [Node](../core/crates/gem_stellar/src/rpc/client.rs) | | Sonic | Unsupported | | Algorand | [Algorand Indexer](../core/crates/gem_algorand/src/rpc/indexer/mod.rs) | | Polkadot | [Subscan](../core/crates/gem_polkadot/src/rpc/indexer/mod.rs) | | Plasma | Unsupported | -| Cardano | [Cardano chain client](../core/crates/gem_cardano/src/rpc/client.rs) | +| Cardano | [Node](../core/crates/gem_cardano/src/rpc/client.rs) | | Abstract | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | | Berachain | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | | Ink | [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | | Unichain | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | | Hyperliquid | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | -| HyperCore | [HyperCore chain client](../core/crates/gem_hypercore/src/provider/transactions.rs) | +| HyperCore | [Node](../core/crates/gem_hypercore/src/provider/transactions.rs) | | Monad | [Etherscan](../core/crates/gem_evm/src/rpc/etherscan.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) | | X Layer | [Ankr](../core/crates/gem_evm/src/rpc/ankr.rs) | | Robinhood Chain | [Blockscout](../core/crates/gem_evm/src/rpc/blockscout.rs), [Alchemy](../core/crates/gem_evm/src/rpc/alchemy.rs) |