Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
235282f
add yielder
0xh3rman Nov 21, 2025
c0f5276
add apy
0xh3rman Nov 23, 2025
fc4c798
code improvement
0xh3rman Dec 2, 2025
fce8d43
Refactor multicall3 usage and add YoVault position batching
0xh3rman Jan 6, 2026
96472fa
add name to yield postion
0xh3rman Jan 7, 2026
71b4900
Merge branch 'main' into yielder
0xh3rman Jan 7, 2026
c2780b7
code cleanup
0xh3rman Jan 7, 2026
5529436
more code cleanup
0xh3rman Jan 7, 2026
8409d3a
Add yield availability check and fix asset value calc
0xh3rman Jan 13, 2026
4b37dd8
merge main
0xh3rman Jan 14, 2026
020d924
Add multi-chain support for Yo yield provider
0xh3rman Jan 16, 2026
939d2aa
Merge branch 'main' into yielder
0xh3rman Jan 16, 2026
16dfa29
add yield build_transaction
0xh3rman Jan 18, 2026
72815dc
handle TransactionInputType::Yield in Gateway and preload
0xh3rman Jan 18, 2026
7ed3887
add approval, yield data
0xh3rman Jan 18, 2026
cd529d3
cleanup
0xh3rman Jan 18, 2026
8685781
add convert_to_shares
0xh3rman Jan 19, 2026
eda71ba
Merge remote-tracking branch 'origin/main' into yielder
0xh3rman Jan 20, 2026
1d670aa
Update quote_data_mapper.rs
0xh3rman Jan 20, 2026
80cc776
Update preload_mapper.rs
0xh3rman Jan 20, 2026
da52dd3
Add Yield to banner
0xh3rman Jan 20, 2026
a13b1d9
Merge remote-tracking branch 'origin' into yielder
0xh3rman Jan 20, 2026
2381088
fetch performance data
0xh3rman Jan 22, 2026
e483deb
fix fetch_rewards for ethereum
0xh3rman Jan 23, 2026
dd01b43
Merge branch 'main' into yielder
0xh3rman Jan 23, 2026
2b3ba0e
compile AssetMetaData
0xh3rman Jan 23, 2026
52b3e8e
remove is_earn_enabled from metadata
0xh3rman Jan 23, 2026
919b196
Merge branch 'main' into yielder
0xh3rman Jan 24, 2026
31a4635
code cleanup
0xh3rman Jan 24, 2026
9ea0029
Update GemstoneTest.kt
0xh3rman Jan 24, 2026
2330868
code cleanup
0xh3rman Jan 24, 2026
494b198
renaming variables and cleanup
0xh3rman Jan 26, 2026
ce59fd6
merge StakeData and YieldData and more cleanup
0xh3rman Jan 26, 2026
dfbe273
add risk level
0xh3rman Jan 26, 2026
7a08add
add back is_stake_enabled
0xh3rman Jan 26, 2026
6857799
Merge branch 'main' into yielder
0xh3rman Jan 27, 2026
131dee2
remove yo api client
0xh3rman Jan 28, 2026
9e6ab2d
Merge branch 'main' into yielder
0xh3rman Jan 28, 2026
348db89
Merge branch 'main' into yielder
0xh3rman Jan 29, 2026
9e01da9
Fix value_to method call in ThorChain
0xh3rman Jan 29, 2026
0564fbd
fix merge error
0xh3rman Jan 29, 2026
ad5b093
fix lint
0xh3rman Feb 1, 2026
d6c303d
Merge branch 'main' into yielder
0xh3rman Feb 1, 2026
994f8a4
fix merge error
0xh3rman Feb 1, 2026
3afce63
Merge branch 'main' into yielder
0xh3rman Feb 2, 2026
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
41 changes: 15 additions & 26 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,11 @@ Individual `gem_*` crates for each blockchain with unified RPC client patterns:

## Technology Stack

- Framework: Rust workspace with Rocket web framework
- Database: PostgreSQL (primary), Redis (caching)
- Message Queue: RabbitMQ with Lapin
- RPC: Custom `gem_jsonrpc` client library for blockchain interactions
- Mobile: UniFFI for iOS/Android bindings
- Serialization: Serde with custom serializers
- Async: Tokio runtime
- Testing: Built-in Rust testing with integration tests
- **Framework**: Rust workspace with Rocket, Tokio async runtime
- **Database**: PostgreSQL with Diesel ORM, Redis caching
- **Message Queue**: RabbitMQ with Lapin
- **Mobile**: UniFFI for iOS/Android bindings
- **Serialization**: Serde with custom serializers

