Skip to content

Yo yielder integration#1008

Open
gemdev111 wants to merge 23 commits intomainfrom
yo-yielder-intergration
Open

Yo yielder integration#1008
gemdev111 wants to merge 23 commits intomainfrom
yo-yielder-intergration

Conversation

@gemdev111
Copy link
Contributor

No description provided.

@gemdev111 gemdev111 self-assigned this Mar 12, 2026
@semanticdiff-com
Copy link

semanticdiff-com bot commented Mar 12, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  gemstone/src/gateway/mod.rs  27% smaller
  Cargo.lock Unsupported file format
  Cargo.toml Unsupported file format
  crates/primitives/src/earn_type.rs  0% smaller
  crates/yielder/Cargo.toml Unsupported file format
  crates/yielder/src/client_factory.rs  0% smaller
  crates/yielder/src/error.rs  0% smaller
  crates/yielder/src/lib.rs  0% smaller
  crates/yielder/src/provider.rs  0% smaller
  crates/yielder/src/yielder.rs  0% smaller
  crates/yielder/src/yo/assets.rs  0% smaller
  crates/yielder/src/yo/client.rs  0% smaller
  crates/yielder/src/yo/contract.rs  0% smaller
  crates/yielder/src/yo/mod.rs  0% smaller
  crates/yielder/src/yo/provider.rs  0% smaller
  gemstone/Cargo.toml Unsupported file format

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new yielder crate to the project, enabling integration with yield-earning protocols, specifically 'Yo'. The yielder crate provides core logic for managing yield assets, fetching user positions, and preparing contract call data for deposit and withdrawal operations. The GemGateway has been updated to incorporate this new functionality, allowing the application to interact with yield providers and offer earn-related features to users.

Highlights

  • New yielder crate introduced: A new Rust crate, crates/yielder, has been added to the workspace, providing core logic for yield earning functionalities.
  • Yo integration for yield earning: The yielder crate integrates with 'Yo' to manage yield assets, track user positions, and facilitate deposit/withdrawal operations.
  • GemGateway updated with yield features: The GemGateway now includes a Yielder instance and its earn-related methods (get_balance_earn, get_earn_data, get_earn_providers, get_earn_positions) have been updated to leverage the new yielder crate.
  • EarnType extended: The EarnType enum in crates/primitives has been extended with provider() and provider_id() methods to help identify the associated yield provider.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • Cargo.lock
    • Added yielder package and its dependencies.
  • Cargo.toml
    • Added crates/yielder to the workspace members.
  • crates/primitives/src/earn_type.rs
    • Added provider and provider_id methods to EarnType.
  • crates/yielder/Cargo.toml
    • Added a new Cargo.toml file for the yielder crate, defining its package metadata and dependencies.
  • crates/yielder/src/client_factory.rs
    • Added a new module to create Ethereum clients for yield providers.
  • crates/yielder/src/error.rs
    • Added a new module defining custom error types for the yielder crate.
  • crates/yielder/src/lib.rs
    • Added a new library file, declaring modules and exporting public items for the yielder crate.
  • crates/yielder/src/provider.rs
    • Added a new trait EarnProvider for defining yield earning service interfaces.
  • crates/yielder/src/yielder.rs
    • Added a new module implementing the Yielder struct to manage and interact with multiple earn providers.
  • crates/yielder/src/yo/assets.rs
    • Added a new module defining YoAsset and listing supported Yo assets.
  • crates/yielder/src/yo/client.rs
    • Added a new module defining YoClient trait and YoGatewayClient for Yo contract interactions.
  • crates/yielder/src/yo/contract.rs
    • Added a new module defining Solidity interfaces for YoVaultToken and YoGateway contracts.
  • crates/yielder/src/yo/mod.rs
    • Added a new module, declaring sub-modules and constants for Yo integration.
  • crates/yielder/src/yo/provider.rs
    • Added a new module implementing the EarnProvider trait specifically for Yo.
  • gemstone/Cargo.toml
    • Added yielder as a dependency to the gemstone crate.
  • gemstone/src/gateway/mod.rs
    • Updated GemGateway to include and utilize the new Yielder functionality for earn-related operations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new yielder crate for handling yield-providing protocols, starting with an integration for "Yo". The new crate is well-structured with clear separation of concerns for providers, clients, and contracts. The integration into GemGateway replaces placeholder implementations with calls to the new Yielder instance.

My review focuses on improving error handling. Specifically, I've pointed out a few places where errors are silently ignored, which could make debugging difficult. I've also suggested a way to preserve more detailed error information when propagating errors from the yielder crate up to the GemGateway.

