Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1f25a5b
Refactor swapper providers to use rpc_provider field
0xh3rman Oct 2, 2025
8853d4b
Refactor swapper clients to use generic HTTP and RPC clients
0xh3rman Oct 2, 2025
5418898
Refactor network and RPC clients; update Sui and Tron integration
0xh3rman Oct 3, 2025
a8696ca
Refactor imports and add Default for HyperCoreBridge
0xh3rman Oct 3, 2025
6eaed59
Merge branch 'main' into swapper-gem-client
0xh3rman Oct 6, 2025
ffb618c
Merge branch 'main' into swapper-gem-client
0xh3rman Oct 7, 2025
39a9745
adding swapper crate
0xh3rman Oct 7, 2025
2c1256d
adding swapper crate
0xh3rman Oct 7, 2025
2cf5141
remove SuiRpcClient
0xh3rman Oct 7, 2025
357a83c
refactor client_factory
0xh3rman Oct 7, 2025
2c7be93
remove Alien prefixes
0xh3rman Oct 7, 2025
a5e76ea
remove swapper code from gemstone
0xh3rman Oct 7, 2025
5a66112
re-add GemSwapper and fix iOS build
0xh3rman Oct 7, 2025
27194f4
Merge branch 'main' into swapper-gem-client
0xh3rman Oct 7, 2025
7f27677
cleanup Cargo.toml, add back test
0xh3rman Oct 7, 2025
874ce6b
Merge branch 'swapper-gem-client' of github.com:gemwalletcom/core int…
0xh3rman Oct 7, 2025
129c616
reuse NativeProvider
0xh3rman Oct 7, 2025
bb8bc2d
refactor AlienClient
0xh3rman Oct 7, 2025
7ac014b
Merge branch 'main' into swapper-gem-client
0xh3rman Oct 7, 2025
5a2ddb4
remove tron_client
0xh3rman Oct 7, 2025
c26f7c1
add missing permit2_data_to_eip712_json export
0xh3rman Oct 7, 2025
f42d75d
add back SwapProviderConfig
0xh3rman Oct 7, 2025
8209e85
Merge branch 'main' into swapper-gem-client
0xh3rman Oct 7, 2025
3cf19b8
Update ci.yml
0xh3rman Oct 7, 2025
331158b
review comments
0xh3rman Oct 8, 2025
eb30300
cleanup
0xh3rman Oct 8, 2025
c053651
Merge branch 'main' into swapper-gem-client
0xh3rman Oct 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ jobs:

- name: Build all integration tests only
run: |
just --unstable gemstone build-integration-tests
just --unstable build-integration-tests

- name: Check gemstone dependencies
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Gem Wallet Core is a Rust-based cryptocurrency wallet backend engine supporting

### Cross-Platform Library (`gemstone/`)
Shared Rust library compiled to iOS Swift Package and Android AAR using UniFFI bindings. Contains blockchain RPC clients, swap integrations, payment URI decoding, and message signing.
- Key module: `gemstone::swapper` — swapper module for on-device swap integrations
- Key module: `gemstone::gem_swapper` — swapper module for on-device swap integrations

### Blockchain Support
Individual `gem_*` crates for each blockchain with unified RPC client patterns:
Expand Down
67 changes: 54 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ members = [
"crates/number_formatter",
"crates/pricer_dex",
"crates/streamer",
"crates/swapper",
"crates/tracing",
]

Expand Down
29 changes: 25 additions & 4 deletions apps/api/src/assets/cilent.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::error::Error;

use primitives::{Asset, AssetBasic, AssetFull, AssetId, ChainAddress, NFTCollection, Perpetual};
use search_index::{ASSETS_INDEX_NAME, AssetDocument, NFTS_INDEX_NAME, NFTDocument, PERPETUALS_INDEX_NAME, PerpetualDocument, SearchIndexClient};
use search_index::{ASSETS_INDEX_NAME, AssetDocument, NFTDocument, NFTS_INDEX_NAME, PERPETUALS_INDEX_NAME, PerpetualDocument, SearchIndexClient};
use storage::DatabaseClient;

pub struct AssetsClient {
Expand Down Expand Up @@ -84,7 +84,14 @@ impl SearchClient {

let assets: Vec<AssetDocument> = self
.client
.search(ASSETS_INDEX_NAME, &request.query, &build_filter(filters), [].as_ref(), request.limit, request.offset)
.search(
ASSETS_INDEX_NAME,
&request.query,
&build_filter(filters),
[].as_ref(),
request.limit,
request.offset,
)
.await?;

Ok(assets.into_iter().map(|x| AssetBasic::new(x.asset, x.properties, x.score)).collect())
Expand All @@ -93,7 +100,14 @@ impl SearchClient {
pub async fn get_perpetuals_search(&self, request: &SearchRequest) -> Result<Vec<Perpetual>, Box<dyn Error + Send + Sync>> {
let perpetuals: Vec<PerpetualDocument> = self
.client
.search(PERPETUALS_INDEX_NAME, &request.query, &build_filter(vec![]), [].as_ref(), request.limit, request.offset)
.search(
PERPETUALS_INDEX_NAME,
&request.query,
&build_filter(vec![]),
[].as_ref(),
request.limit,
request.offset,
)
.await?;

Ok(perpetuals.into_iter().map(|x| x.perpetual).collect())
Expand All @@ -102,7 +116,14 @@ impl SearchClient {
pub async fn get_nfts_search(&self, request: &SearchRequest) -> Result<Vec<NFTCollection>, Box<dyn Error + Send + Sync>> {
let nfts: Vec<NFTDocument> = self
.client
.search(NFTS_INDEX_NAME, &request.query, &build_filter(vec![]), [].as_ref(), request.limit, request.offset)
.search(
NFTS_INDEX_NAME,
&request.query,
&build_filter(vec![]),
[].as_ref(),
request.limit,
request.offset,
)
.await?;

Ok(nfts.into_iter().map(|x| x.collection).collect())
Expand Down
3 changes: 2 additions & 1 deletion apps/daemon/src/consumers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ pub async fn run_consumer_fetch_nft_associations(settings: Settings, database: A
let nft_client = NFTClient::new(&settings.postgres.url, nft_config).await;
let nft_client = Arc::new(Mutex::new(nft_client));
let consumer = FetchNftAssetsAddressesConsumer::new(database.clone(), stream_producer, cacher, nft_client);
streamer::run_consumer::<ChainAddressPayload, FetchNftAssetsAddressesConsumer, usize>(&name, stream_reader, queue, consumer, ConsumerConfig::default()).await
streamer::run_consumer::<ChainAddressPayload, FetchNftAssetsAddressesConsumer, usize>(&name, stream_reader, queue, consumer, ConsumerConfig::default())
.await
}

pub async fn run_consumer_support(settings: Settings, _database: Arc<Mutex<DatabaseClient>>) -> Result<(), Box<dyn Error + Send + Sync>> {
Expand Down
6 changes: 1 addition & 5 deletions apps/daemon/src/worker/search/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,5 @@ pub async fn jobs(settings: Settings) -> Vec<Pin<Box<dyn Future<Output = ()> + S
}
});

vec![
Box::pin(assets_index_updater),
Box::pin(perpetuals_index_updater),
Box::pin(nfts_index_updater),
]
vec![Box::pin(assets_index_updater), Box::pin(perpetuals_index_updater), Box::pin(nfts_index_updater)]
}
3 changes: 2 additions & 1 deletion bin/gas-bench/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::error::Error;
use gem_evm::fee_calculator::FeeCalculator;
use gem_evm::models::fee::EthereumFeeHistory;
use gem_evm::{ether_conv::EtherConv, jsonrpc::EthereumRpc};
use gemstone::network::{alien_provider::NativeProvider, jsonrpc_client_with_chain};
use gemstone::alien::reqwest_provider::NativeProvider;
use gemstone::network::jsonrpc_client_with_chain;
use num_bigint::BigInt;
use primitives::{Chain, PriorityFeeValue, fee::FeePriority};
use std::fmt::Display;
Expand Down
2 changes: 1 addition & 1 deletion bin/gas-bench/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
etherscan::EtherscanClient,
gasflow::GasflowClient,
};
use gemstone::network::alien_provider::NativeProvider;
use gemstone::alien::reqwest_provider::NativeProvider;
use primitives::fee::FeePriority;

#[derive(Debug, Clone)]
Expand Down
2 changes: 1 addition & 1 deletion crates/gem_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ serde_json = { workspace = true }
serde_urlencoded = { workspace = true }
reqwest = { workspace = true, optional = true }
hex = { workspace = true }
tokio = { workspace = true, features = ["time"], optional = true }
tokio = { workspace = true, features = ["time"], optional = true }
45 changes: 0 additions & 45 deletions crates/gem_client/src/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ use std::time::Duration;
#[cfg(feature = "reqwest")]
use tokio::time::sleep;

/// Create a retry policy for API requests that handles common HTTP error scenarios
///
/// NOTE: This uses reqwest's built-in retry mechanism which does NOT implement exponential backoff.
/// It will retry immediately without delays, which may not be suitable for rate limiting scenarios.
/// For rate limiting with proper backoff, use the `retry()` function instead.
pub fn retry_policy<S>(host: S, max_retries: u32) -> retry::Builder
where
S: for<'a> PartialEq<&'a str> + Send + Sync + 'static,
Expand All @@ -27,46 +22,6 @@ where
})
}

/// Retry policy with exponential backoff for rate limiting and transient errors
///
/// This function provides proper exponential backoff (2^attempt seconds) for handling
/// HTTP errors and other transient failures. Uses async sleep when reqwest
/// feature is enabled, otherwise falls back to blocking sleep.
///
/// # Arguments
/// * `operation` - A closure that returns a Future to be retried
/// * `max_retries` - Maximum number of retry attempts
/// * `should_retry_fn` - Optional predicate function to determine if error should trigger retry
/// If None, defaults to clearly transient errors (429, 502, 503, 504, throttling)
///
/// # Example
/// ```no_run
/// use gem_client::retry::{retry, default_should_retry};
///
/// #[tokio::main]
/// async fn main() {
/// // Retry on clearly transient errors (429, 502, 503, 504, throttling) - default behavior
/// let result = retry(
/// || async { Ok::<(), String>(()) },
/// 3,
/// None
/// ).await;
///
/// // Custom retry logic
/// let result = retry(
/// || async { Ok::<(), String>(()) },
/// 3,
/// Some(|error: &String| error.contains("429"))
/// ).await;
///
/// // Use explicit predefined function
/// let result = retry(
/// || async { Ok::<(), String>(()) },
/// 3,
/// Some(default_should_retry)
/// ).await;
/// }
/// ```
pub async fn retry<T, E, F, Fut, P>(operation: F, max_retries: u32, should_retry_fn: Option<P>) -> Result<T, E>
where
F: Fn() -> Fut,
Expand Down
2 changes: 1 addition & 1 deletion crates/gem_evm/src/provider/preload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl<C: Client + Clone> EthereumClient<C> {
let gas_estimate = {
let estimate = self
.estimate_gas(
&input.sender_address,
Some(&input.sender_address),
&to,
Some(&bigint_to_hex_string(&value)),
Some(&bytes_to_hex_string(&data)),
Expand Down
2 changes: 1 addition & 1 deletion crates/gem_evm/src/rpc/ankr/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde_json::json;

use crate::rpc::ankr::model::{TokenBalances, Transactions, ankr_chain};

#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct AnkrClient<C: Client + Clone> {
pub chain: EVMChain,
rpc_client: GenericJsonRpcClient<C>,
Expand Down
22 changes: 18 additions & 4 deletions crates/gem_evm/src/rpc/client.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use alloy_primitives::{Address, Bytes, hex};
use gem_client::Client;
use gem_jsonrpc::client::JsonRpcClient as GenericJsonRpcClient;
use gem_jsonrpc::types::{JsonRpcError, JsonRpcResult};
use gem_jsonrpc::types::{ERROR_INTERNAL_ERROR, JsonRpcError, JsonRpcResult};

use num_bigint::{BigInt, Sign};
use serde::de::DeserializeOwned;
use serde_json::json;
use serde_serializers::biguint_from_hex_str;
use std::any::TypeId;
use std::str::FromStr;

Expand All @@ -27,7 +29,7 @@ pub const FUNCTION_ERC20_NAME: &str = "0x06fdde03";
pub const FUNCTION_ERC20_SYMBOL: &str = "0x95d89b41";
pub const FUNCTION_ERC20_DECIMALS: &str = "0x313ce567";

#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct EthereumClient<C: Client + Clone> {
pub chain: EVMChain,
pub client: GenericJsonRpcClient<C>,
Expand Down Expand Up @@ -187,6 +189,15 @@ impl<C: Client + Clone> EthereumClient<C> {
self.client.call("eth_getBalance", params).await
}

pub async fn gas_price(&self) -> Result<BigInt, JsonRpcError> {
let value: String = self.client.call("eth_gasPrice", json!([])).await?;
let biguint = biguint_from_hex_str(&value).map_err(|_| JsonRpcError {
code: ERROR_INTERNAL_ERROR,
message: format!("Failed to parse gas price: {value}"),
})?;
Ok(BigInt::from_biguint(Sign::Plus, biguint))
}

pub async fn get_chain_id(&self) -> Result<String, JsonRpcError> {
self.client.call("eth_chainId", json!([])).await
}
Expand Down Expand Up @@ -236,12 +247,15 @@ impl<C: Client + Clone> EthereumClient<C> {
Ok(self.client.batch_call::<String>(calls).await?.extract())
}

pub async fn estimate_gas(&self, from: &str, to: &str, value: Option<&str>, data: Option<&str>) -> Result<String, JsonRpcError> {
pub async fn estimate_gas(&self, from: Option<&str>, to: &str, value: Option<&str>, data: Option<&str>) -> Result<String, JsonRpcError> {
let mut params_obj = json!({
"from": from,
"to": to
});

if let Some(from) = from {
params_obj["from"] = json!(from);
}

if let Some(value) = value {
params_obj["value"] = json!(value);
}
Expand Down
5 changes: 4 additions & 1 deletion crates/gem_jsonrpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = { workspace = true }
[features]
default = ["types"]
types = []
client = ["dep:gem_client"]
client = ["dep:gem_client", "dep:async-trait", "dep:primitives", "dep:hex"]
reqwest = ["client", "gem_client/reqwest", "dep:reqwest"]

[dependencies]
Expand All @@ -15,3 +15,6 @@ serde_json = { workspace = true }

gem_client = { path = "../gem_client", optional = true }
reqwest = { workspace = true, optional = true }
async-trait = { workspace = true, optional = true }
primitives = { path = "../primitives", optional = true }
hex = { workspace = true, optional = true }
5 changes: 5 additions & 0 deletions crates/gem_jsonrpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ pub mod types;
pub mod client;
#[cfg(feature = "client")]
pub use client::*;

#[cfg(feature = "client")]
pub mod rpc;
#[cfg(feature = "client")]
pub use rpc::{HttpMethod, RpcClient, RpcProvider, Target, X_CACHE_TTL};
Loading
Loading