## Development Workflow

Expand Down Expand Up @@ -164,6 +161,11 @@ Follow the existing code style patterns unless explicitly asked to change
### Commit Messages
- Write descriptive messages following conventional commit format

### Code Style
- **Prefer immutability**: Avoid `mut` when possible. Use functional patterns like `map()`, `filter()`, `fold()`, and method chaining instead of mutable accumulators
- **Minimal comments**: Do not add comments unless absolutely necessary. Code should be self-documenting through clear naming and structure. Comments are acceptable only for non-obvious business logic or external API quirks
- **No dead code**: Remove unused functions, variables, and imports immediately. Don't comment out code "for later"

### Naming and Conventions
- Files/modules: `snake_case` (e.g., `asset_id.rs`, `chain_address.rs`)
- Crates: Prefixed naming (`gem_*` for blockchains, `security_*` for security)
Expand Down Expand Up @@ -194,24 +196,18 @@ IMPORTANT: Always import models and types at the top of the file. Never use inli
### Database Patterns
- Separate database models from domain primitives
- Use `as_primitive()` methods for conversion
- Diesel ORM with PostgreSQL backend
- Support transactions and upserts

### Async Patterns
- Tokio runtime throughout
- Async client structs returning `Result<T, Error>`
- Use `Arc<tokio::sync::Mutex<T>>` for shared async state

## Architecture & Patterns

### Key Development Patterns
- One crate per blockchain using unified RPC client patterns
- UniFFI bindings require careful Rust API design for mobile compatibility
- Use `BigDecimal` for financial precision
- Use async/await with Tokio across services
- Database models use Diesel ORM with automatic migrations
- Consider cross-platform performance constraints for mobile
- Shared U256 conversions: prefer `u256_to_biguint` and `biguint_to_u256` from `crates/gem_evm/src/u256.rs` for Alloy `U256` <-> `BigUint` conversions.
- Consider cross-platform performance constraints for mobile (UniFFI bindings require careful Rust API design)
- Shared U256 conversions: prefer `u256_to_biguint` and `biguint_to_u256` from `crates/gem_evm/src/u256.rs` for Alloy `U256` <-> `BigUint` conversions

### Code Organization
- **Modular structure**: Break down long files into smaller, focused modules by logical responsibility
Expand Down Expand Up @@ -277,7 +273,6 @@ Direct repository access methods available on `DatabaseClient` include:
- **Use `primitives::hex`** for hex encoding/decoding (not `alloy_primitives::hex`)
- RPC calls expect hex strings directly; avoid double encoding
- Use `JsonRpcClient::batch_call()` for batch operations
- Propagate errors via `JsonRpcError`

### Blockchain Provider Patterns
- Each blockchain crate has a `provider/` directory with trait implementations
Expand All @@ -288,19 +283,13 @@ Direct repository access methods available on `DatabaseClient` include:

## Testing

### Conventions
- Place integration tests in `tests/` directories
- Place integration tests in `tests/` directories with layout: `src/`, `tests/`, `testdata/`
- Use `#[tokio::test]` for async tests
- Prefix test names descriptively with `test_`
- Use `Result<(), Box<dyn std::error::Error + Send + Sync>>` for test error handling
- Configure integration tests with `test = false` and appropriate `required-features` for manual execution
- Prefer real networks for RPC client tests (e.g., Ethereum mainnet)
- Test data management: For long JSON test data (>20 lines), store in `testdata/` and load with `include_str!()`; per-crate layout is typically `src/`, `tests/`, `testdata/`

### Integration Testing
- Add integration tests for RPC functionality to verify real network compatibility
- Prefer recent blocks for batch operations (more reliable than historical blocks)
- Verify both successful calls and proper error propagation
- Prefer real networks and recent blocks for RPC client tests
- Test data: store long JSON (>20 lines) in `testdata/` and load with `include_str!()`
- Use realistic contract addresses (e.g., USDC) for `eth_call` testing

## Task Completion
Expand Down
25 changes: 23 additions & 2 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 @@ -49,6 +49,7 @@ members = [
"crates/streamer",
"crates/swapper",
"crates/tracing",
"crates/yielder",
]

[workspace.dependencies]
Expand Down
1 change: 1 addition & 0 deletions apps/daemon/src/pusher/pusher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl Pusher {
title: localizer.notification_unfreeze_title(self.get_value(amount, asset.symbol.clone()).as_str()),
message: None,
}),
TransactionType::EarnDeposit | TransactionType::EarnWithdraw => Err("Earn transactions not implemented".into()),
}
}

Expand Down
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::alien::{AlienProvider, new_alien_client, reqwest_provider::NativeProvider};
use gem_jsonrpc::native_provider::NativeProvider;
use gemstone::alien::{AlienProvider, new_alien_client};
use gemstone::network::JsonRpcClient;
use num_bigint::BigInt;
use primitives::{Chain, PriorityFeeValue, fee::FeePriority};
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 @@ -20,8 +20,8 @@ use crate::{
jito::{JitoClient, JitoTipFloor},
solana_client::{JUPITER_PROGRAM, SolanaFeeData, SolanaGasClient},
};
use gem_jsonrpc::native_provider::NativeProvider;
use gem_evm::ether_conv::EtherConv;
use gemstone::alien::reqwest_provider::NativeProvider;
use primitives::fee::FeePriority;

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
Expand Down
4 changes: 2 additions & 2 deletions bin/gas-bench/src/solana_client.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::error::Error;
use std::sync::Arc;

use gem_jsonrpc::client::JsonRpcClient;
use gem_jsonrpc::{NativeProvider, client::JsonRpcClient};
use gem_solana::models::jito::{FeeStats, JitoTipEstimates, calculate_fee_stats, estimate_jito_tips};
use gem_solana::models::prioritization_fee::SolanaPrioritizationFee;
use gemstone::alien::{AlienProvider, new_alien_client, reqwest_provider::NativeProvider};
use gemstone::alien::{AlienProvider, new_alien_client};
use primitives::Chain;
use serde_json::json;

Expand Down
6 changes: 5 additions & 1 deletion crates/gem_aptos/src/rpc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ impl<C: Client> AptosClient<C> {
AssetSubtype::TOKEN => Ok(1500),
}
}
TransactionInputType::Swap(_, _, _) | TransactionInputType::Stake(_, _) | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) => Ok(1500),
TransactionInputType::Swap(_, _, _)
| TransactionInputType::Stake(_, _)
| TransactionInputType::TokenApprove(_, _)
| TransactionInputType::Generic(_, _, _)
| TransactionInputType::Yield(_, _, _) => Ok(1500),
TransactionInputType::Perpetual(_, _) => unimplemented!(),
}
}
Expand Down
18 changes: 12 additions & 6 deletions crates/gem_cosmos/src/provider/preload_mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt {
| TransactionInputType::Account(_, _)
| TransactionInputType::TokenApprove(_, _)
| TransactionInputType::Generic(_, _, _)
| TransactionInputType::Perpetual(_, _) => BigInt::from(3_000u64),
| TransactionInputType::Perpetual(_, _)
| TransactionInputType::Yield(_, _, _) => BigInt::from(3_000u64),
TransactionInputType::Swap(_, _, _) => BigInt::from(3_000u64),
TransactionInputType::Stake(_, _) => BigInt::from(25_000u64),
},
Expand All @@ -22,7 +23,8 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt {
| TransactionInputType::Account(_, _)
| TransactionInputType::TokenApprove(_, _)
| TransactionInputType::Generic(_, _, _)
| TransactionInputType::Perpetual(_, _) => BigInt::from(10_000u64),
| TransactionInputType::Perpetual(_, _)
| TransactionInputType::Yield(_, _, _) => BigInt::from(10_000u64),
TransactionInputType::Swap(_, _, _) => BigInt::from(10_000u64),
TransactionInputType::Stake(_, _) => BigInt::from(100_000u64),
},
Expand All @@ -33,7 +35,8 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt {
| TransactionInputType::Account(_, _)
| TransactionInputType::TokenApprove(_, _)
| TransactionInputType::Generic(_, _, _)
| TransactionInputType::Perpetual(_, _) => BigInt::from(3_000u64),
| TransactionInputType::Perpetual(_, _)
| TransactionInputType::Yield(_, _, _) => BigInt::from(3_000u64),
TransactionInputType::Swap(_, _, _) => BigInt::from(3_000u64),
TransactionInputType::Stake(_, _) => BigInt::from(10_000u64),
},
Expand All @@ -44,7 +47,8 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt {
| TransactionInputType::Account(_, _)
| TransactionInputType::TokenApprove(_, _)
| TransactionInputType::Generic(_, _, _)
| TransactionInputType::Perpetual(_, _) => BigInt::from(100_000u64),
| TransactionInputType::Perpetual(_, _)
| TransactionInputType::Yield(_, _, _) => BigInt::from(100_000u64),
TransactionInputType::Swap(_, _, _) => BigInt::from(100_000u64),
TransactionInputType::Stake(_, _) => BigInt::from(200_000u64),
},
Expand All @@ -55,7 +59,8 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt {
| TransactionInputType::Account(_, _)
| TransactionInputType::TokenApprove(_, _)
| TransactionInputType::Generic(_, _, _)
| TransactionInputType::Perpetual(_, _) => BigInt::from(100_000_000_000_000u64),
| TransactionInputType::Perpetual(_, _)
| TransactionInputType::Yield(_, _, _) => BigInt::from(100_000_000_000_000u64),
TransactionInputType::Swap(_, _, _) => BigInt::from(100_000_000_000_000u64),
TransactionInputType::Stake(_, _) => BigInt::from(1_000_000_000_000_000u64),
},
Expand All @@ -71,7 +76,8 @@ fn get_gas_limit(input_type: &TransactionInputType, _chain: CosmosChain) -> u64
| TransactionInputType::Account(_, _)
| TransactionInputType::TokenApprove(_, _)
| TransactionInputType::Generic(_, _, _)
| TransactionInputType::Perpetual(_, _) => 200_000,
| TransactionInputType::Perpetual(_, _)
| TransactionInputType::Yield(_, _, _) => 200_000,
TransactionInputType::Swap(_, _, _) => 200_000,
TransactionInputType::Stake(_, operation) => match operation {
StakeType::Stake(_) | StakeType::Unstake(_) => 1_000_000,
Expand Down
12 changes: 4 additions & 8 deletions crates/gem_evm/src/call_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@ pub struct DecodedCall {
pub fn decode_call(calldata: &str, abi: Option<&str>) -> Result<DecodedCall, Box<dyn Error + Send + Sync>> {
let calldata = hex::decode(calldata)?;

// Check minimum calldata length early
if calldata.len() < 4 {
return Err("Calldata too short".into());
}

// Try ERC20 interface first if no ABI provided
if abi.is_none()
&& let Ok(call) = IERC20Calls::abi_decode(&calldata)
{
let erc20_call = if abi.is_none() { IERC20Calls::abi_decode(&calldata).ok() } else { None };
if let Some(call) = erc20_call {
return Ok(call.into());
}

Expand Down Expand Up @@ -108,6 +105,7 @@ impl From<IERC20Calls> for DecodedCall {
IERC20Calls::name(_) => ("name", vec![]),
IERC20Calls::symbol(_) => ("symbol", vec![]),
IERC20Calls::decimals(_) => ("decimals", vec![]),
IERC20Calls::balanceOf(balance_of) => ("balanceOf", vec![("account", "address", balance_of.account.to_string())]),
IERC20Calls::allowance(allowance) => (
"allowance",
vec![("owner", "address", allowance.owner.to_string()), ("spender", "address", allowance.spender.to_string())],
Expand Down Expand Up @@ -148,7 +146,6 @@ mod tests {

#[test]
fn test_decode_custom_abi() {
// Using ERC721 safeTransferFrom as test case
let calldata = "0x42842e0e0000000000000000000000008ba1f109551bd432803012645aac136c0c3def25000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909000000000000000000000000000000000000000000000000000000000000007b";
let abi = r#"[
{
Expand Down Expand Up @@ -190,8 +187,7 @@ mod tests {

#[test]
fn test_decode_short_calldata() {
// Test that short calldata returns proper error
let result = decode_call("0x1234", None); // Only 2 bytes, need 4
let result = decode_call("0x1234", None);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Calldata too short"));
}
Expand Down
1 change: 1 addition & 0 deletions crates/gem_evm/src/contracts/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ sol! {
function name() public view virtual returns (string memory);
function symbol() public view virtual returns (string memory);
function decimals() public view virtual returns (uint8);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);

function transfer(address to, uint256 value) external returns (bool);
Expand Down
Loading
Loading