.iter()
.filter_map(|asset| {
let chain = asset.chain;
let client = create_eth_client(rpc_provider.clone(), chain).ok()?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of .ok()? silently discards errors from create_eth_client. This can hide configuration problems, such as a missing RPC endpoint for a supported chain. It would be beneficial to log these errors to aid in debugging.


pub async fn positions(&self, chain: Chain, address: &str, asset_ids: &[AssetId]) -> Vec<DelegationBase> {
let futures: Vec<_> = self.providers.iter().map(|p| p.get_positions(chain, address, asset_ids)).collect();
futures::future::join_all(futures).await.into_iter().filter_map(|r| r.ok()).flatten().collect()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Errors from individual providers are silently ignored here by using .ok(). While this makes the function more robust if one provider fails, it also hides potential issues. Consider logging the errors to provide visibility into provider failures.

msg: "Earn provider not available".to_string(),
})
pub async fn get_earn_data(&self, asset_id: AssetId, address: String, value: String, earn_type: GemEarnType) -> Result<GemContractCallData, GatewayError> {
self.yielder.get_earn_data(&asset_id, &address, &value, &earn_type).await.map_err(|e| GatewayError::NetworkError { msg: e.to_string() })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Converting all YielderError variants to a generic GatewayError::NetworkError with a string message loses valuable structured error information. This can make error handling on the client side less precise. Consider adding a more specific error variant to GatewayError for yielder-related failures, or matching on the YielderError to map it to different existing GatewayError variants. For example, YielderError::UnsupportedAsset could be mapped to a more specific error than a generic network error.


pub const YO_USDC: YoAsset = YoAsset {
chain: Chain::Base,
asset_token: address!("0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

always do checksum

.collect()
}

async fn check_token_allowance(&self, token: Address, owner: Address, amount: U256) -> Result<Option<ApprovalData>, YielderError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have this method somewhere else

.collect()
}

async fn check_token_allowance(&self, token: Address, owner: Address, amount: U256) -> Result<Option<ApprovalData>, YielderError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to use multicall and check for reuse


use super::{YO_PARTNER_ID_GEM, YoAsset, client::YoClient, supported_assets};

const GAS_LIMIT: &str = "300000";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const GAS_LIMIT: &str = "300000";
const GAS_LIMIT: u64= 300_000;

return None;
}
let asset_id = a.asset_id();
Some(DelegationBase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to mapper.rs. map_to_delegation

Ok(assets
.iter()
.zip(positions)
.filter_map(|(a, data)| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.filter_map(|(a, data)| {
.filter_map(|(asset, data)| {

self.providers.iter().flat_map(|p| p.earn_providers(asset_id)).collect()
}

pub async fn positions(&self, chain: Chain, address: &str, asset_ids: &[AssetId]) -> Vec<DelegationBase> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub async fn positions(&self, chain: Chain, address: &str, asset_ids: &[AssetId]) -> Vec<DelegationBase> {
pub async fn get_positions(&self, chain: Chain, address: &str, asset_ids: &[AssetId]) -> Vec<DelegationBase> {

futures::future::join_all(futures).await.into_iter().filter_map(|r| r.ok()).flatten().collect()
}

pub async fn balance(&self, chain: Chain, address: &str) -> Vec<AssetBalance> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub async fn balance(&self, chain: Chain, address: &str) -> Vec<AssetBalance> {
pub async fn get_balance(&self, chain: Chain, address: &str) -> Vec<AssetBalance> {

msg: "Earn provider not available".to_string(),
})
pub async fn get_earn_data(&self, asset_id: AssetId, address: String, value: String, earn_type: GemEarnType) -> Result<GemContractCallData, GatewayError> {
self.yielder.earn_data(&asset_id, &address, &value, &earn_type).await.map_err(|e| GatewayError::NetworkError { msg: e.to_string() })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.yielder.earn_data(&asset_id, &address, &value, &earn_type).await.map_err(|e| GatewayError::NetworkError { msg: e.to_string() })
self.yielder.get_earn_data(&asset_id, &address, &value, &earn_type).await.map_err(|e| GatewayError::NetworkError { msg: e.to_string() })

fn earn_providers(&self, asset_id: &AssetId) -> Vec<DelegationValidator>;
fn earn_asset_ids_for_chain(&self, chain: Chain) -> Vec<AssetId>;

async fn positions(&self, chain: Chain, address: &str, asset_ids: &[AssetId]) -> Result<Vec<DelegationBase>, YielderError>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async fn positions(&self, chain: Chain, address: &str, asset_ids: &[AssetId]) -> Result<Vec<DelegationBase>, YielderError>;
async fn positions(&self, asset_id: &AssetId, address: &str) -> Result<Vec<DelegationBase>, YielderError>;

pub trait EarnProvider: Send + Sync {
fn id(&self) -> YieldProvider;
fn earn_providers(&self, asset_id: &AssetId) -> Vec<DelegationValidator>;
fn earn_asset_ids_for_chain(&self, chain: Chain) -> Vec<AssetId>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn earn_asset_ids_for_chain(&self, chain: Chain) -> Vec<AssetId>;

}

async fn get_balance(&self, chain: Chain, address: &str, token_ids: &[String]) -> Result<Vec<AssetBalance>, YielderError> {
let token_match = |a: &&YoAsset| token_ids.iter().any(|t| a.asset_token.to_string().eq_ignore_ascii_case(t));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let token_match = |a: &&YoAsset| token_ids.iter().any(|t| a.asset_token.to_string().eq_ignore_ascii_case(t));
let token_match = |a: &&YoAsset| token_ids.iter().any(|t| a.asset_token.to_string() == t);

self.get_position_for_asset(address, &asset).await
}

async fn get_balance(&self, chain: Chain, address: &str, token_ids: &[String]) -> Result<Vec<AssetBalance>, YielderError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async fn get_balance(&self, chain: Chain, address: &str, token_ids: &[String]) -> Result<Vec<AssetBalance>, YielderError> {
async fn get_balance(&self, chain: Chain, address: &str, token_id: String) -> Result<Vec<AssetBalance>, YielderError> {


pub async fn get_data(&self, asset_id: &AssetId, address: &str, value: &str, earn_type: &EarnType) -> Result<ContractCallData, YielderError> {
let provider_id = earn_type.provider_id();
let provider = self.providers.iter().find(|p| p.get_provider(asset_id).is_some_and(|v| v.id == provider_id)).ok_or_else(|| YielderError::unsupported_asset(asset_id))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplify

#[uniffi::constructor]
pub fn new(provider: Arc<dyn AlienProvider>, preferences: Arc<dyn GemPreferences>, secure_preferences: Arc<dyn GemPreferences>, api_url: String) -> Self {
let api_client = GemApiClient::new(api_url, provider.clone());
let wrapper = AlienProviderWrapper { provider: provider.clone() };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let wrapper = AlienProviderWrapper { provider: provider.clone() };
let wrapper = AlienProviderWrapper::new(provider.clone());

}

#[cfg(feature = "rpc")]
pub async fn multicall3_batch<T, R, const N: usize>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub async fn multicall3_batch<T, R, const N: usize>(
pub async fn multicall3<T, R, const N: usize>(


use super::EthereumClient;

pub fn create_eth_client(provider: Arc<dyn RpcProvider>, chain: Chain) -> Option<EthereumClient<RpcClient>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn create_eth_client(provider: Arc<dyn RpcProvider>, chain: Chain) -> Option<EthereumClient<RpcClient>> {
pub fn create_eth_client(provider: Arc<dyn RpcProvider>, chain: EVMChain) -> EthereumClient<RpcClient> {

@gemdev111 gemdev111 marked this pull request as ready for review March 18, 2026 13:48
Replace internal call-data helpers with YoClient impl that builds TransactionObject directly (using IYoGateway::...abi_encode). Simplify IYoGateway ABI by removing several unused view functions. Update provider logic: remove empty-assets early return in fetch_positions, inline asset filtering, adjust DelegationBase field ordering (set delegation_id and validator_id earlier), and rename local tx -> transaction while mapping .to/.data into ContractCallData. Miscellaneous formatting/cleanup.
Add #[cfg(test)] modules with unit tests for convert_to_assets_ceil (crates/yielder/src/yo/client.rs) and apply_slippage (crates/yielder/src/yo/provider.rs). Tests cover typical cases and edge cases (including zero values) to ensure correct rounding behavior and slippage calculation.
Refactor method names across the yielder and yo modules for clearer, consistent naming. Changes include:
- Provider/EarnProvider: get_positions -> positions
- Yielder: get_positions -> positions, get_earn_data -> earn_data, get_provider -> provider_by_id
- YoClient/YoGatewayClient: get_positions_batch -> positions_batch, convert_to_shares -> quote_shares
- YoEarnProvider: get_asset -> asset, fetch_positions -> positions_for_chain, get_positions -> positions
- Updated all call sites (crates/yielder and gemstone gateway) to use the new names.
No behavior changes intended — purely a rename refactor to improve readability and naming consistency.
…ching

 Add two methods to EthereumClient:
  - call_contract: typed eth_call for single contract calls (no multicall3 overhead)
  - multicall3_batch: batched multicall3 with const generic chunk size

  Simplify Yo client:
  - positions_batch uses multicall3_batch instead of manual call building + chunk decoding
  - check_token_allowance and quote_shares use direct call_contract instead of multicall3
  - Remove duplicate IYoVaultToken (was just IERC4626 with a different name)
…tency

 Move apply_slippage_in_bp from swapper to gem_evm::slippage so both swapper and yielder reuse the same implementation.

  Yielder naming cleanup:
  - gateways → clients, gateway_for_chain → get_client
  - positions_batch → get_positions, quote_shares → get_quote_shares
  - asset → get_asset, positions_for_chain → get_positions_for_chain
  - Consistent get_ prefix across all lookup methods

  Remove duplicate slippage logic from yielder provider.
- Simplify EarnProvider trait: merge deposit/withdraw into single get_data, singularize get_providers/get_positions to return Option, remove id(), remove get_asset_ids_for_chain by moving get_balance into trait
  - Remove redundant chain param from get_position — derived from asset_id
  - Make Yielder a thin orchestrator: pure routing, no business logic, no provider_by_id
  - Move client creation from Yielder::new into YoEarnProvider::new
  - Hide yo/ internals — only YoEarnProvider is exported
  - Remove dead code: strum dependency, YieldProvider re-export, provider_not_found, strum::ParseError impl
  - Use biguint_to_u256 instead of BigUint → String → U256 roundtrip
  - Update gateway FFI: get_earn_provider (singular), get_earn_position (singular, no chain param)
…m_evm

Move AlienError, RpcClient, RpcProvider, and create_client to gem_jsonrpc::alien.
Add create_eth_client to gem_evm::rpc for EVM-specific client creation.
Both swapper and yielder can now use shared concrete types with no generics.
Remove swapper's local AlienError definition, use gem_jsonrpc::alien instead.
Update client_factory to delegate to shared create_client and create_eth_client.
EarnProvider trait: merge deposit/withdraw into get_data, singularize
get_provider/get_position to Option, move get_balance into trait,
remove id() and get_asset_ids_for_chain.

Yielder orchestrator: pure routing with no business logic, aggregates
providers/positions into Vec, deduplicates balances across providers,
routes get_data by provider_id from EarnType.

YoEarnProvider: drop YoClient trait and HashMap<Chain, Arc<dyn YoClient>>,
use concrete YoGatewayClient created on demand via shared create_eth_client.
Batch all chain assets in single multicall3 for get_balance.

Cleanup: remove strum dependency, remove client_factory (use gem_evm shared),
use concrete alien types (no generics), hardcode Yo APR at 4.9%,
use biguint_to_u256 instead of string roundtrip, use .parse() over FromStr.

Gateway FFI: get_earn_providers/get_earn_positions take single asset_id
instead of chain + Vec<AssetId>.
Change get_balance(chain, address) to get_balance(chain, address, token_ids).
Provider matches user's token IDs against supported earn assets with
case-insensitive comparison. No matching tokens → early return, zero RPC calls.
- Extract mapping functions to mapper.rs (map_to_asset_balance, map_to_contract_call_data)
- Add get_positions helper to reduce duplication in YoEarnProvider
- Reuse get_asset in get_provider to avoid duplicate find logic
- Remove hardcoded YO_APR, use 0.0 (iOS reads APR from DB)
- Fix token comparison to use direct equality instead of case-insensitive
- Add tests for map_to_asset_balance and map_to_contract_call_data
- Delete gem_evm client_factory (returned Option causing double conversion)
- Inline EVM client creation in swapper with proper Result types
- Add yielder/src/client_factory.rs mirroring swapper pattern
- Add YielderError::NotSupportedChain unit variant
- Fix import grouping in yo/provider.rs and yo/mapper.rs
- Add Yielder::with_providers for testability without real RPC
- Extract get_provider(asset_id, provider_id) routing in Yielder::get_data
- Add YielderError::NotSupportedAsset unit variant
- Export EarnProvider trait from yielder crate
- Add AlienProviderWrapper::new constructor, make field private
@gemdev111 gemdev111 force-pushed the yo-yielder-intergration branch from 7ac0bfb to a3662e3 Compare March 18, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants