Skip to content

Commit f902ac0

Browse files
Hiksangclaude
andcommitted
Add ve(3,3) gauge staking, vote-escrow, and reward farming
- New Gauge, VoteEscrow, Voter traits in defi-core - SolidlyGauge adapter for Ramses, NEST, Ring Few - CLI `defi gauge` command: deposit, withdraw, claim, lock, vote - RewardInfo, GaugeInfo, VeNftInfo types - Ramses voter/ve_token/gauge_factory addresses from official docs - NEST veNEST address verified on-chain Supports full ve(3,3) farming cycle: LP stake → gauge rewards → lock veNFT → vote → claim bribes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e3c4a48 commit f902ac0

11 files changed

Lines changed: 621 additions & 3 deletions

File tree

config/protocols/dex/nest.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ native = true
88
description = "Algebra V3 ve(3,3) MetaDEX on HyperEVM ($4M TVL)"
99

1010
[protocol.contracts]
11-
# NEST uses pool_deployer instead of a typical swap router.
12-
# Setting router = pool_deployer as workaround; needs custom handling for swaps.
1311
router = "0x3842CE04380b8655a3a47ed87ea0d311adca161f"
1412
pool_deployer = "0x3842CE04380b8655a3a47ed87ea0d311adca161f"
1513
position_manager = "0xeaf58788a405f3253814b4559391a22be8616250"
1614
voter = "0x566bdc5444fd5fe5d93ec379Bd66eC861ddbA901"
15+
ve_token = "0x2f2Ae07e3cc3391A2E27825652BA8DcdD5412074"

config/protocols/dex/ramses_cl.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ description = "Ramses concentrated liquidity on HyperEVM ($2.8M TVL)"
1111
factory = "0x07E60782535752be279929e2DFfDd136Db2e6b45"
1212
router = "0x76D91074B46fF76E04FE59a90526a40009943fd2"
1313
quoter = "0x403Bf94fe505cA0F0b1563C350B57dCeC8303ECd"
14+
voter = "0x9aab8C415aF5936b09C595B09B1ff15cbaDCD843"
15+
ve_token = "0xAE6D5FcE541216BDA471D311425B5412D9f1DEb9"
16+
gauge_factory = "0xE17988013E15d29B655634Da0056ba27BA4D3E27"

config/protocols/dex/ramses_hl.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ description = "Ramses V2 AMM on HyperEVM ($2.6M TVL)"
1010
[protocol.contracts]
1111
factory = "0xd0a07E160511c40ccD5340e94660E9C9c01b0D27"
1212
router = "0xdcC44285fBc236457A5cd91C2f77AD8421B0D8ED"
13+
voter = "0x9aab8C415aF5936b09C595B09B1ff15cbaDCD843"
14+
ve_token = "0xAE6D5FcE541216BDA471D311425B5412D9f1DEb9"
15+
gauge_factory = "0xE17988013E15d29B655634Da0056ba27BA4D3E27"
16+
minter = "0x252aCC15430a26748CED7376b317e74e250FcF00"
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
use alloy::primitives::{Address, U256};
2+
use clap::{Args, Subcommand};
3+
4+
use defi_core::error::{DefiError, Result};
5+
use defi_core::registry::{ChainConfig, Registry};
6+
7+
use crate::executor::Executor;
8+
use crate::output::OutputMode;
9+
10+
#[derive(Args)]
11+
pub struct GaugeArgs {
12+
#[command(subcommand)]
13+
pub command: GaugeCommand,
14+
}
15+
16+
#[derive(Subcommand)]
17+
pub enum GaugeCommand {
18+
/// Deposit LP tokens into a gauge for farming
19+
Deposit {
20+
/// Protocol to use (e.g., ramses-cl, nest-v1)
21+
#[arg(long)]
22+
protocol: String,
23+
/// Gauge contract address
24+
#[arg(long)]
25+
gauge: String,
26+
/// LP token amount (raw U256)
27+
#[arg(long)]
28+
amount: String,
29+
/// veNFT token ID for boosted rewards (optional)
30+
#[arg(long)]
31+
ve_nft: Option<String>,
32+
},
33+
/// Withdraw LP tokens from a gauge
34+
Withdraw {
35+
/// Protocol to use
36+
#[arg(long)]
37+
protocol: String,
38+
/// Gauge contract address
39+
#[arg(long)]
40+
gauge: String,
41+
/// LP token amount (raw U256)
42+
#[arg(long)]
43+
amount: String,
44+
},
45+
/// Claim earned rewards from a gauge
46+
Claim {
47+
/// Protocol to use
48+
#[arg(long)]
49+
protocol: String,
50+
/// Gauge contract address
51+
#[arg(long)]
52+
gauge: String,
53+
},
54+
/// Create a veNFT lock
55+
Lock {
56+
/// Protocol to use
57+
#[arg(long)]
58+
protocol: String,
59+
/// Amount to lock (human-readable, 18 decimals)
60+
#[arg(long)]
61+
amount: String,
62+
/// Lock duration in days
63+
#[arg(long, default_value = "365")]
64+
days: u64,
65+
},
66+
/// Vote on gauge emissions with veNFT
67+
Vote {
68+
/// Protocol to use
69+
#[arg(long)]
70+
protocol: String,
71+
/// veNFT token ID
72+
#[arg(long)]
73+
ve_nft: String,
74+
/// Pool addresses (comma-separated)
75+
#[arg(long)]
76+
pools: String,
77+
/// Vote weights (comma-separated, same order as pools)
78+
#[arg(long)]
79+
weights: String,
80+
},
81+
}
82+
83+
fn parse_amount_18(amount: &str) -> Result<U256> {
84+
let parts: Vec<&str> = amount.split('.').collect();
85+
let (whole, frac) = match parts.len() {
86+
1 => (parts[0], ""),
87+
2 => (parts[0], parts[1]),
88+
_ => return Err(DefiError::InvalidParam("Invalid amount format".to_string())),
89+
};
90+
let whole_val = U256::from(
91+
whole
92+
.parse::<u64>()
93+
.map_err(|e| DefiError::InvalidParam(format!("Invalid amount: {e}")))?,
94+
);
95+
let frac_val = if frac.is_empty() {
96+
U256::ZERO
97+
} else {
98+
let frac_padded = format!("{:0<18}", frac);
99+
U256::from(
100+
frac_padded[..18]
101+
.parse::<u64>()
102+
.map_err(|e| DefiError::InvalidParam(format!("Invalid fractional amount: {e}")))?,
103+
)
104+
};
105+
Ok(whole_val * U256::from(10u64).pow(U256::from(18)) + frac_val)
106+
}
107+
108+
fn parse_address(s: &str) -> Result<Address> {
109+
s.parse::<Address>()
110+
.map_err(|e| DefiError::InvalidParam(format!("Invalid address '{s}': {e}")))
111+
}
112+
113+
fn parse_u256(s: &str) -> Result<U256> {
114+
U256::from_str_radix(s, 10)
115+
.map_err(|e| DefiError::InvalidParam(format!("Invalid U256 '{s}': {e}")))
116+
}
117+
118+
pub async fn run(
119+
args: GaugeArgs,
120+
registry: &Registry,
121+
_chain: &ChainConfig,
122+
executor: &Executor,
123+
output: &OutputMode,
124+
) -> Result<()> {
125+
match args.command {
126+
GaugeCommand::Deposit {
127+
protocol,
128+
gauge,
129+
amount,
130+
ve_nft,
131+
} => {
132+
let entry = registry.get_protocol(&protocol)?;
133+
let gauge_adapter = defi_protocols::factory::create_gauge(entry)?;
134+
let gauge_addr = parse_address(&gauge)?;
135+
let amount_val = parse_amount_18(&amount)?;
136+
let token_id = ve_nft.as_deref().map(parse_u256).transpose()?;
137+
138+
let tx = gauge_adapter
139+
.build_deposit(gauge_addr, amount_val, token_id)
140+
.await?;
141+
let result = executor.execute(tx).await?;
142+
output.print(&result)?;
143+
}
144+
GaugeCommand::Withdraw {
145+
protocol,
146+
gauge,
147+
amount,
148+
} => {
149+
let entry = registry.get_protocol(&protocol)?;
150+
let gauge_adapter = defi_protocols::factory::create_gauge(entry)?;
151+
let gauge_addr = parse_address(&gauge)?;
152+
let amount_val = parse_amount_18(&amount)?;
153+
154+
let tx = gauge_adapter.build_withdraw(gauge_addr, amount_val).await?;
155+
let result = executor.execute(tx).await?;
156+
output.print(&result)?;
157+
}
158+
GaugeCommand::Claim { protocol, gauge } => {
159+
let entry = registry.get_protocol(&protocol)?;
160+
let gauge_adapter = defi_protocols::factory::create_gauge(entry)?;
161+
let gauge_addr = parse_address(&gauge)?;
162+
163+
let tx = gauge_adapter.build_claim_rewards(gauge_addr).await?;
164+
let result = executor.execute(tx).await?;
165+
output.print(&result)?;
166+
}
167+
GaugeCommand::Lock {
168+
protocol,
169+
amount,
170+
days,
171+
} => {
172+
let entry = registry.get_protocol(&protocol)?;
173+
let system = defi_protocols::factory::create_gauge(entry)?;
174+
let amount_val = parse_amount_18(&amount)?;
175+
let lock_duration_secs = days * 86400;
176+
177+
let tx = system
178+
.build_create_lock(amount_val, lock_duration_secs)
179+
.await?;
180+
let result = executor.execute(tx).await?;
181+
output.print(&result)?;
182+
}
183+
GaugeCommand::Vote {
184+
protocol,
185+
ve_nft,
186+
pools,
187+
weights,
188+
} => {
189+
let entry = registry.get_protocol(&protocol)?;
190+
let system = defi_protocols::factory::create_gauge(entry)?;
191+
let token_id = parse_u256(&ve_nft)?;
192+
let pool_addrs: Result<Vec<Address>> =
193+
pools.split(',').map(|s| parse_address(s.trim())).collect();
194+
let weight_vals: Result<Vec<U256>> =
195+
weights.split(',').map(|s| parse_u256(s.trim())).collect();
196+
197+
let tx = system
198+
.build_vote(token_id, pool_addrs?, weight_vals?)
199+
.await?;
200+
let result = executor.execute(tx).await?;
201+
output.print(&result)?;
202+
}
203+
}
204+
Ok(())
205+
}

crates/defi-cli/src/commands/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod bridge;
22
pub mod cdp;
33
pub mod dex;
4+
pub mod gauge;
45
pub mod lending;
56
pub mod schema;
67
pub mod staking;
@@ -52,6 +53,8 @@ pub enum Commands {
5253
Schema(schema::SchemaArgs),
5354
/// DEX operations: swap, quote, compare
5455
Dex(dex::DexArgs),
56+
/// Gauge operations: deposit, withdraw, claim, lock, vote (ve(3,3))
57+
Gauge(gauge::GaugeArgs),
5558
/// Lending operations: supply, borrow, repay, withdraw, rates, position
5659
Lending(lending::LendingArgs),
5760
/// CDP operations: open, adjust, close, info
@@ -82,6 +85,7 @@ pub async fn run(cli: Cli) -> Result<(), DefiError> {
8285
Commands::Status(args) => status::run(args, &registry, &output_mode).await,
8386
Commands::Schema(args) => schema::run(args, &output_mode).await,
8487
Commands::Dex(args) => dex::run(args, &registry, chain, &executor, &output_mode).await,
88+
Commands::Gauge(args) => gauge::run(args, &registry, chain, &executor, &output_mode).await,
8589
Commands::Lending(args) => {
8690
lending::run(args, &registry, chain, &executor, &output_mode).await
8791
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use alloy::primitives::{Address, U256};
2+
use async_trait::async_trait;
3+
4+
use crate::error::Result;
5+
use crate::types::{DeFiTx, RewardInfo};
6+
7+
/// ve(3,3) Gauge operations — stake LP tokens to earn emissions
8+
#[async_trait]
9+
pub trait Gauge: Send + Sync {
10+
fn name(&self) -> &str;
11+
12+
/// Deposit LP tokens into gauge
13+
async fn build_deposit(
14+
&self,
15+
gauge: Address,
16+
amount: U256,
17+
token_id: Option<U256>,
18+
) -> Result<DeFiTx>;
19+
20+
/// Withdraw LP tokens from gauge
21+
async fn build_withdraw(&self, gauge: Address, amount: U256) -> Result<DeFiTx>;
22+
23+
/// Claim earned rewards from gauge
24+
async fn build_claim_rewards(&self, gauge: Address) -> Result<DeFiTx>;
25+
26+
/// Get pending rewards for a user
27+
async fn get_pending_rewards(&self, gauge: Address, user: Address) -> Result<Vec<RewardInfo>>;
28+
}
29+
30+
/// ve(3,3) Vote-escrow operations — lock tokens for veNFT
31+
#[async_trait]
32+
pub trait VoteEscrow: Send + Sync {
33+
fn name(&self) -> &str;
34+
35+
/// Create a new veNFT lock
36+
async fn build_create_lock(&self, amount: U256, lock_duration: u64) -> Result<DeFiTx>;
37+
38+
/// Increase lock amount
39+
async fn build_increase_amount(&self, token_id: U256, amount: U256) -> Result<DeFiTx>;
40+
41+
/// Increase lock duration
42+
async fn build_increase_unlock_time(
43+
&self,
44+
token_id: U256,
45+
lock_duration: u64,
46+
) -> Result<DeFiTx>;
47+
48+
/// Withdraw after lock expires
49+
async fn build_withdraw_expired(&self, token_id: U256) -> Result<DeFiTx>;
50+
}
51+
52+
/// ve(3,3) Voter operations — vote on gauge emissions
53+
#[async_trait]
54+
pub trait Voter: Send + Sync {
55+
fn name(&self) -> &str;
56+
57+
/// Vote for gauges with veNFT
58+
async fn build_vote(
59+
&self,
60+
token_id: U256,
61+
pools: Vec<Address>,
62+
weights: Vec<U256>,
63+
) -> Result<DeFiTx>;
64+
65+
/// Claim bribes for voted pools
66+
async fn build_claim_bribes(&self, bribes: Vec<Address>, token_id: U256) -> Result<DeFiTx>;
67+
68+
/// Claim trading fees
69+
async fn build_claim_fees(&self, fees: Vec<Address>, token_id: U256) -> Result<DeFiTx>;
70+
}
71+
72+
/// Combined ve(3,3) system — gauge staking + vote-escrow + voter
73+
///
74+
/// Implementors of this trait provide the full ve(3,3) stack.
75+
/// The trait is auto-implemented for any type implementing all three sub-traits.
76+
pub trait GaugeSystem: Gauge + VoteEscrow + Voter {}
77+
78+
impl<T: Gauge + VoteEscrow + Voter> GaugeSystem for T {}

crates/defi-core/src/traits/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod bridge;
22
pub mod cdp;
33
pub mod derivatives;
44
pub mod dex;
5+
pub mod gauge;
56
pub mod lending;
67
pub mod liquid_staking;
78
pub mod options;
@@ -13,6 +14,7 @@ pub use bridge::Bridge;
1314
pub use cdp::Cdp;
1415
pub use derivatives::Derivatives;
1516
pub use dex::Dex;
17+
pub use gauge::{Gauge, GaugeSystem, VoteEscrow, Voter};
1618
pub use lending::Lending;
1719
pub use liquid_staking::LiquidStaking;
1820
pub use options::Options;

crates/defi-core/src/types.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,33 @@ pub struct OptionParams {
325325
pub amount: U256,
326326
}
327327

328+
// === ve(3,3) Types ===
329+
330+
#[derive(Debug, Clone, Serialize, Deserialize)]
331+
pub struct RewardInfo {
332+
pub token: Address,
333+
pub symbol: String,
334+
pub amount: U256,
335+
pub value_usd: Option<f64>,
336+
}
337+
338+
#[derive(Debug, Clone, Serialize, Deserialize)]
339+
pub struct GaugeInfo {
340+
pub gauge: Address,
341+
pub pool: Address,
342+
pub total_staked: U256,
343+
pub reward_rate: U256,
344+
pub rewards: Vec<RewardInfo>,
345+
}
346+
347+
#[derive(Debug, Clone, Serialize, Deserialize)]
348+
pub struct VeNftInfo {
349+
pub token_id: U256,
350+
pub amount: U256,
351+
pub unlock_time: u64,
352+
pub voting_power: U256,
353+
}
354+
328355
// === Yield Types ===
329356

330357
#[derive(Debug, Clone, Serialize, Deserialize)]

0 commit comments

Comments
 (0)