From a4bd0d4076e871e5b49cc17a4d5d2128d090de5c Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:50:57 +0200 Subject: [PATCH 1/5] Add earn/yield primitive types and FFI bindings - New types: EarnProviderType, YieldProvider, EarnType, EarnTransaction, EarnData - Add provider_type field to DelegationValidator - Add earn field to Balance - Add EarnDeposit/EarnWithdraw transaction types - Add Earn variant to TransactionInputType - Add earn_data to TransactionLoadMetadata::Evm - Update gemstone FFI remote types and conversions --- apps/daemon/src/pusher/pusher.rs | 2 +- .../gem_aptos/src/provider/staking_mapper.rs | 3 +- crates/gem_aptos/src/rpc/client.rs | 2 +- .../gem_cosmos/src/provider/preload_mapper.rs | 18 +++++--- .../gem_cosmos/src/provider/staking_mapper.rs | 3 +- crates/gem_evm/src/provider/preload.rs | 3 +- crates/gem_evm/src/provider/preload_mapper.rs | 9 +++- .../gem_evm/src/provider/staking_ethereum.rs | 3 +- crates/gem_evm/src/provider/staking_monad.rs | 3 +- .../src/provider/staking_smartchain.rs | 3 +- .../src/provider/staking_mapper.rs | 3 +- .../gem_hypercore/src/signer/core_signer.rs | 4 +- .../gem_solana/src/provider/preload_mapper.rs | 6 ++- .../gem_solana/src/provider/staking_mapper.rs | 3 +- crates/gem_sui/src/provider/preload_mapper.rs | 3 +- crates/gem_sui/src/provider/staking_mapper.rs | 3 +- .../gem_tron/src/provider/balances_mapper.rs | 1 + .../gem_tron/src/provider/preload_mapper.rs | 2 + .../gem_tron/src/provider/staking_mapper.rs | 4 +- crates/primitives/src/asset_balance.rs | 17 ++++++++ crates/primitives/src/delegation.rs | 2 + crates/primitives/src/earn_data.rs | 15 +++++++ crates/primitives/src/earn_provider_type.rs | 12 ++++++ crates/primitives/src/earn_transaction.rs | 16 +++++++ crates/primitives/src/earn_type.rs | 12 ++++++ crates/primitives/src/lib.rs | 14 +++++- .../primitives/src/testkit/delegation_mock.rs | 3 +- crates/primitives/src/transaction.rs | 8 +++- .../primitives/src/transaction_input_type.rs | 8 ++++ .../src/transaction_load_metadata.rs | 3 +- crates/primitives/src/transaction_type.rs | 2 + crates/primitives/src/yield_provider.rs | 11 +++++ gemstone/src/models/balance.rs | 1 + gemstone/src/models/stake.rs | 10 ++++- gemstone/src/models/transaction.rs | 43 ++++++++++++++++--- 35 files changed, 218 insertions(+), 37 deletions(-) create mode 100644 crates/primitives/src/earn_data.rs create mode 100644 crates/primitives/src/earn_provider_type.rs create mode 100644 crates/primitives/src/earn_transaction.rs create mode 100644 crates/primitives/src/earn_type.rs create mode 100644 crates/primitives/src/yield_provider.rs diff --git a/apps/daemon/src/pusher/pusher.rs b/apps/daemon/src/pusher/pusher.rs index 2bdb07d72..b96bc4f6e 100644 --- a/apps/daemon/src/pusher/pusher.rs +++ b/apps/daemon/src/pusher/pusher.rs @@ -111,7 +111,7 @@ impl Pusher { let message = format!("Closed perpetual position for {value} at {to_address}"); Ok(Message { title, message: Some(message) }) } - TransactionType::AssetActivation | TransactionType::PerpetualModifyPosition => todo!(), + TransactionType::AssetActivation | TransactionType::PerpetualModifyPosition | TransactionType::EarnDeposit | TransactionType::EarnWithdraw => todo!(), TransactionType::StakeFreeze => Ok(Message { title: localizer.notification_freeze_title(self.get_value(amount, asset.symbol.clone()).as_str()), message: None, diff --git a/crates/gem_aptos/src/provider/staking_mapper.rs b/crates/gem_aptos/src/provider/staking_mapper.rs index b086579a9..af7aefbb4 100644 --- a/crates/gem_aptos/src/provider/staking_mapper.rs +++ b/crates/gem_aptos/src/provider/staking_mapper.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use num_bigint::BigUint; -use primitives::{Chain, DelegationBase, DelegationState, DelegationValidator}; +use primitives::{Chain, DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; use crate::models::{DelegationPoolStake, StakingConfig, ValidatorInfo, ValidatorSet}; @@ -21,6 +21,7 @@ pub fn map_validator(validator: &ValidatorInfo, apy: f64, commission: f64, is_ac is_active, commission, apr: apy, + provider_type: EarnProviderType::Stake, } } diff --git a/crates/gem_aptos/src/rpc/client.rs b/crates/gem_aptos/src/rpc/client.rs index 30282aac0..3c4896f0b 100644 --- a/crates/gem_aptos/src/rpc/client.rs +++ b/crates/gem_aptos/src/rpc/client.rs @@ -108,7 +108,7 @@ impl AptosClient { } } TransactionInputType::Swap(_, _, _) | TransactionInputType::Stake(_, _) | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) => Ok(1500), - TransactionInputType::Perpetual(_, _) => unimplemented!(), + TransactionInputType::Perpetual(_, _) | TransactionInputType::Earn(_, _) => unimplemented!(), } } diff --git a/crates/gem_cosmos/src/provider/preload_mapper.rs b/crates/gem_cosmos/src/provider/preload_mapper.rs index 3a9913803..5c4cb45d7 100644 --- a/crates/gem_cosmos/src/provider/preload_mapper.rs +++ b/crates/gem_cosmos/src/provider/preload_mapper.rs @@ -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::Earn(_, _) => BigInt::from(3_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(3_000u64), TransactionInputType::Stake(_, _) => BigInt::from(25_000u64), }, @@ -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::Earn(_, _) => BigInt::from(10_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(10_000u64), TransactionInputType::Stake(_, _) => BigInt::from(100_000u64), }, @@ -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::Earn(_, _) => BigInt::from(3_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(3_000u64), TransactionInputType::Stake(_, _) => BigInt::from(10_000u64), }, @@ -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::Earn(_, _) => BigInt::from(100_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(100_000u64), TransactionInputType::Stake(_, _) => BigInt::from(200_000u64), }, @@ -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::Earn(_, _) => 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), }, @@ -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::Earn(_, _) => 200_000, TransactionInputType::Swap(_, _, _) => 200_000, TransactionInputType::Stake(_, operation) => match operation { StakeType::Stake(_) | StakeType::Unstake(_) => 1_000_000, diff --git a/crates/gem_cosmos/src/provider/staking_mapper.rs b/crates/gem_cosmos/src/provider/staking_mapper.rs index fa378eaca..8f5c153c4 100644 --- a/crates/gem_cosmos/src/provider/staking_mapper.rs +++ b/crates/gem_cosmos/src/provider/staking_mapper.rs @@ -11,7 +11,7 @@ use crate::models::{OsmosisDistributionProportions, OsmosisMintParams}; use number_formatter::BigNumberFormatter; use primitives::chain_cosmos::CosmosChain; -use primitives::{DelegationBase, DelegationState, DelegationValidator}; +use primitives::{DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; use std::collections::HashMap; const BOND_STATUS_BONDED: &str = "BOND_STATUS_BONDED"; @@ -68,6 +68,7 @@ pub fn map_staking_validators(validators: Vec, chain: CosmosChain, ap is_active, commission: commission_rate * 100.0, apr: validator_apr, + provider_type: EarnProviderType::Stake, } }) .collect() diff --git a/crates/gem_evm/src/provider/preload.rs b/crates/gem_evm/src/provider/preload.rs index 1487aa921..b1effb015 100644 --- a/crates/gem_evm/src/provider/preload.rs +++ b/crates/gem_evm/src/provider/preload.rs @@ -65,13 +65,14 @@ impl EthereumClient { let metadata = if let TransactionInputType::Stake(_, _) = &input.input_type { match input.metadata { - TransactionLoadMetadata::Evm { nonce, chain_id, .. } => TransactionLoadMetadata::Evm { + TransactionLoadMetadata::Evm { nonce, chain_id, earn_data, .. } => TransactionLoadMetadata::Evm { nonce, chain_id, stake_data: Some(StakeData { data: if params.data.is_empty() { None } else { Some(hex::encode(¶ms.data)) }, to: Some(params.to), }), + earn_data, }, _ => input.metadata, } diff --git a/crates/gem_evm/src/provider/preload_mapper.rs b/crates/gem_evm/src/provider/preload_mapper.rs index 17d167e3c..97731e4ed 100644 --- a/crates/gem_evm/src/provider/preload_mapper.rs +++ b/crates/gem_evm/src/provider/preload_mapper.rs @@ -46,6 +46,7 @@ pub fn map_transaction_preload(nonce_hex: String, chain_id: String) -> Result()?, stake_data: None, + earn_data: None, }) } @@ -284,7 +285,7 @@ mod tests { use super::*; use crate::everstake::{EVERSTAKE_POOL_ADDRESS, IAccounting}; use num_bigint::BigUint; - use primitives::{Delegation, DelegationBase, DelegationState, DelegationValidator, RedelegateData}; + use primitives::{Delegation, DelegationBase, DelegationState, DelegationValidator, EarnProviderType, RedelegateData}; fn everstake_validator() -> DelegationValidator { DelegationValidator { @@ -294,6 +295,7 @@ mod tests { is_active: true, commission: 10.0, apr: 4.2, + provider_type: EarnProviderType::Stake, } } @@ -428,6 +430,7 @@ mod tests { is_active: true, commission: 5.0, apr: 10.0, + provider_type: EarnProviderType::Stake, }; let stake_type = StakeType::Stake(validator); @@ -464,6 +467,7 @@ mod tests { is_active: true, commission: 5.0, apr: 10.0, + provider_type: EarnProviderType::Stake, }, price: None, }; @@ -501,6 +505,7 @@ mod tests { is_active: true, commission: 5.0, apr: 10.0, + provider_type: EarnProviderType::Stake, }, price: None, }; @@ -512,6 +517,7 @@ mod tests { is_active: true, commission: 3.0, apr: 12.0, + provider_type: EarnProviderType::Stake, }; let redelegate_data = RedelegateData { delegation, to_validator }; @@ -549,6 +555,7 @@ mod tests { is_active: true, commission: 5.0, apr: 10.0, + provider_type: EarnProviderType::Stake, }, price: None, }; diff --git a/crates/gem_evm/src/provider/staking_ethereum.rs b/crates/gem_evm/src/provider/staking_ethereum.rs index 6111a1646..77110dcc6 100644 --- a/crates/gem_evm/src/provider/staking_ethereum.rs +++ b/crates/gem_evm/src/provider/staking_ethereum.rs @@ -1,7 +1,7 @@ use gem_client::Client; use num_bigint::BigUint; use num_traits::Zero; -use primitives::{AssetBalance, AssetId, Balance, Chain, DelegationBase, DelegationState, DelegationValidator}; +use primitives::{AssetBalance, AssetId, Balance, Chain, DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; use std::error::Error; use crate::everstake::{EVERSTAKE_POOL_ADDRESS, get_everstake_account_state, map_balance_to_delegation, map_withdraw_request_to_delegations}; @@ -32,6 +32,7 @@ impl EthereumClient { is_active: true, commission: 0.1, apr: apy, + provider_type: EarnProviderType::Stake, }]) } diff --git a/crates/gem_evm/src/provider/staking_monad.rs b/crates/gem_evm/src/provider/staking_monad.rs index 8a7e622ed..4fa6bb661 100644 --- a/crates/gem_evm/src/provider/staking_monad.rs +++ b/crates/gem_evm/src/provider/staking_monad.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, Utc}; use gem_client::Client; use num_bigint::BigUint; use num_traits::{ToPrimitive, Zero}; -use primitives::{AssetBalance, AssetId, Chain, DelegationBase, DelegationState, DelegationValidator}; +use primitives::{AssetBalance, AssetId, Chain, DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; use crate::monad::{ IMonadStakingLens, MONAD_SCALE, MonadLensBalance, MonadLensDelegation, MonadLensValidatorInfo, STAKING_LENS_CONTRACT, decode_get_lens_apys, decode_get_lens_balance, @@ -124,6 +124,7 @@ impl EthereumClient { is_active: validator.is_active, commission: Self::lens_commission_rate(&validator.commission), apr: if validator.apy_bps > 0 { validator.apy_bps as f64 / 100.0 } else { network_apy }, + provider_type: EarnProviderType::Stake, } } diff --git a/crates/gem_evm/src/provider/staking_smartchain.rs b/crates/gem_evm/src/provider/staking_smartchain.rs index 66f6a4b9a..4f5921f66 100644 --- a/crates/gem_evm/src/provider/staking_smartchain.rs +++ b/crates/gem_evm/src/provider/staking_smartchain.rs @@ -7,7 +7,7 @@ use gem_bsc::stake_hub::{ }; use gem_client::Client; use num_bigint::BigUint; -use primitives::{AssetId, Chain, DelegationBase, DelegationState, DelegationValidator}; +use primitives::{AssetId, Chain, DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; use std::{error::Error, str::FromStr}; #[cfg(feature = "rpc")] @@ -37,6 +37,7 @@ impl EthereumClient { is_active: !v.jailed, commission: v.commission as f64 / 10000.0, apr: v.apy as f64 / 100.0, + provider_type: EarnProviderType::Stake, }) .collect()) } diff --git a/crates/gem_hypercore/src/provider/staking_mapper.rs b/crates/gem_hypercore/src/provider/staking_mapper.rs index 41fca6c59..92a53a4b1 100644 --- a/crates/gem_hypercore/src/provider/staking_mapper.rs +++ b/crates/gem_hypercore/src/provider/staking_mapper.rs @@ -1,7 +1,7 @@ use crate::models::balance::{DelegationBalance, Validator}; use num_bigint::BigUint; use number_formatter::BigNumberFormatter; -use primitives::{Asset, Chain, DelegationBase, DelegationState, DelegationValidator}; +use primitives::{Asset, Chain, DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; use std::str::FromStr; pub fn map_staking_validators(validators: Vec, chain: Chain, apy: Option) -> Vec { @@ -15,6 +15,7 @@ pub fn map_staking_validators(validators: Vec, chain: Chain, apy: Opt is_active: x.is_active, commission: x.commission, apr: calculated_apy, + provider_type: EarnProviderType::Stake, }) .collect() } diff --git a/crates/gem_hypercore/src/signer/core_signer.rs b/crates/gem_hypercore/src/signer/core_signer.rs index 32ecacf33..6c6607533 100644 --- a/crates/gem_hypercore/src/signer/core_signer.rs +++ b/crates/gem_hypercore/src/signer/core_signer.rs @@ -428,7 +428,7 @@ mod tests { use crate::core::actions::Grouping; use num_bigint::{BigInt, BigUint}; use primitives::{ - Asset, Chain, Delegation, DelegationBase, DelegationState, DelegationValidator, GasPriceType, StakeType, TransactionInputType, TransactionLoadInput, + Asset, Chain, Delegation, DelegationBase, DelegationState, DelegationValidator, EarnProviderType, GasPriceType, StakeType, TransactionInputType, TransactionLoadInput, TransactionLoadMetadata, }; @@ -443,6 +443,7 @@ mod tests { is_active: true, commission: 0.0, apr: 0.0, + provider_type: EarnProviderType::Stake, }; let input = TransactionLoadInput { input_type: TransactionInputType::Stake(asset.clone(), StakeType::Stake(validator)), @@ -497,6 +498,7 @@ mod tests { is_active: true, commission: 0.0, apr: 0.0, + provider_type: EarnProviderType::Stake, }, price: None, }; diff --git a/crates/gem_solana/src/provider/preload_mapper.rs b/crates/gem_solana/src/provider/preload_mapper.rs index e2e1c0080..d452423db 100644 --- a/crates/gem_solana/src/provider/preload_mapper.rs +++ b/crates/gem_solana/src/provider/preload_mapper.rs @@ -40,7 +40,8 @@ fn get_gas_limit(input_type: &TransactionInputType) -> BigInt { | TransactionInputType::Account(_, _) | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) - | TransactionInputType::Perpetual(_, _) => BigInt::from(100_000), + | TransactionInputType::Perpetual(_, _) + | TransactionInputType::Earn(_, _) => BigInt::from(100_000), TransactionInputType::Swap(_, _, _) => BigInt::from(420_000), TransactionInputType::Stake(_, _) => BigInt::from(100_000), } @@ -54,7 +55,8 @@ fn get_multiple_of(input_type: &TransactionInputType) -> i64 { | TransactionInputType::Account(asset, _) | TransactionInputType::TokenApprove(asset, _) | TransactionInputType::Generic(asset, _, _) - | TransactionInputType::Perpetual(asset, _) => match &asset.id.token_subtype() { + | TransactionInputType::Perpetual(asset, _) + | TransactionInputType::Earn(asset, _) => match &asset.id.token_subtype() { AssetSubtype::NATIVE => 25_000, AssetSubtype::TOKEN => 50_000, }, diff --git a/crates/gem_solana/src/provider/staking_mapper.rs b/crates/gem_solana/src/provider/staking_mapper.rs index 2ee98538b..20d2ea5e8 100644 --- a/crates/gem_solana/src/provider/staking_mapper.rs +++ b/crates/gem_solana/src/provider/staking_mapper.rs @@ -1,7 +1,7 @@ use crate::models::{EpochInfo, TokenAccountInfo, VoteAccount}; use chrono::Utc; use num_bigint::BigUint; -use primitives::{AssetId, Chain, DelegationBase, DelegationState, DelegationValidator}; +use primitives::{AssetId, Chain, DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; pub fn map_staking_validators(vote_accounts: Vec, chain: Chain, network_apy: f64) -> Vec { vote_accounts @@ -18,6 +18,7 @@ pub fn map_staking_validators(vote_accounts: Vec, chain: Chain, net is_active, commission: validator.commission as f64, apr: validator_apr, + provider_type: EarnProviderType::Stake, } }) .collect() diff --git a/crates/gem_sui/src/provider/preload_mapper.rs b/crates/gem_sui/src/provider/preload_mapper.rs index a4263a7b9..b04aa7d29 100644 --- a/crates/gem_sui/src/provider/preload_mapper.rs +++ b/crates/gem_sui/src/provider/preload_mapper.rs @@ -36,7 +36,8 @@ fn get_gas_limit(input_type: &TransactionInputType) -> u64 { | TransactionInputType::Deposit(_) | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) - | TransactionInputType::Perpetual(_, _) => GAS_BUDGET, + | TransactionInputType::Perpetual(_, _) + | TransactionInputType::Earn(_, _) => GAS_BUDGET, TransactionInputType::Swap(_, _, _) => 50_000_000, TransactionInputType::Stake(_, _) => GAS_BUDGET, } diff --git a/crates/gem_sui/src/provider/staking_mapper.rs b/crates/gem_sui/src/provider/staking_mapper.rs index e16d3dfee..908cf030c 100644 --- a/crates/gem_sui/src/provider/staking_mapper.rs +++ b/crates/gem_sui/src/provider/staking_mapper.rs @@ -2,7 +2,7 @@ use crate::models::RpcSuiSystemState; use crate::models::staking::{SuiStakeDelegation, SuiSystemState, SuiValidators}; use chrono::{DateTime, Utc}; use num_bigint::BigUint; -use primitives::{Chain, DelegationBase, DelegationState, DelegationValidator, StakeValidator}; +use primitives::{Chain, DelegationBase, DelegationState, DelegationValidator, EarnProviderType, StakeValidator}; pub fn map_validators(validators: SuiValidators, default_apy: f64) -> Vec { validators @@ -15,6 +15,7 @@ pub fn map_validators(validators: SuiValidators, default_apy: f64) -> Vec ChainParameter { @@ -277,6 +278,7 @@ mod tests { is_active: true, commission: 0.0, apr: 0.0, + provider_type: EarnProviderType::Stake, }); let with_bandwidth = account_usage(DEFAULT_BANDWIDTH_BYTES, 0, 0); diff --git a/crates/gem_tron/src/provider/staking_mapper.rs b/crates/gem_tron/src/provider/staking_mapper.rs index 6b582fa35..b6fbaf9c3 100644 --- a/crates/gem_tron/src/provider/staking_mapper.rs +++ b/crates/gem_tron/src/provider/staking_mapper.rs @@ -1,6 +1,6 @@ use crate::address::TronAddress; use crate::models::WitnessesList; -use primitives::{Chain, DelegationValidator, StakeValidator}; +use primitives::{Chain, DelegationValidator, EarnProviderType, StakeValidator}; const SYSTEM_UNSTAKING_VALIDATOR_ID: &str = "system"; const SYSTEM_UNSTAKING_VALIDATOR_NAME: &str = "Unstaking"; @@ -22,6 +22,7 @@ pub fn map_staking_validators(witnesses: WitnessesList, apy: Option) -> Vec is_active: witness.is_jobs.unwrap_or(false), commission: 0.0, apr: default_apy, + provider_type: EarnProviderType::Stake, }) }) .collect(); @@ -33,6 +34,7 @@ pub fn map_staking_validators(witnesses: WitnessesList, apy: Option) -> Vec is_active: true, commission: 0.0, apr: default_apy, + provider_type: EarnProviderType::Stake, }); validators diff --git a/crates/primitives/src/asset_balance.rs b/crates/primitives/src/asset_balance.rs index 0fc871d62..613b5aa4a 100644 --- a/crates/primitives/src/asset_balance.rs +++ b/crates/primitives/src/asset_balance.rs @@ -51,6 +51,17 @@ impl AssetBalance { is_active: true, } } + pub fn new_earn(asset_id: AssetId, earn: BigUint) -> Self { + Self { + asset_id, + balance: Balance { + earn, + ..Balance::coin_balance(BigUint::from(0u32)) + }, + is_active: true, + } + } + pub fn new_staking_with_metadata(asset_id: AssetId, staked: BigUint, pending: BigUint, rewards: BigUint, metadata: BalanceMetadata) -> Self { Self { asset_id, @@ -71,6 +82,7 @@ pub struct Balance { pub pending_unconfirmed: BigUint, pub rewards: BigUint, pub reserved: BigUint, + pub earn: BigUint, pub withdrawable: BigUint, pub metadata: Option, } @@ -97,6 +109,7 @@ impl Balance { pending_unconfirmed: BigUint::from(0u32), rewards: BigUint::from(0u32), reserved: BigUint::from(0u32), + earn: BigUint::from(0u32), withdrawable: BigUint::from(0u32), metadata: None, } @@ -112,6 +125,7 @@ impl Balance { pending: BigUint::from(0u32), rewards: BigUint::from(0u32), reserved: BigUint::from(0u32), + earn: BigUint::from(0u32), withdrawable: BigUint::from(0u32), metadata: None, } @@ -127,6 +141,7 @@ impl Balance { pending: BigUint::from(0u32), pending_unconfirmed: BigUint::from(0u32), rewards: BigUint::from(0u32), + earn: BigUint::from(0u32), withdrawable: BigUint::from(0u32), metadata: None, } @@ -146,8 +161,10 @@ impl Balance { pending_unconfirmed: BigUint::from(0u32), rewards: rewards.unwrap_or(BigUint::from(0u32)), reserved: BigUint::from(0u32), + earn: BigUint::from(0u32), withdrawable: BigUint::from(0u32), metadata, } } + } diff --git a/crates/primitives/src/delegation.rs b/crates/primitives/src/delegation.rs index e6a3c2357..57031144a 100644 --- a/crates/primitives/src/delegation.rs +++ b/crates/primitives/src/delegation.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumString}; use typeshare::typeshare; +use crate::earn_provider_type::EarnProviderType; use crate::{AssetId, Chain, Price, StakeValidator}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -45,6 +46,7 @@ pub struct DelegationValidator { pub is_active: bool, pub commission: f64, pub apr: f64, + pub provider_type: EarnProviderType, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, AsRefStr, EnumString, PartialEq)] diff --git a/crates/primitives/src/earn_data.rs b/crates/primitives/src/earn_data.rs new file mode 100644 index 000000000..cd66d2c18 --- /dev/null +++ b/crates/primitives/src/earn_data.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::swap::ApprovalData; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[typeshare(swift = "Equatable, Hashable, Sendable")] +#[serde(rename_all = "camelCase")] +pub struct EarnData { + pub provider: Option, + pub contract_address: Option, + pub call_data: Option, + pub approval: Option, + pub gas_limit: Option, +} diff --git a/crates/primitives/src/earn_provider_type.rs b/crates/primitives/src/earn_provider_type.rs new file mode 100644 index 000000000..c89f1191f --- /dev/null +++ b/crates/primitives/src/earn_provider_type.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; +use strum::{AsRefStr, Display, EnumString}; +use typeshare::typeshare; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, AsRefStr, EnumString, PartialEq, Eq)] +#[typeshare(swift = "Equatable, CaseIterable, Sendable")] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum EarnProviderType { + Stake, + Earn, +} diff --git a/crates/primitives/src/earn_transaction.rs b/crates/primitives/src/earn_transaction.rs new file mode 100644 index 000000000..2047bb164 --- /dev/null +++ b/crates/primitives/src/earn_transaction.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::{Chain, swap::ApprovalData}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[typeshare(swift = "Sendable")] +#[serde(rename_all = "camelCase")] +pub struct EarnTransaction { + pub chain: Chain, + pub from: String, + pub to: String, + pub data: String, + pub value: Option, + pub approval: Option, +} diff --git a/crates/primitives/src/earn_type.rs b/crates/primitives/src/earn_type.rs new file mode 100644 index 000000000..538bc0952 --- /dev/null +++ b/crates/primitives/src/earn_type.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::{Delegation, DelegationValidator}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "content")] +#[typeshare(swift = "Equatable, Hashable, Sendable")] +pub enum EarnType { + Deposit(DelegationValidator), + Withdraw(Delegation), +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index c9225725d..f149beec3 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -220,14 +220,24 @@ pub mod chart; pub use self::chart::{ChartCandleStick, ChartDateValue}; pub mod delegation; pub use self::delegation::{Delegation, DelegationBase, DelegationState, DelegationValidator}; +pub mod stake_type; +pub use self::stake_type::{RedelegateData, Resource, StakeData, StakeType, TronStakeData, TronUnfreeze, TronVote}; +pub mod earn_provider_type; +pub use self::earn_provider_type::EarnProviderType; +pub mod yield_provider; +pub use self::yield_provider::YieldProvider; +pub mod earn_type; +pub use self::earn_type::EarnType; +pub mod earn_transaction; +pub use self::earn_transaction::EarnTransaction; +pub mod earn_data; +pub use self::earn_data::EarnData; pub mod transaction_update; pub use self::transaction_update::{TransactionChange, TransactionMetadata, TransactionStateRequest, TransactionUpdate}; pub mod transaction_preload_input; pub use self::transaction_preload_input::TransactionPreloadInput; pub mod transaction_fee; pub use self::transaction_fee::{FeeOption, TransactionFee}; -pub mod stake_type; -pub use self::stake_type::{RedelegateData, Resource, StakeData, StakeType, TronStakeData, TronUnfreeze, TronVote}; pub mod transaction_load_metadata; pub use self::transaction_load_metadata::{HyperliquidOrder, TransactionLoadMetadata}; pub mod transaction_input_type; diff --git a/crates/primitives/src/testkit/delegation_mock.rs b/crates/primitives/src/testkit/delegation_mock.rs index 263a59666..98d63e126 100644 --- a/crates/primitives/src/testkit/delegation_mock.rs +++ b/crates/primitives/src/testkit/delegation_mock.rs @@ -1,4 +1,4 @@ -use crate::{AssetId, Chain, Delegation, DelegationBase, DelegationState, DelegationValidator}; +use crate::{AssetId, Chain, Delegation, DelegationBase, DelegationState, DelegationValidator, EarnProviderType}; use num_bigint::BigUint; impl Delegation { @@ -56,6 +56,7 @@ impl DelegationValidator { is_active: true, commission: 0.05, apr: 0.08, + provider_type: EarnProviderType::Stake, } } } diff --git a/crates/primitives/src/transaction.rs b/crates/primitives/src/transaction.rs index fbdcb572b..e7d9cacc2 100644 --- a/crates/primitives/src/transaction.rs +++ b/crates/primitives/src/transaction.rs @@ -260,7 +260,9 @@ impl Transaction { | TransactionType::SmartContractCall | TransactionType::PerpetualOpenPosition | TransactionType::PerpetualClosePosition - | TransactionType::PerpetualModifyPosition => vec![self.asset_id.clone(), self.fee_asset_id.clone()], + | TransactionType::PerpetualModifyPosition + | TransactionType::EarnDeposit + | TransactionType::EarnWithdraw => vec![self.asset_id.clone(), self.fee_asset_id.clone()], TransactionType::Swap => self .metadata .clone() @@ -295,7 +297,9 @@ impl Transaction { | TransactionType::SmartContractCall | TransactionType::PerpetualOpenPosition | TransactionType::PerpetualClosePosition - | TransactionType::PerpetualModifyPosition => vec![AssetAddress::new(self.asset_id.clone(), self.to.clone(), None)], + | TransactionType::PerpetualModifyPosition + | TransactionType::EarnDeposit + | TransactionType::EarnWithdraw => vec![AssetAddress::new(self.asset_id.clone(), self.to.clone(), None)], TransactionType::Swap => self .metadata .clone() diff --git a/crates/primitives/src/transaction_input_type.rs b/crates/primitives/src/transaction_input_type.rs index b519a4d0d..1a677b78c 100644 --- a/crates/primitives/src/transaction_input_type.rs +++ b/crates/primitives/src/transaction_input_type.rs @@ -1,3 +1,4 @@ +use crate::earn_type::EarnType; use crate::stake_type::StakeType; use crate::swap::{ApprovalData, SwapData}; use crate::transaction_fee::TransactionFee; @@ -21,6 +22,7 @@ pub enum TransactionInputType { TransferNft(Asset, NFTAsset), Account(Asset, AccountDataType), Perpetual(Asset, PerpetualType), + Earn(Asset, EarnType), } impl TransactionInputType { @@ -35,6 +37,7 @@ impl TransactionInputType { TransactionInputType::TransferNft(asset, _) => asset, TransactionInputType::Account(asset, _) => asset, TransactionInputType::Perpetual(asset, _) => asset, + TransactionInputType::Earn(asset, _) => asset, } } @@ -63,6 +66,7 @@ impl TransactionInputType { TransactionInputType::TransferNft(asset, _) => asset, TransactionInputType::Account(asset, _) => asset, TransactionInputType::Perpetual(asset, _) => asset, + TransactionInputType::Earn(asset, _) => asset, } } @@ -87,6 +91,10 @@ impl TransactionInputType { PerpetualType::Close(_) | PerpetualType::Reduce(_) => TransactionType::PerpetualClosePosition, PerpetualType::Modify(_) => TransactionType::PerpetualModifyPosition, }, + TransactionInputType::Earn(_, earn_type) => match earn_type { + EarnType::Deposit(_) => TransactionType::EarnDeposit, + EarnType::Withdraw(_) => TransactionType::EarnWithdraw, + }, } } } diff --git a/crates/primitives/src/transaction_load_metadata.rs b/crates/primitives/src/transaction_load_metadata.rs index c65bdfde7..1b48149ef 100644 --- a/crates/primitives/src/transaction_load_metadata.rs +++ b/crates/primitives/src/transaction_load_metadata.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - UTXO, + EarnData, UTXO, solana_token_program::SolanaTokenProgramId, stake_type::{StakeData, TronStakeData}, }; @@ -49,6 +49,7 @@ pub enum TransactionLoadMetadata { nonce: u64, chain_id: u64, stake_data: Option, + earn_data: Option, }, Near { sequence: u64, diff --git a/crates/primitives/src/transaction_type.rs b/crates/primitives/src/transaction_type.rs index 025d0e20f..0eefd012f 100644 --- a/crates/primitives/src/transaction_type.rs +++ b/crates/primitives/src/transaction_type.rs @@ -27,6 +27,8 @@ pub enum TransactionType { PerpetualOpenPosition, PerpetualClosePosition, PerpetualModifyPosition, + EarnDeposit, + EarnWithdraw, } impl TransactionType { diff --git a/crates/primitives/src/yield_provider.rs b/crates/primitives/src/yield_provider.rs new file mode 100644 index 000000000..b9dab7b30 --- /dev/null +++ b/crates/primitives/src/yield_provider.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; +use strum::{AsRefStr, Display, EnumString}; +use typeshare::typeshare; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, AsRefStr, EnumString, PartialEq, Eq)] +#[typeshare(swift = "Equatable, CaseIterable, Sendable")] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum YieldProvider { + Yo, +} diff --git a/gemstone/src/models/balance.rs b/gemstone/src/models/balance.rs index 9c3588e4f..0af403cc3 100644 --- a/gemstone/src/models/balance.rs +++ b/gemstone/src/models/balance.rs @@ -22,6 +22,7 @@ pub struct GemBalance { pub pending_unconfirmed: GemBigUint, pub rewards: GemBigUint, pub reserved: GemBigUint, + pub earn: GemBigUint, pub withdrawable: GemBigUint, pub metadata: Option, } diff --git a/gemstone/src/models/stake.rs b/gemstone/src/models/stake.rs index 641b98bd9..cc5d158d6 100644 --- a/gemstone/src/models/stake.rs +++ b/gemstone/src/models/stake.rs @@ -1,6 +1,6 @@ use crate::models::custom_types::{DateTimeUtc, GemBigUint}; use primitives::stake_type::{FreezeType, Resource}; -use primitives::{AssetId, Chain, Delegation, DelegationBase, DelegationState, DelegationValidator, Price, StakeChain}; +use primitives::{AssetId, Chain, Delegation, DelegationBase, DelegationState, DelegationValidator, EarnProviderType, Price, StakeChain}; pub type GemFreezeType = FreezeType; pub type GemResource = Resource; @@ -8,6 +8,7 @@ pub type GemDelegation = Delegation; pub type GemDelegationBase = DelegationBase; pub type GemDelegationValidator = DelegationValidator; pub type GemDelegationState = DelegationState; +pub type GemEarnProviderType = EarnProviderType; pub type GemPrice = Price; pub type GemStakeChain = StakeChain; @@ -50,6 +51,12 @@ pub enum GemDelegationState { AwaitingWithdrawal, } +#[uniffi::remote(Enum)] +pub enum GemEarnProviderType { + Stake, + Earn, +} + #[uniffi::remote(Record)] pub struct GemDelegationValidator { pub chain: Chain, @@ -58,6 +65,7 @@ pub struct GemDelegationValidator { pub is_active: bool, pub commission: f64, pub apr: f64, + pub provider_type: GemEarnProviderType, } #[uniffi::remote(Record)] diff --git a/gemstone/src/models/transaction.rs b/gemstone/src/models/transaction.rs index 40fb4f9c9..2d4533211 100644 --- a/gemstone/src/models/transaction.rs +++ b/gemstone/src/models/transaction.rs @@ -2,10 +2,10 @@ use crate::models::*; use num_bigint::BigInt; use primitives::stake_type::{FreezeData, StakeData}; use primitives::{ - AccountDataType, Asset, FeeOption, GasPriceType, HyperliquidOrder, PerpetualConfirmData, PerpetualDirection, PerpetualProvider, PerpetualType, Resource, StakeType, - TransactionChange, TransactionFee, TransactionInputType, TransactionLoadInput, TransactionLoadMetadata, TransactionMetadata, TransactionPerpetualMetadata, TransactionState, - TransactionStateRequest, TransactionType, TransactionUpdate, TransferDataExtra, TransferDataOutputAction, TransferDataOutputType, TronStakeData, TronUnfreeze, TronVote, - UInt64, WalletConnectionSessionAppMetadata, + AccountDataType, Asset, EarnData, EarnType, FeeOption, GasPriceType, HyperliquidOrder, PerpetualConfirmData, PerpetualDirection, PerpetualProvider, PerpetualType, Resource, + StakeType, TransactionChange, TransactionFee, TransactionInputType, TransactionLoadInput, TransactionLoadMetadata, TransactionMetadata, TransactionPerpetualMetadata, + TransactionState, TransactionStateRequest, TransactionType, TransactionUpdate, TransferDataExtra, TransferDataOutputAction, TransferDataOutputType, TronStakeData, + TronUnfreeze, TronVote, UInt64, WalletConnectionSessionAppMetadata, perpetual::{CancelOrderData, PerpetualModifyConfirmData, PerpetualModifyPositionType, PerpetualReduceData, TPSLOrderData}, }; use std::collections::HashMap; @@ -127,6 +127,8 @@ pub enum TransactionType { PerpetualOpenPosition, PerpetualClosePosition, PerpetualModifyPosition, + EarnDeposit, + EarnWithdraw, } pub type GemAccountDataType = AccountDataType; @@ -180,6 +182,25 @@ pub struct GemFreezeData { pub resource: GemResource, } +pub type GemEarnData = EarnData; + +#[uniffi::remote(Record)] +pub struct GemEarnData { + pub provider: Option, + pub contract_address: Option, + pub call_data: Option, + pub approval: Option, + pub gas_limit: Option, +} + +pub type GemEarnType = EarnType; + +#[uniffi::remote(Enum)] +pub enum GemEarnType { + Deposit(GemDelegationValidator), + Withdraw(GemDelegation), +} + pub type GemWalletConnectionSessionAppMetadata = WalletConnectionSessionAppMetadata; #[uniffi::remote(Record)] @@ -302,6 +323,10 @@ pub enum GemTransactionInputType { asset: GemAsset, perpetual_type: GemPerpetualType, }, + Earn { + asset: GemAsset, + earn_type: GemEarnType, + }, } impl GemTransactionInputType { @@ -314,7 +339,8 @@ impl GemTransactionInputType { | Self::Generic { asset, .. } | Self::TransferNft { asset, .. } | Self::Account { asset, .. } - | Self::Perpetual { asset, .. } => asset, + | Self::Perpetual { asset, .. } + | Self::Earn { asset, .. } => asset, Self::Swap { from_asset, .. } => from_asset, } } @@ -405,6 +431,7 @@ pub enum GemTransactionLoadMetadata { nonce: u64, chain_id: u64, stake_data: Option, + earn_data: Option, }, Near { sequence: u64, @@ -489,7 +516,7 @@ impl From for GemTransactionLoadMetadata { TransactionLoadMetadata::Bitcoin { utxos } => GemTransactionLoadMetadata::Bitcoin { utxos }, TransactionLoadMetadata::Zcash { utxos, branch_id } => GemTransactionLoadMetadata::Zcash { utxos, branch_id }, TransactionLoadMetadata::Cardano { utxos } => GemTransactionLoadMetadata::Cardano { utxos }, - TransactionLoadMetadata::Evm { nonce, chain_id, stake_data } => GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data }, + TransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data } => GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data }, TransactionLoadMetadata::Near { sequence, block_hash } => GemTransactionLoadMetadata::Near { sequence, block_hash }, TransactionLoadMetadata::Stellar { sequence, @@ -577,7 +604,7 @@ impl From for TransactionLoadMetadata { GemTransactionLoadMetadata::Bitcoin { utxos } => TransactionLoadMetadata::Bitcoin { utxos }, GemTransactionLoadMetadata::Zcash { utxos, branch_id } => TransactionLoadMetadata::Zcash { utxos, branch_id }, GemTransactionLoadMetadata::Cardano { utxos } => TransactionLoadMetadata::Cardano { utxos }, - GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data } => TransactionLoadMetadata::Evm { nonce, chain_id, stake_data }, + GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data } => TransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data }, GemTransactionLoadMetadata::Near { sequence, block_hash } => TransactionLoadMetadata::Near { sequence, block_hash }, GemTransactionLoadMetadata::Stellar { sequence, @@ -680,6 +707,7 @@ impl From for GemTransactionInputType { TransactionInputType::TransferNft(asset, nft_asset) => GemTransactionInputType::TransferNft { asset, nft_asset }, TransactionInputType::Account(asset, account_type) => GemTransactionInputType::Account { asset, account_type }, TransactionInputType::Perpetual(asset, perpetual_type) => GemTransactionInputType::Perpetual { asset, perpetual_type }, + TransactionInputType::Earn(asset, earn_type) => GemTransactionInputType::Earn { asset, earn_type }, } } } @@ -831,6 +859,7 @@ impl From for TransactionInputType { GemTransactionInputType::TransferNft { asset, nft_asset } => TransactionInputType::TransferNft(asset, nft_asset), GemTransactionInputType::Account { asset, account_type } => TransactionInputType::Account(asset, account_type), GemTransactionInputType::Perpetual { asset, perpetual_type } => TransactionInputType::Perpetual(asset, perpetual_type), + GemTransactionInputType::Earn { asset, earn_type } => TransactionInputType::Earn(asset, earn_type), } } } From c2602ab27de82d0c5df9d008644f8819203ebd16 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:36:35 +0200 Subject: [PATCH 2/5] Add earn transaction facade in EVM preload and gateway Handle Earn input type in get_transaction_params and get_extra_fee_gas_limit (preload_mapper), preserve earn_data in map_transaction_load (preload), and add get_earn_data stub on GemGateway for future yielder integration. Co-Authored-By: Claude Opus 4.6 --- crates/gem_evm/src/provider/preload.rs | 10 ++++++++ crates/gem_evm/src/provider/preload_mapper.rs | 23 +++++++++++++++++++ gemstone/src/gateway/mod.rs | 6 +++++ 3 files changed, 39 insertions(+) diff --git a/crates/gem_evm/src/provider/preload.rs b/crates/gem_evm/src/provider/preload.rs index b1effb015..e07bbd72c 100644 --- a/crates/gem_evm/src/provider/preload.rs +++ b/crates/gem_evm/src/provider/preload.rs @@ -76,6 +76,16 @@ impl EthereumClient { }, _ => input.metadata, } + } else if let TransactionInputType::Earn(_, _) = &input.input_type { + match input.metadata { + TransactionLoadMetadata::Evm { nonce, chain_id, earn_data, .. } => TransactionLoadMetadata::Evm { + nonce, + chain_id, + stake_data: None, + earn_data, + }, + _ => input.metadata, + } } else { input.metadata }; diff --git a/crates/gem_evm/src/provider/preload_mapper.rs b/crates/gem_evm/src/provider/preload_mapper.rs index 97731e4ed..4f4c4fd5e 100644 --- a/crates/gem_evm/src/provider/preload_mapper.rs +++ b/crates/gem_evm/src/provider/preload_mapper.rs @@ -146,6 +146,20 @@ pub fn get_transaction_params(chain: EVMChain, input: &TransactionLoadInput) -> } _ => Err("Unsupported chain for staking".into()), }, + TransactionInputType::Earn(_, _) => { + let earn_data = match &input.metadata { + TransactionLoadMetadata::Evm { earn_data: Some(ed), .. } => ed, + _ => return Err("Missing earn data in EVM metadata".into()), + }; + let contract_address = earn_data.contract_address.as_ref().ok_or("Missing earn contract address")?; + let call_data_hex = earn_data.call_data.as_ref().ok_or("Missing earn call data")?; + + if let Some(approval) = &earn_data.approval { + Ok(TransactionParams::new(approval.token.clone(), encode_erc20_approve(&approval.spender)?, BigInt::from(0))) + } else { + Ok(TransactionParams::new(contract_address.clone(), hex::decode(call_data_hex)?, value)) + } + } _ => Err("Unsupported transfer type".into()), } } @@ -184,6 +198,15 @@ pub fn get_extra_fee_gas_limit(input: &TransactionLoadInput) -> Result { + if let TransactionLoadMetadata::Evm { earn_data: Some(ref ed), .. } = input.metadata + && ed.approval.is_some() + && let Some(ref gas_limit) = ed.gas_limit + { + return Ok(BigInt::from_str_radix(gas_limit, 10)?); + } + Ok(BigInt::from(0)) + } _ => Ok(BigInt::from(0)), } } diff --git a/gemstone/src/gateway/mod.rs b/gemstone/src/gateway/mod.rs index f4a02e518..435c367ed 100644 --- a/gemstone/src/gateway/mod.rs +++ b/gemstone/src/gateway/mod.rs @@ -299,6 +299,12 @@ impl GemGateway { Ok(self.provider(chain).await?.get_is_token_address(&token_id)) } + pub async fn get_earn_data(&self, _chain: Chain, _asset_id: String, _address: String, _value: String, _earn_type: GemEarnType) -> Result { + Err(GatewayError::NetworkError { + msg: "Earn provider not available".to_string(), + }) + } + pub async fn get_node_status(&self, chain: Chain, url: &str) -> Result { let start_time = std::time::Instant::now(); let provider = self.provider_with_url(chain, url.to_string()).await?; From 6f1404af64a1b096be70aecbfa49aedc2436286a Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:25:20 +0200 Subject: [PATCH 3/5] Update mod.rs --- gemstone/src/gateway/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gemstone/src/gateway/mod.rs b/gemstone/src/gateway/mod.rs index 435c367ed..7f506bc5f 100644 --- a/gemstone/src/gateway/mod.rs +++ b/gemstone/src/gateway/mod.rs @@ -305,6 +305,14 @@ impl GemGateway { }) } + pub async fn get_earn_providers(&self, _asset_id: String) -> Result, GatewayError> { + Ok(vec![]) + } + + pub async fn get_earn_positions(&self, _chain: Chain, _address: String) -> Result, GatewayError> { + Ok(vec![]) + } + pub async fn get_node_status(&self, chain: Chain, url: &str) -> Result { let start_time = std::time::Instant::now(); let provider = self.provider_with_url(chain, url.to_string()).await?; From d1bc611f65950328b4ecfb3e37522ba37f5274f0 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:08:21 +0200 Subject: [PATCH 4/5] Cleanup --- crates/gem_aptos/src/rpc/client.rs | 2 +- .../gem_cosmos/src/provider/preload_mapper.rs | 12 +++++----- crates/gem_evm/src/provider/preload.rs | 13 +---------- crates/gem_evm/src/provider/preload_mapper.rs | 23 ++++++------------- .../gem_solana/src/provider/preload_mapper.rs | 4 ++-- crates/gem_sui/src/provider/preload_mapper.rs | 2 +- crates/primitives/src/earn_data.rs | 5 ++-- .../primitives/src/transaction_input_type.rs | 9 ++++---- .../src/transaction_load_metadata.rs | 3 +-- gemstone/src/models/transaction.rs | 22 +++++++++++------- 10 files changed, 40 insertions(+), 55 deletions(-) diff --git a/crates/gem_aptos/src/rpc/client.rs b/crates/gem_aptos/src/rpc/client.rs index 3c4896f0b..0bc6afb83 100644 --- a/crates/gem_aptos/src/rpc/client.rs +++ b/crates/gem_aptos/src/rpc/client.rs @@ -108,7 +108,7 @@ impl AptosClient { } } TransactionInputType::Swap(_, _, _) | TransactionInputType::Stake(_, _) | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) => Ok(1500), - TransactionInputType::Perpetual(_, _) | TransactionInputType::Earn(_, _) => unimplemented!(), + TransactionInputType::Perpetual(_, _) | TransactionInputType::Earn(_, _, _) => unimplemented!(), } } diff --git a/crates/gem_cosmos/src/provider/preload_mapper.rs b/crates/gem_cosmos/src/provider/preload_mapper.rs index 5c4cb45d7..d4c503e72 100644 --- a/crates/gem_cosmos/src/provider/preload_mapper.rs +++ b/crates/gem_cosmos/src/provider/preload_mapper.rs @@ -12,7 +12,7 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt { | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => BigInt::from(3_000u64), + | TransactionInputType::Earn(_, _, _) => BigInt::from(3_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(3_000u64), TransactionInputType::Stake(_, _) => BigInt::from(25_000u64), }, @@ -24,7 +24,7 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt { | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => BigInt::from(10_000u64), + | TransactionInputType::Earn(_, _, _) => BigInt::from(10_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(10_000u64), TransactionInputType::Stake(_, _) => BigInt::from(100_000u64), }, @@ -36,7 +36,7 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt { | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => BigInt::from(3_000u64), + | TransactionInputType::Earn(_, _, _) => BigInt::from(3_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(3_000u64), TransactionInputType::Stake(_, _) => BigInt::from(10_000u64), }, @@ -48,7 +48,7 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt { | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => BigInt::from(100_000u64), + | TransactionInputType::Earn(_, _, _) => BigInt::from(100_000u64), TransactionInputType::Swap(_, _, _) => BigInt::from(100_000u64), TransactionInputType::Stake(_, _) => BigInt::from(200_000u64), }, @@ -60,7 +60,7 @@ fn get_fee(chain: CosmosChain, input_type: &TransactionInputType) -> BigInt { | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => BigInt::from(100_000_000_000_000u64), + | TransactionInputType::Earn(_, _, _) => 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), }, @@ -77,7 +77,7 @@ fn get_gas_limit(input_type: &TransactionInputType, _chain: CosmosChain) -> u64 | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => 200_000, + | TransactionInputType::Earn(_, _, _) => 200_000, TransactionInputType::Swap(_, _, _) => 200_000, TransactionInputType::Stake(_, operation) => match operation { StakeType::Stake(_) | StakeType::Unstake(_) => 1_000_000, diff --git a/crates/gem_evm/src/provider/preload.rs b/crates/gem_evm/src/provider/preload.rs index e07bbd72c..1487aa921 100644 --- a/crates/gem_evm/src/provider/preload.rs +++ b/crates/gem_evm/src/provider/preload.rs @@ -65,24 +65,13 @@ impl EthereumClient { let metadata = if let TransactionInputType::Stake(_, _) = &input.input_type { match input.metadata { - TransactionLoadMetadata::Evm { nonce, chain_id, earn_data, .. } => TransactionLoadMetadata::Evm { + TransactionLoadMetadata::Evm { nonce, chain_id, .. } => TransactionLoadMetadata::Evm { nonce, chain_id, stake_data: Some(StakeData { data: if params.data.is_empty() { None } else { Some(hex::encode(¶ms.data)) }, to: Some(params.to), }), - earn_data, - }, - _ => input.metadata, - } - } else if let TransactionInputType::Earn(_, _) = &input.input_type { - match input.metadata { - TransactionLoadMetadata::Evm { nonce, chain_id, earn_data, .. } => TransactionLoadMetadata::Evm { - nonce, - chain_id, - stake_data: None, - earn_data, }, _ => input.metadata, } diff --git a/crates/gem_evm/src/provider/preload_mapper.rs b/crates/gem_evm/src/provider/preload_mapper.rs index 4f4c4fd5e..446ef2226 100644 --- a/crates/gem_evm/src/provider/preload_mapper.rs +++ b/crates/gem_evm/src/provider/preload_mapper.rs @@ -46,7 +46,6 @@ pub fn map_transaction_preload(nonce_hex: String, chain_id: String) -> Result()?, stake_data: None, - earn_data: None, }) } @@ -146,18 +145,11 @@ pub fn get_transaction_params(chain: EVMChain, input: &TransactionLoadInput) -> } _ => Err("Unsupported chain for staking".into()), }, - TransactionInputType::Earn(_, _) => { - let earn_data = match &input.metadata { - TransactionLoadMetadata::Evm { earn_data: Some(ed), .. } => ed, - _ => return Err("Missing earn data in EVM metadata".into()), - }; - let contract_address = earn_data.contract_address.as_ref().ok_or("Missing earn contract address")?; - let call_data_hex = earn_data.call_data.as_ref().ok_or("Missing earn call data")?; - + TransactionInputType::Earn(_, _, earn_data) => { if let Some(approval) = &earn_data.approval { Ok(TransactionParams::new(approval.token.clone(), encode_erc20_approve(&approval.spender)?, BigInt::from(0))) } else { - Ok(TransactionParams::new(contract_address.clone(), hex::decode(call_data_hex)?, value)) + Ok(TransactionParams::new(earn_data.contract_address.clone(), hex::decode(&earn_data.call_data)?, value)) } } _ => Err("Unsupported transfer type".into()), @@ -198,12 +190,11 @@ pub fn get_extra_fee_gas_limit(input: &TransactionLoadInput) -> Result { - if let TransactionLoadMetadata::Evm { earn_data: Some(ref ed), .. } = input.metadata - && ed.approval.is_some() - && let Some(ref gas_limit) = ed.gas_limit - { - return Ok(BigInt::from_str_radix(gas_limit, 10)?); + TransactionInputType::Earn(_, _, earn_data) => { + if earn_data.approval.is_some() { + if let Some(gas_limit) = &earn_data.gas_limit { + return Ok(BigInt::from_str_radix(gas_limit, 10)?); + } } Ok(BigInt::from(0)) } diff --git a/crates/gem_solana/src/provider/preload_mapper.rs b/crates/gem_solana/src/provider/preload_mapper.rs index d452423db..93ac30421 100644 --- a/crates/gem_solana/src/provider/preload_mapper.rs +++ b/crates/gem_solana/src/provider/preload_mapper.rs @@ -41,7 +41,7 @@ fn get_gas_limit(input_type: &TransactionInputType) -> BigInt { | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => BigInt::from(100_000), + | TransactionInputType::Earn(_, _, _) => BigInt::from(100_000), TransactionInputType::Swap(_, _, _) => BigInt::from(420_000), TransactionInputType::Stake(_, _) => BigInt::from(100_000), } @@ -56,7 +56,7 @@ fn get_multiple_of(input_type: &TransactionInputType) -> i64 { | TransactionInputType::TokenApprove(asset, _) | TransactionInputType::Generic(asset, _, _) | TransactionInputType::Perpetual(asset, _) - | TransactionInputType::Earn(asset, _) => match &asset.id.token_subtype() { + | TransactionInputType::Earn(asset, _, _) => match &asset.id.token_subtype() { AssetSubtype::NATIVE => 25_000, AssetSubtype::TOKEN => 50_000, }, diff --git a/crates/gem_sui/src/provider/preload_mapper.rs b/crates/gem_sui/src/provider/preload_mapper.rs index b04aa7d29..e397570c9 100644 --- a/crates/gem_sui/src/provider/preload_mapper.rs +++ b/crates/gem_sui/src/provider/preload_mapper.rs @@ -37,7 +37,7 @@ fn get_gas_limit(input_type: &TransactionInputType) -> u64 { | TransactionInputType::TokenApprove(_, _) | TransactionInputType::Generic(_, _, _) | TransactionInputType::Perpetual(_, _) - | TransactionInputType::Earn(_, _) => GAS_BUDGET, + | TransactionInputType::Earn(_, _, _) => GAS_BUDGET, TransactionInputType::Swap(_, _, _) => 50_000_000, TransactionInputType::Stake(_, _) => GAS_BUDGET, } diff --git a/crates/primitives/src/earn_data.rs b/crates/primitives/src/earn_data.rs index cd66d2c18..ed64e5d24 100644 --- a/crates/primitives/src/earn_data.rs +++ b/crates/primitives/src/earn_data.rs @@ -7,9 +7,8 @@ use crate::swap::ApprovalData; #[typeshare(swift = "Equatable, Hashable, Sendable")] #[serde(rename_all = "camelCase")] pub struct EarnData { - pub provider: Option, - pub contract_address: Option, - pub call_data: Option, + pub contract_address: String, + pub call_data: String, pub approval: Option, pub gas_limit: Option, } diff --git a/crates/primitives/src/transaction_input_type.rs b/crates/primitives/src/transaction_input_type.rs index 1a677b78c..6badd9e2a 100644 --- a/crates/primitives/src/transaction_input_type.rs +++ b/crates/primitives/src/transaction_input_type.rs @@ -1,3 +1,4 @@ +use crate::earn_data::EarnData; use crate::earn_type::EarnType; use crate::stake_type::StakeType; use crate::swap::{ApprovalData, SwapData}; @@ -22,7 +23,7 @@ pub enum TransactionInputType { TransferNft(Asset, NFTAsset), Account(Asset, AccountDataType), Perpetual(Asset, PerpetualType), - Earn(Asset, EarnType), + Earn(Asset, EarnType, EarnData), } impl TransactionInputType { @@ -37,7 +38,7 @@ impl TransactionInputType { TransactionInputType::TransferNft(asset, _) => asset, TransactionInputType::Account(asset, _) => asset, TransactionInputType::Perpetual(asset, _) => asset, - TransactionInputType::Earn(asset, _) => asset, + TransactionInputType::Earn(asset, _, _) => asset, } } @@ -66,7 +67,7 @@ impl TransactionInputType { TransactionInputType::TransferNft(asset, _) => asset, TransactionInputType::Account(asset, _) => asset, TransactionInputType::Perpetual(asset, _) => asset, - TransactionInputType::Earn(asset, _) => asset, + TransactionInputType::Earn(asset, _, _) => asset, } } @@ -91,7 +92,7 @@ impl TransactionInputType { PerpetualType::Close(_) | PerpetualType::Reduce(_) => TransactionType::PerpetualClosePosition, PerpetualType::Modify(_) => TransactionType::PerpetualModifyPosition, }, - TransactionInputType::Earn(_, earn_type) => match earn_type { + TransactionInputType::Earn(_, earn_type, _) => match earn_type { EarnType::Deposit(_) => TransactionType::EarnDeposit, EarnType::Withdraw(_) => TransactionType::EarnWithdraw, }, diff --git a/crates/primitives/src/transaction_load_metadata.rs b/crates/primitives/src/transaction_load_metadata.rs index 1b48149ef..c65bdfde7 100644 --- a/crates/primitives/src/transaction_load_metadata.rs +++ b/crates/primitives/src/transaction_load_metadata.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - EarnData, UTXO, + UTXO, solana_token_program::SolanaTokenProgramId, stake_type::{StakeData, TronStakeData}, }; @@ -49,7 +49,6 @@ pub enum TransactionLoadMetadata { nonce: u64, chain_id: u64, stake_data: Option, - earn_data: Option, }, Near { sequence: u64, diff --git a/gemstone/src/models/transaction.rs b/gemstone/src/models/transaction.rs index 2d4533211..05752e1fd 100644 --- a/gemstone/src/models/transaction.rs +++ b/gemstone/src/models/transaction.rs @@ -186,9 +186,8 @@ pub type GemEarnData = EarnData; #[uniffi::remote(Record)] pub struct GemEarnData { - pub provider: Option, - pub contract_address: Option, - pub call_data: Option, + pub contract_address: String, + pub call_data: String, pub approval: Option, pub gas_limit: Option, } @@ -326,6 +325,7 @@ pub enum GemTransactionInputType { Earn { asset: GemAsset, earn_type: GemEarnType, + earn_data: GemEarnData, }, } @@ -352,6 +352,13 @@ impl GemTransactionInputType { } } + pub fn earn_data(&self) -> Result<&GemEarnData, String> { + match self { + Self::Earn { earn_data, .. } => Ok(earn_data), + _ => Err("Expected Earn".to_string()), + } + } + pub fn stake_type(&self) -> Result<&GemStakeType, String> { match self { Self::Stake { stake_type, .. } => Ok(stake_type), @@ -431,7 +438,6 @@ pub enum GemTransactionLoadMetadata { nonce: u64, chain_id: u64, stake_data: Option, - earn_data: Option, }, Near { sequence: u64, @@ -516,7 +522,7 @@ impl From for GemTransactionLoadMetadata { TransactionLoadMetadata::Bitcoin { utxos } => GemTransactionLoadMetadata::Bitcoin { utxos }, TransactionLoadMetadata::Zcash { utxos, branch_id } => GemTransactionLoadMetadata::Zcash { utxos, branch_id }, TransactionLoadMetadata::Cardano { utxos } => GemTransactionLoadMetadata::Cardano { utxos }, - TransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data } => GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data }, + TransactionLoadMetadata::Evm { nonce, chain_id, stake_data } => GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data }, TransactionLoadMetadata::Near { sequence, block_hash } => GemTransactionLoadMetadata::Near { sequence, block_hash }, TransactionLoadMetadata::Stellar { sequence, @@ -604,7 +610,7 @@ impl From for TransactionLoadMetadata { GemTransactionLoadMetadata::Bitcoin { utxos } => TransactionLoadMetadata::Bitcoin { utxos }, GemTransactionLoadMetadata::Zcash { utxos, branch_id } => TransactionLoadMetadata::Zcash { utxos, branch_id }, GemTransactionLoadMetadata::Cardano { utxos } => TransactionLoadMetadata::Cardano { utxos }, - GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data } => TransactionLoadMetadata::Evm { nonce, chain_id, stake_data, earn_data }, + GemTransactionLoadMetadata::Evm { nonce, chain_id, stake_data } => TransactionLoadMetadata::Evm { nonce, chain_id, stake_data }, GemTransactionLoadMetadata::Near { sequence, block_hash } => TransactionLoadMetadata::Near { sequence, block_hash }, GemTransactionLoadMetadata::Stellar { sequence, @@ -707,7 +713,7 @@ impl From for GemTransactionInputType { TransactionInputType::TransferNft(asset, nft_asset) => GemTransactionInputType::TransferNft { asset, nft_asset }, TransactionInputType::Account(asset, account_type) => GemTransactionInputType::Account { asset, account_type }, TransactionInputType::Perpetual(asset, perpetual_type) => GemTransactionInputType::Perpetual { asset, perpetual_type }, - TransactionInputType::Earn(asset, earn_type) => GemTransactionInputType::Earn { asset, earn_type }, + TransactionInputType::Earn(asset, earn_type, earn_data) => GemTransactionInputType::Earn { asset, earn_type, earn_data }, } } } @@ -859,7 +865,7 @@ impl From for TransactionInputType { GemTransactionInputType::TransferNft { asset, nft_asset } => TransactionInputType::TransferNft(asset, nft_asset), GemTransactionInputType::Account { asset, account_type } => TransactionInputType::Account(asset, account_type), GemTransactionInputType::Perpetual { asset, perpetual_type } => TransactionInputType::Perpetual(asset, perpetual_type), - GemTransactionInputType::Earn { asset, earn_type } => TransactionInputType::Earn(asset, earn_type), + GemTransactionInputType::Earn { asset, earn_type, earn_data } => TransactionInputType::Earn(asset, earn_type, earn_data), } } } From 5e71b7d8d68d80b6c85d3cc938331ee7600f30c6 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:07:32 +0200 Subject: [PATCH 5/5] Updates typeshare --- crates/primitives/src/asset_metadata.rs | 4 ++++ crates/primitives/src/balance_type.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/crates/primitives/src/asset_metadata.rs b/crates/primitives/src/asset_metadata.rs index fcc63b3f9..4c90de872 100644 --- a/crates/primitives/src/asset_metadata.rs +++ b/crates/primitives/src/asset_metadata.rs @@ -12,12 +12,16 @@ struct AssetMetaData { is_swap_enabled: bool, #[serde(rename = "isStakeEnabled")] is_stake_enabled: bool, + #[serde(rename = "isEarnEnabled")] + is_earn_enabled: bool, #[serde(rename = "isPinned")] is_pinned: bool, #[serde(rename = "isActive")] is_active: bool, #[serde(rename = "stakingApr")] staking_apr: Option, + #[serde(rename = "earnApr")] + earn_apr: Option, #[serde(rename = "rankScore")] rank_score: i32, } diff --git a/crates/primitives/src/balance_type.rs b/crates/primitives/src/balance_type.rs index f3d501896..e45394766 100644 --- a/crates/primitives/src/balance_type.rs +++ b/crates/primitives/src/balance_type.rs @@ -8,4 +8,5 @@ pub enum BalanceType { pendingUnconfirmed, rewards, reserved, + earn, }