wip/gobbler-lps

This commit is contained in:
stacc 2024-10-11 18:12:04 +01:00
parent 7f1b42f70f
commit 60acc16480
5 changed files with 553 additions and 344 deletions

7
Cargo.lock generated
View File

@ -2111,6 +2111,9 @@ dependencies = [
"solana-program-test",
"solana-sdk",
"spl-associated-token-account 1.1.3",
"spl-memo 3.0.1",
"spl-token 3.5.0",
"spl-token-2022 0.9.0",
"tracing",
]
@ -5358,7 +5361,7 @@ dependencies = [
[[package]]
name = "quic-geyser-client"
version = "0.1.5"
source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_new_streaming#b0fa0a2f50240ab6c1e7a59b928ea74da9a6785b"
source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_v1.17.29#594077ccebfde826920192c17b0e501bb185650e"
dependencies = [
"anyhow",
"bincode",
@ -5375,7 +5378,7 @@ dependencies = [
[[package]]
name = "quic-geyser-common"
version = "0.1.5"
source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_new_streaming#b0fa0a2f50240ab6c1e7a59b928ea74da9a6785b"
source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_v1.17.29#594077ccebfde826920192c17b0e501bb185650e"
dependencies = [
"anyhow",
"bincode",

View File

@ -30,6 +30,10 @@ serde_derive = "1.0"
mango-feeds-connector = { workspace = true }
# raydium-cp
raydium-cp-swap = "0.1.143"
spl-memo = "3.0.0"
spl-token = "3.5.0"
spl-token-2022 = "0.9.0"
[dev-dependencies]
router-test-lib = { path = "../router-test-lib", version = "0.1" }

View File

@ -7,7 +7,9 @@ use anchor_spl::token_2022::spl_token_2022::extension::{
};
use anchor_spl::token_2022::spl_token_2022::state::Mint;
use mango_feeds_connector::chain_data::AccountData;
use raydium_cp_swap::curve::{ConstantProductCurve, CurveCalculator, Fees, TradeDirection};
use raydium_cp_swap::curve::{
ConstantProductCurve, CurveCalculator, Fees, TradeDirection,
};
use raydium_cp_swap::states::{AmmConfig, PoolState, PoolStatusBitIndex};
use solana_program::clock::Clock;
use solana_program::pubkey::Pubkey;
@ -18,11 +20,30 @@ use std::panic;
use router_lib::dex::{DexEdge, DexEdgeIdentifier};
/// Enums to represent operations and directions
#[derive(Clone, Copy, Debug)]
pub enum Operation {
Swap,
Deposit,
Withdraw,
}
#[derive(Clone, Copy, Debug)]
pub enum Direction {
AtoB,
BtoA,
AandBtoLP,
LPtoAandB,
}
/// Modify GobblerEdgeIdentifier to include operation and direction
pub struct GobblerEdgeIdentifier {
pub pool: Pubkey,
pub mint_a: Pubkey,
pub mint_b: Pubkey,
pub is_a_to_b: bool,
pub lp_mint: Pubkey,
pub operation: Operation,
pub direction: Direction,
}
impl DexEdgeIdentifier for GobblerEdgeIdentifier {
@ -31,19 +52,41 @@ impl DexEdgeIdentifier for GobblerEdgeIdentifier {
}
fn desc(&self) -> String {
format!("Gobbler_{}", self.pool)
format!(
"Gobbler_{}_{}_{}",
self.operation_string(),
self.direction_string(),
self.pool
)
}
fn input_mint(&self) -> Pubkey {
self.mint_a
match self.operation {
Operation::Swap => match self.direction {
Direction::AtoB => self.mint_a,
Direction::BtoA => self.mint_b,
_ => self.mint_a, // Default case
},
Operation::Deposit => self.mint_a, // For deposit, input mints are mint_a and mint_b
Operation::Withdraw => self.lp_mint,
}
}
fn output_mint(&self) -> Pubkey {
self.mint_b
match self.operation {
Operation::Swap => match self.direction {
Direction::AtoB => self.mint_b,
Direction::BtoA => self.mint_a,
_ => self.mint_b, // Default case
},
Operation::Deposit => self.lp_mint,
Operation::Withdraw => self.mint_a, // For withdraw, output mints are mint_a and mint_b
}
}
fn accounts_needed(&self) -> usize {
11
// Adjust based on operation
11 // This might vary depending on actual requirements
}
fn as_any(&self) -> &dyn Any {
@ -51,6 +94,26 @@ impl DexEdgeIdentifier for GobblerEdgeIdentifier {
}
}
impl GobblerEdgeIdentifier {
fn operation_string(&self) -> &'static str {
match self.operation {
Operation::Swap => "Swap",
Operation::Deposit => "Deposit",
Operation::Withdraw => "Withdraw",
}
}
fn direction_string(&self) -> &'static str {
match self.direction {
Direction::AtoB => "AtoB",
Direction::BtoA => "BtoA",
Direction::AandBtoLP => "AandBtoLP",
Direction::LPtoAandB => "LPtoAandB",
}
}
}
/// Modify GobblerEdge to include operation and direction
pub struct GobblerEdge {
pub pool: PoolState,
pub config: AmmConfig,
@ -58,6 +121,9 @@ pub struct GobblerEdge {
pub vault_1_amount: u64,
pub mint_0: Option<TransferFeeConfig>,
pub mint_1: Option<TransferFeeConfig>,
pub lp_supply: u64,
pub operation: Operation,
pub direction: Direction,
}
impl DexEdge for GobblerEdge {
@ -94,11 +160,8 @@ pub fn swap_base_input(
amount_in: u64,
) -> anyhow::Result<(u64, u64, u64)> {
let res = panic::catch_unwind(|| {
let pool_state = pool;
let block_timestamp = pool_state.open_time + 1; // TODO: This should be the actual clock
if !pool_state.get_status_by_bit(PoolStatusBitIndex::Swap)
|| block_timestamp < pool_state.open_time
{
let block_timestamp = Clock::get()?.unix_timestamp as u64;
if !pool.get_status_by_bit(PoolStatusBitIndex::Swap) || block_timestamp < pool.open_time {
return Err(anyhow!("Pool is not trading"));
}
@ -108,48 +171,53 @@ pub fn swap_base_input(
return Err(anyhow!("Amount too low after transfer fee"));
}
let (total_input_token_amount, total_output_token_amount) = if input_vault_key == pool_state.token_0_vault
&& output_vault_key == pool_state.token_1_vault
{
vault_amount_without_fee(pool_state, input_vault_amount, output_vault_amount)?
} else if input_vault_key == pool_state.token_1_vault
&& output_vault_key == pool_state.token_0_vault
{
let (out, inp) = vault_amount_without_fee(pool_state, output_vault_amount, input_vault_amount)?;
(inp, out)
} else {
return Err(anyhow!("Invalid vault configuration"));
};
let (total_input_token_amount, total_output_token_amount) =
if input_vault_key == pool.token_0_vault && output_vault_key == pool.token_1_vault {
vault_amount_without_fee(pool, input_vault_amount, output_vault_amount)?
} else if input_vault_key == pool.token_1_vault && output_vault_key == pool.token_0_vault
{
let (out, inp) =
vault_amount_without_fee(pool, output_vault_amount, input_vault_amount)?;
(inp, out)
} else {
return Err(anyhow!("Invalid vault configuration"));
};
let (input_token_creator_rate, input_token_lp_rate) =
if input_vault_key == pool.token_0_vault {
(amm_config.token_0_creator_rate, amm_config.token_0_lp_rate)
} else {
(amm_config.token_1_creator_rate, amm_config.token_1_lp_rate)
};
let (input_token_creator_rate, input_token_lp_rate) = if input_vault_key == pool_state.token_0_vault {
(amm_config.token_0_creator_rate, amm_config.token_0_lp_rate)
} else {
(amm_config.token_1_creator_rate, amm_config.token_1_lp_rate)
};
let protocol_fee = (amm_config.token_0_creator_rate
let protocol_fee = (amm_config.token_0_creator_rate
+ amm_config.token_1_creator_rate
+ amm_config.token_0_lp_rate
+ amm_config.token_1_lp_rate) / 10000;
+ amm_config.token_0_lp_rate
+ amm_config.token_1_lp_rate)
/ 10000;
let swap_result = raydium_cp_swap::curve::CurveCalculator::swap_base_input(
let swap_result = CurveCalculator::swap_base_input(
actual_amount_in.into(),
total_input_token_amount.into(),
total_output_token_amount.into(),
protocol_fee+input_token_creator_rate+input_token_lp_rate,
protocol_fee + input_token_creator_rate + input_token_lp_rate,
input_token_creator_rate,
input_token_lp_rate,
).ok_or(anyhow!("Swap calculation failed"))?;
)
.ok_or(anyhow!("Swap calculation failed"))?;
let output_transfer_fee = get_transfer_fee(output_mint, swap_result.destination_amount_swapped.try_into()?)?;
let amount_received = swap_result.destination_amount_swapped.saturating_sub(output_transfer_fee.into());
let output_transfer_fee =
get_transfer_fee(output_mint, swap_result.destination_amount_swapped.try_into()?)?;
let amount_received = swap_result
.destination_amount_swapped
.saturating_sub(output_transfer_fee.into());
Ok((
amount_in,
amount_received.try_into().map_err(|e| anyhow!("Failed to convert amount_received: {}", e))?,
(protocol_fee+input_token_creator_rate+input_token_lp_rate).try_into().map_err(|e| anyhow!("Failed to convert fees: {}", e))?,
amount_received.try_into()?,
(protocol_fee + input_token_creator_rate + input_token_lp_rate)
.try_into()
.map_err(|e| anyhow!("Failed to convert fees: {}", e))?,
))
});
@ -162,18 +230,15 @@ pub fn swap_base_output(
amm_config: &AmmConfig,
input_vault_key: Pubkey,
input_vault_amount: u64,
_input_mint: &Option<TransferFeeConfig>,
input_mint: &Option<TransferFeeConfig>,
output_vault_key: Pubkey,
output_vault_amount: u64,
output_mint: &Option<TransferFeeConfig>,
amount_out: u64,
) -> anyhow::Result<(u64, u64, u64)> {
let res = panic::catch_unwind(|| {
let pool_state = pool;
let block_timestamp = pool_state.open_time + 1; // TODO: This should be the actual clock
if !pool_state.get_status_by_bit(PoolStatusBitIndex::Swap)
|| block_timestamp < pool_state.open_time
{
let block_timestamp = Clock::get()?.unix_timestamp as u64;
if !pool.get_status_by_bit(PoolStatusBitIndex::Swap) || block_timestamp < pool.open_time {
return Err(anyhow!("Pool is not trading"));
}
@ -182,53 +247,62 @@ pub fn swap_base_output(
}
let output_transfer_fee = get_transfer_fee(output_mint, amount_out)?;
let actual_amount_out = amount_out.checked_add(output_transfer_fee)
let actual_amount_out = amount_out
.checked_add(output_transfer_fee)
.ok_or_else(|| anyhow!("Output amount overflow"))?;
let (total_input_token_amount, total_output_token_amount) = if input_vault_key == pool_state.token_0_vault
&& output_vault_key == pool_state.token_1_vault
{
vault_amount_without_fee(pool_state, input_vault_amount, output_vault_amount)?
} else if input_vault_key == pool_state.token_1_vault
&& output_vault_key == pool_state.token_0_vault
{
let (out, inp) = vault_amount_without_fee(pool_state, output_vault_amount, input_vault_amount)?;
(inp, out)
} else {
return Err(anyhow!("Invalid vault configuration"));
};
let (total_input_token_amount, total_output_token_amount) =
if input_vault_key == pool.token_0_vault && output_vault_key == pool.token_1_vault {
vault_amount_without_fee(pool, input_vault_amount, output_vault_amount)?
} else if input_vault_key == pool.token_1_vault && output_vault_key == pool.token_0_vault
{
let (out, inp) =
vault_amount_without_fee(pool, output_vault_amount, input_vault_amount)?;
(inp, out)
} else {
return Err(anyhow!("Invalid vault configuration"));
};
if total_output_token_amount < actual_amount_out.into() {
return Err(anyhow!("Insufficient liquidity"));
}
let (input_token_creator_rate, input_token_lp_rate) = if input_vault_key == pool_state.token_0_vault {
(amm_config.token_0_creator_rate, amm_config.token_0_lp_rate)
} else {
(amm_config.token_1_creator_rate, amm_config.token_1_lp_rate)
};
let (input_token_creator_rate, input_token_lp_rate) =
if input_vault_key == pool.token_0_vault {
(amm_config.token_0_creator_rate, amm_config.token_0_lp_rate)
} else {
(amm_config.token_1_creator_rate, amm_config.token_1_lp_rate)
};
let protocol_fee = (amm_config.token_0_creator_rate
let protocol_fee = (amm_config.token_0_creator_rate
+ amm_config.token_1_creator_rate
+ amm_config.token_0_lp_rate
+ amm_config.token_1_lp_rate) / 10000;
+ amm_config.token_0_lp_rate
+ amm_config.token_1_lp_rate)
/ 10000;
let swap_result = raydium_cp_swap::curve::CurveCalculator::swap_base_output(
let swap_result = CurveCalculator::swap_base_output(
actual_amount_out.into(),
total_input_token_amount.into(),
total_output_token_amount.into(),
protocol_fee+input_token_creator_rate+input_token_lp_rate,
protocol_fee + input_token_creator_rate + input_token_lp_rate,
input_token_creator_rate,
input_token_lp_rate,
).ok_or(anyhow!("Swap calculation failed"))?;
)
.ok_or(anyhow!("Swap calculation failed"))?;
let input_transfer_fee = get_transfer_fee(_input_mint, swap_result.source_amount_swapped.try_into()?)?;
let amount_in = swap_result.source_amount_swapped.saturating_add(input_transfer_fee.into());
let input_transfer_fee =
get_transfer_fee(input_mint, swap_result.source_amount_swapped.try_into()?)?;
let amount_in = swap_result
.source_amount_swapped
.saturating_add(input_transfer_fee.into());
Ok((
amount_in.try_into().map_err(|e| anyhow!("Failed to convert amount_in: {}", e))?,
amount_in.try_into()?,
amount_out,
swap_result.total_fees.try_into().map_err(|e| anyhow!("Failed to convert fees: {}", e))?,
swap_result
.total_fees
.try_into()
.map_err(|e| anyhow!("Failed to convert fees: {}", e))?,
))
});
@ -242,7 +316,7 @@ pub fn get_transfer_fee(
let fee = if let Some(transfer_fee_config) = mint_info {
transfer_fee_config
.calculate_epoch_fee(Clock::get()?.epoch, pre_fee_amount)
.unwrap()
.unwrap_or(0)
} else {
0
};
@ -257,9 +331,9 @@ pub fn vault_amount_without_fee(
Ok((
vault_0
.checked_sub(pool.protocol_fees_token_0 + pool.fund_fees_token_0)
.ok_or(anyhow::format_err!("invalid sub"))?,
.ok_or(anyhow::format_err!("Invalid subtraction for vault_0"))?,
vault_1
.checked_sub(pool.protocol_fees_token_1 + pool.fund_fees_token_1)
.ok_or(anyhow::format_err!("invalid sub"))?,
.ok_or(anyhow::format_err!("Invalid subtraction for vault_1"))?,
))
}

View File

@ -1,9 +1,8 @@
use crate::edge::{swap_base_input, swap_base_output, GobblerEdge, GobblerEdgeIdentifier};
use crate::edge::{ swap_base_input, swap_base_output, Direction, GobblerEdge, GobblerEdgeIdentifier, Operation, _get_transfer_config};
use crate::gobbler_ix_builder;
use anchor_lang::{AccountDeserialize, Discriminator, Id};
use anchor_spl::token::spl_token::state::AccountState;
use anchor_spl::token::{spl_token, Token};
use anchor_spl::token_2022::spl_token_2022;
use anyhow::Context;
use async_trait::async_trait;
use itertools::Itertools;
@ -43,82 +42,108 @@ impl DexInterface for GobblerDex {
where
Self: Sized,
{
let pools =
fetch_raydium_account::<PoolState>(rpc, raydium_cp_swap::id(), PoolState::LEN).await?;
// Fetch all PoolState accounts
let pools = fetch_gobbler_pools(rpc).await?;
// Collect vaults to identify any banned ones (e.g., frozen accounts)
let vaults = pools
.iter()
.flat_map(|x| [x.1.token_0_vault, x.1.token_1_vault])
.collect::<HashSet<_>>();
let vaults = rpc.get_multiple_accounts(&vaults).await?;
let banned_vaults = vaults
.iter()
.filter(|x| {
x.1.owner == Token::id()
&& spl_token::state::Account::unpack(x.1.data()).unwrap().state
== AccountState::Frozen
})
.map(|x| x.0)
.flat_map(|(_, pool)| vec![pool.token_0_vault, pool.token_1_vault])
.collect::<HashSet<_>>();
let pools = pools
let vault_accounts = rpc.get_multiple_accounts(&vaults).await?;
let banned_vaults = vault_accounts
.iter()
.filter(|(_pool_pk, pool)| {
pool.token_0_program == Token::id() && pool.token_1_program == Token::id()
// TODO Remove filter when 2022 are working
.filter(|(_, account)| {
account.owner == Token::id()
&& spl_token::state::Account::unpack(account.data())
.map(|acc| acc.state == AccountState::Frozen)
.unwrap_or(false)
})
.filter(|(_pool_pk, pool)| {
!banned_vaults.contains(&pool.token_0_vault)
.map(|(pubkey, _)| *pubkey)
.collect::<HashSet<_>>();
// Filter out pools with banned vaults or unsupported token programs
let valid_pools = pools
.into_iter()
.filter(|(_, pool)| {
pool.token_0_program == Token::id()
&& pool.token_1_program == Token::id()
&& !banned_vaults.contains(&pool.token_0_vault)
&& !banned_vaults.contains(&pool.token_1_vault)
})
.collect_vec();
.collect::<Vec<_>>();
let edge_pairs = pools
.iter()
.map(|(pool_pk, pool)| {
(
Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_0_mint,
mint_b: pool.token_1_mint,
is_a_to_b: true,
}),
Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_1_mint,
mint_b: pool.token_0_mint,
is_a_to_b: false,
}),
)
})
.collect_vec();
// Create edge identifiers for each pool
let mut edge_identifiers = Vec::new();
for (pool_pk, pool) in &valid_pools {
// Swap edges between Token A and Token B
let swap_a_to_b = Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_0_mint,
mint_b: pool.token_1_mint,
lp_mint: pool.lp_mint,
operation: Operation::Swap,
direction: Direction::AtoB,
});
let swap_b_to_a = Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_1_mint,
mint_b: pool.token_0_mint,
lp_mint: pool.lp_mint,
operation: Operation::Swap,
direction: Direction::BtoA,
});
// Deposit edge from (Token A, Token B) to LP Token
let deposit_edge = Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_0_mint,
mint_b: pool.token_1_mint,
lp_mint: pool.lp_mint,
operation: Operation::Deposit,
direction: Direction::AandBtoLP,
});
// Withdrawal edge from LP Token to (Token A, Token B)
let withdraw_edge = Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_0_mint,
mint_b: pool.token_1_mint,
lp_mint: pool.lp_mint,
operation: Operation::Withdraw,
direction: Direction::LPtoAandB,
});
edge_identifiers.push(swap_a_to_b);
edge_identifiers.push(swap_b_to_a);
edge_identifiers.push(deposit_edge);
edge_identifiers.push(withdraw_edge);
}
let mut needed_accounts = HashSet::new();
let mut edges_per_pk = HashMap::new();
let edges_per_pk = {
let mut map = HashMap::new();
for ((pool_pk, pool), (edge_a_to_b, edge_b_to_a)) in pools.iter().zip(edge_pairs.iter())
{
let entry = vec![
edge_a_to_b.clone() as Arc<dyn DexEdgeIdentifier>,
edge_b_to_a.clone(),
];
for (pool_pk, pool) in &valid_pools {
// Collect all necessary accounts
needed_accounts.insert(*pool_pk);
needed_accounts.insert(pool.amm_config);
needed_accounts.insert(pool.token_0_vault);
needed_accounts.insert(pool.token_1_vault);
needed_accounts.insert(pool.lp_mint);
needed_accounts.insert(pool.token_0_mint);
needed_accounts.insert(pool.token_1_mint);
utils::insert_or_extend(&mut map, pool_pk, &entry);
utils::insert_or_extend(&mut map, &pool.amm_config, &entry);
utils::insert_or_extend(&mut map, &pool.token_0_vault, &entry);
utils::insert_or_extend(&mut map, &pool.token_1_vault, &entry);
needed_accounts.insert(*pool_pk);
needed_accounts.insert(pool.amm_config);
needed_accounts.insert(pool.token_0_vault);
needed_accounts.insert(pool.token_1_vault);
// TODO Uncomment for Token-2022
// needed_accounts.insert(pool.token_0_mint);
// needed_accounts.insert(pool.token_1_mint);
}
map
};
// Map edges to their corresponding public keys
let edges = edge_identifiers
.iter()
.filter(|edge| edge.as_any().downcast_ref::<GobblerEdgeIdentifier>().unwrap().pool == *pool_pk)
.cloned()
.collect::<Vec<_>>();
utils::insert_or_extend(&mut edges_per_pk, pool_pk, &edges.into_iter().map(|edge| edge as Arc<dyn DexEdgeIdentifier>).collect::<Vec<_>>());
}
Ok(Arc::new(GobblerDex {
edges: edges_per_pk,
@ -132,12 +157,9 @@ impl DexInterface for GobblerDex {
fn subscription_mode(&self) -> DexSubscriptionMode {
DexSubscriptionMode::Mixed(MixedDexSubscription {
accounts: Default::default(),
accounts: self.needed_accounts.clone(),
programs: HashSet::from([raydium_cp_swap::id()]),
token_accounts_for_owner: HashSet::from([Pubkey::from_str(
"GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL",
)
.unwrap()]),
token_accounts_for_owner: HashSet::new(),
})
}
@ -157,35 +179,39 @@ impl DexInterface for GobblerDex {
let id = id
.as_any()
.downcast_ref::<GobblerEdgeIdentifier>()
.unwrap();
.context("Invalid edge identifier type")?;
let pool_account = chain_data.account(&id.pool)?;
let pool = PoolState::try_deserialize(&mut pool_account.account.data())?;
let config_account = chain_data.account(&pool.amm_config)?;
let config = AmmConfig::try_deserialize(&mut config_account.account.data())?;
let vault_0_account = chain_data.account(&pool.token_0_vault)?;
let vault_0 = spl_token_2022::state::Account::unpack(vault_0_account.account.data())?;
let vault_0 = spl_token::state::Account::unpack(&vault_0_account.account.data())?;
let vault_1_account = chain_data.account(&pool.token_1_vault)?;
let vault_1 = spl_token_2022::state::Account::unpack(vault_1_account.account.data())?;
let vault_1 = spl_token::state::Account::unpack(&vault_1_account.account.data())?;
let transfer_0_fee = None;
let transfer_1_fee = None;
let lp_mint_account = chain_data.account(&pool.lp_mint)?;
let lp_mint = spl_token::state::Mint::unpack(&lp_mint_account.account.data())?;
// TODO Uncomment for Token-2022
// let mint_0_account = chain_data.account(&pool.token_0_mint)?;
// let mint_1_account = chain_data.account(&pool.token_1_mint)?;
// let transfer_0_fee = crate::edge::get_transfer_config(mint_0_account)?;
// let transfer_1_fee = crate::edge::get_transfer_config(mint_1_account)?;
let mint_0_account = chain_data.account(&pool.token_0_mint)?;
let mint_1_account = chain_data.account(&pool.token_1_mint)?;
let mint_0 = _get_transfer_config(&mint_0_account)?;
let mint_1 = _get_transfer_config(&mint_1_account)?;
Ok(Arc::new(GobblerEdge {
pool,
config,
vault_0_amount: vault_0.amount,
vault_1_amount: vault_1.amount,
mint_0: transfer_0_fee,
mint_1: transfer_1_fee,
mint_0,
mint_1,
lp_supply: lp_mint.supply,
operation: id.operation,
direction: id.direction,
}))
}
@ -199,69 +225,36 @@ impl DexInterface for GobblerDex {
let id = id
.as_any()
.downcast_ref::<GobblerEdgeIdentifier>()
.unwrap();
let edge = edge.as_any().downcast_ref::<GobblerEdge>().unwrap();
.context("Invalid edge identifier type")?;
let edge = edge.as_any().downcast_ref::<GobblerEdge>().context("Invalid edge type")?;
if !edge.pool.get_status_by_bit(PoolStatusBitIndex::Swap) {
return Ok(Quote {
in_amount: 0,
out_amount: 0,
fee_amount: 0,
fee_mint: edge.pool.token_0_mint,
});
match id.operation {
Operation::Swap => {
// Handle swap operation
// Similar to previous implementation
// ...
}
Operation::Deposit => {
// Handle deposit operation
// Calculate LP tokens received for given in_amounts of Token A and Token B
// Return a Quote with LP tokens as out_amount
// ...
}
Operation::Withdraw => {
// Handle withdrawal operation
// Calculate amounts of Token A and Token B received for given in_amount of LP tokens
// Return a Quote with total value of tokens received as out_amount
// ...
}
}
let clock = chain_data.account(&Clock::id()).context("read clock")?;
let now_ts = clock.account.deserialize_data::<Clock>()?.unix_timestamp as u64;
if edge.pool.open_time > now_ts {
return Ok(Quote {
in_amount: 0,
out_amount: 0,
fee_amount: 0,
fee_mint: edge.pool.token_0_mint,
});
}
let quote = if id.is_a_to_b {
let result = swap_base_input(
&edge.pool,
&edge.config,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
in_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_0_mint,
}
} else {
let result = swap_base_input(
&edge.pool,
&edge.config,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
in_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_1_mint,
}
};
Ok(quote)
// Placeholder return until implementation is provided
Ok(Quote {
in_amount: 0,
out_amount: 0,
fee_amount: 0,
fee_mint: id.mint_a,
})
}
fn build_swap_ix(
@ -276,15 +269,38 @@ impl DexInterface for GobblerDex {
let id = id
.as_any()
.downcast_ref::<GobblerEdgeIdentifier>()
.unwrap();
gobbler_ix_builder::build_swap_ix(
id,
chain_data,
wallet_pk,
in_amount,
out_amount,
max_slippage_bps,
)
.context("Invalid edge identifier type")?;
match id.operation {
Operation::Swap => {
gobbler_ix_builder::build_swap_ix(
id,
chain_data,
wallet_pk,
in_amount,
out_amount,
max_slippage_bps,
)
}
Operation::Deposit => {
gobbler_ix_builder::build_deposit_ix(
id,
chain_data,
wallet_pk,
in_amount, // Adjust as needed
max_slippage_bps,
)
}
Operation::Withdraw => {
gobbler_ix_builder::build_withdraw_ix(
id,
chain_data,
wallet_pk,
in_amount, // Adjust as needed
max_slippage_bps,
)
}
}
}
fn supports_exact_out(&self, _id: &Arc<dyn DexEdgeIdentifier>) -> bool {
@ -301,81 +317,46 @@ impl DexInterface for GobblerDex {
let id = id
.as_any()
.downcast_ref::<GobblerEdgeIdentifier>()
.unwrap();
let edge = edge.as_any().downcast_ref::<GobblerEdge>().unwrap();
.context("Invalid edge identifier type")?;
let edge = edge.as_any().downcast_ref::<GobblerEdge>().context("Invalid edge type")?;
if !edge.pool.get_status_by_bit(PoolStatusBitIndex::Swap) {
return Ok(Quote {
in_amount: u64::MAX,
out_amount: 0,
fee_amount: 0,
fee_mint: edge.pool.token_0_mint,
});
match id.operation {
Operation::Swap => {
// Handle swap operation
// Similar to previous implementation
// ...
}
Operation::Deposit => {
// Handle deposit operation
// Calculate amounts of Token A and Token B required to receive given out_amount of LP tokens
// Return a Quote with total value of tokens required as in_amount
// ...
}
Operation::Withdraw => {
// Handle withdrawal operation
// Calculate in_amount of LP tokens required to receive given out_amounts of Token A and Token B
// Return a Quote with LP tokens as in_amount
// ...
}
}
let clock = chain_data.account(&Clock::id()).context("read clock")?;
let now_ts = clock.account.deserialize_data::<Clock>()?.unix_timestamp as u64;
if edge.pool.open_time > now_ts {
return Ok(Quote {
in_amount: u64::MAX,
out_amount: 0,
fee_amount: 0,
fee_mint: edge.pool.token_0_mint,
});
}
let quote = if id.is_a_to_b {
let result = swap_base_output(
&edge.pool,
&edge.config,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
out_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_0_mint,
}
} else {
let result = swap_base_output(
&edge.pool,
&edge.config,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
out_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_1_mint,
}
};
Ok(quote)
// Placeholder return until implementation is provided
Ok(Quote {
in_amount: 0,
out_amount: 0,
fee_amount: 0,
fee_mint: id.mint_a,
})
}
}
async fn fetch_raydium_account<T: Discriminator + AccountDeserialize>(
async fn fetch_gobbler_pools(
rpc: &mut RouterRpcClient,
program_id: Pubkey,
len: usize,
) -> anyhow::Result<Vec<(Pubkey, T)>> {
) -> anyhow::Result<Vec<(Pubkey, PoolState)>> {
let config = RpcProgramAccountsConfig {
filters: Some(vec![
RpcFilterType::DataSize(len as u64),
RpcFilterType::Memcmp(Memcmp::new_raw_bytes(0, T::DISCRIMINATOR.to_vec())),
RpcFilterType::DataSize(PoolState::LEN as u64),
RpcFilterType::Memcmp(Memcmp::new_raw_bytes(0, PoolState::DISCRIMINATOR.to_vec())),
]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
@ -386,16 +367,17 @@ async fn fetch_raydium_account<T: Discriminator + AccountDeserialize>(
};
let snapshot = rpc
.get_program_accounts_with_config(&program_id, config)
.get_program_accounts_with_config(&raydium_cp_swap::id(), config)
.await?;
let result = snapshot
.iter()
.into_iter()
.map(|account| {
let pool: T = T::try_deserialize(&mut account.data.as_slice()).unwrap();
let pool: PoolState =
PoolState::try_deserialize(&mut account.data.as_slice()).unwrap();
(account.pubkey, pool)
})
.collect_vec();
.collect::<Vec<_>>();
Ok(result)
}

View File

@ -1,13 +1,17 @@
use crate::edge::GobblerEdgeIdentifier;
use anchor_lang::{AccountDeserialize, Id, InstructionData, ToAccountMetas};
use crate::edge::{GobblerEdgeIdentifier, Operation, Direction};
use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas};
use anchor_spl::associated_token::get_associated_token_address;
use raydium_cp_swap::AUTH_SEED;
use raydium_cp_swap::instruction::{Deposit, Withdraw, SwapBaseInput};
use raydium_cp_swap::program::RaydiumCpSwap;
use raydium_cp_swap::states::PoolState;
use raydium_cp_swap::AUTH_SEED;
use router_lib::dex::{AccountProviderView, SwapInstruction};
use solana_program::instruction::Instruction;
use solana_program::pubkey::Pubkey;
use solana_sdk::account::ReadableAccount;
use spl_memo::id as spl_memo_id;
use spl_token::id as spl_token_id;
use spl_token_2022::id as spl_token_2022_id;
pub fn build_swap_ix(
id: &GobblerEdgeIdentifier,
@ -24,58 +28,200 @@ pub fn build_swap_ix(
let other_amount_threshold =
((out_amount as f64 * (10_000f64 - max_slippage_bps as f64)) / 10_000f64).floor() as u64;
let (input_token_mint, output_token_mint) = if id.is_a_to_b {
(pool.token_0_mint, pool.token_1_mint)
} else {
(pool.token_1_mint, pool.token_0_mint)
};
let (input_token_program, output_token_program) = if id.is_a_to_b {
(pool.token_0_program, pool.token_1_program)
} else {
(pool.token_1_program, pool.token_0_program)
};
let (input_vault, output_vault) = if id.is_a_to_b {
(pool.token_0_vault, pool.token_1_vault)
} else {
(pool.token_1_vault, pool.token_0_vault)
match id.operation {
Operation::Swap => {
// Handle swap operation
let (input_token_mint, output_token_mint) = match id.direction {
Direction::AtoB => (pool.token_0_mint, pool.token_1_mint),
Direction::BtoA => (pool.token_1_mint, pool.token_0_mint),
_ => return Err(anyhow::anyhow!("Invalid direction for swap operation")),
};
let (input_token_program, output_token_program) = match id.direction {
Direction::AtoB => (pool.token_0_program, pool.token_1_program),
Direction::BtoA => (pool.token_1_program, pool.token_0_program),
_ => return Err(anyhow::anyhow!("Invalid direction for swap operation")),
};
let (input_vault, output_vault) = match id.direction {
Direction::AtoB => (pool.token_0_vault, pool.token_1_vault),
Direction::BtoA => (pool.token_1_vault, pool.token_0_vault),
_ => return Err(anyhow::anyhow!("Invalid direction for swap operation")),
};
let (input_token_account, output_token_account) = (
get_associated_token_address(wallet_pk, &input_token_mint),
get_associated_token_address(wallet_pk, &output_token_mint),
);
let instruction = SwapBaseInput {
amount_in: amount,
minimum_amount_out: other_amount_threshold,
};
let (authority, __bump) =
Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &raydium_cp_swap::id());
let accounts = raydium_cp_swap::accounts::Swap {
payer: *wallet_pk,
authority,
amm_config: pool.amm_config,
pool_state: id.pool,
input_token_account,
output_token_account,
input_vault,
output_vault,
input_token_program,
output_token_program,
input_token_mint,
output_token_mint,
observation_state: pool.observation_key,
};
let result = SwapInstruction {
instruction: Instruction {
program_id: raydium_cp_swap::id(),
accounts: accounts.to_account_metas(None),
data: instruction.data(),
},
out_pubkey: output_token_account,
out_mint: output_token_mint,
in_amount_offset: 8,
cu_estimate: Some(40_000),
};
Ok(result)
}
Operation::Deposit => build_deposit_ix(
id,
chain_data,
wallet_pk,
in_amount,
max_slippage_bps,
),
Operation::Withdraw => build_withdraw_ix(
id,
chain_data,
wallet_pk,
in_amount,
max_slippage_bps,
),
}
}
pub fn build_deposit_ix(
id: &GobblerEdgeIdentifier,
chain_data: &AccountProviderView,
wallet_pk: &Pubkey,
lp_amount: u64,
max_slippage_bps: i32,
) -> anyhow::Result<SwapInstruction> {
let pool_account = chain_data.account(&id.pool)?;
let pool = PoolState::try_deserialize(&mut pool_account.account.data())?;
let (token_a_mint, token_b_mint) = (pool.token_0_mint, pool.token_1_mint);
let (vault_a, vault_b) = (pool.token_0_vault, pool.token_1_vault);
let user_token_a_account = get_associated_token_address(wallet_pk, &token_a_mint);
let user_token_b_account = get_associated_token_address(wallet_pk, &token_b_mint);
let user_lp_token_account = get_associated_token_address(wallet_pk, &pool.lp_mint);
// You may need to calculate maximum amounts based on slippage
let instruction = Deposit {
lp_token_amount: lp_amount,
maximum_token_0_amount: u64::MAX, // Adjust as needed
maximum_token_1_amount: u64::MAX, // Adjust as needed
};
let (input_token_account, output_token_account) = (
get_associated_token_address(wallet_pk, &input_token_mint),
get_associated_token_address(wallet_pk, &output_token_mint),
);
let instruction = raydium_cp_swap::instruction::SwapBaseInput {
amount_in: amount,
minimum_amount_out: other_amount_threshold,
};
let (authority, __bump) =
Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &raydium_cp_swap::id());
let accounts = raydium_cp_swap::accounts::Swap {
payer: *wallet_pk,
let accounts = raydium_cp_swap::accounts::Deposit {
owner: *wallet_pk,
authority,
amm_config: pool.amm_config,
pool_state: id.pool,
input_token_account,
output_token_account,
input_vault,
output_vault,
input_token_program,
output_token_program,
input_token_mint,
output_token_mint,
observation_state: pool.observation_key,
owner_lp_token: user_lp_token_account,
token_0_account: user_token_a_account,
token_1_account: user_token_b_account,
token_0_vault: vault_a,
token_1_vault: vault_b,
token_program: spl_token_id(),
token_program_2022: spl_token_2022_id(),
vault_0_mint: token_a_mint,
vault_1_mint: token_b_mint,
lp_mint: pool.lp_mint,
};
let deposit_instruction = Instruction {
program_id: raydium_cp_swap::id(),
accounts: accounts.to_account_metas(None),
data: instruction.data(),
};
let result = SwapInstruction {
instruction: Instruction {
program_id: raydium_cp_swap::id(),
accounts: accounts.to_account_metas(None),
data: instruction.data(),
},
out_pubkey: output_token_account,
out_mint: output_token_mint,
instruction: deposit_instruction,
out_pubkey: user_lp_token_account,
out_mint: pool.lp_mint,
in_amount_offset: 8,
cu_estimate: Some(40_000),
};
Ok(result)
}
pub fn build_withdraw_ix(
id: &GobblerEdgeIdentifier,
chain_data: &AccountProviderView,
wallet_pk: &Pubkey,
lp_amount: u64,
max_slippage_bps: i32,
) -> anyhow::Result<SwapInstruction> {
let pool_account = chain_data.account(&id.pool)?;
let pool = PoolState::try_deserialize(&mut pool_account.account.data())?;
let (token_a_mint, token_b_mint) = (pool.token_0_mint, pool.token_1_mint);
let (vault_a, vault_b) = (pool.token_0_vault, pool.token_1_vault);
let user_token_a_account = get_associated_token_address(wallet_pk, &token_a_mint);
let user_token_b_account = get_associated_token_address(wallet_pk, &token_b_mint);
let user_lp_token_account = get_associated_token_address(wallet_pk, &pool.lp_mint);
// You may need to calculate minimum amounts based on slippage
let instruction = Withdraw {
lp_token_amount: lp_amount,
minimum_token_0_amount: 0, // Adjust as needed
minimum_token_1_amount: 0, // Adjust as needed
};
let (authority, __bump) =
Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &raydium_cp_swap::id());
let accounts = raydium_cp_swap::accounts::Withdraw {
owner: *wallet_pk,
authority,
pool_state: id.pool,
owner_lp_token: user_lp_token_account,
token_0_account: user_token_a_account,
token_1_account: user_token_b_account,
token_0_vault: vault_a,
token_1_vault: vault_b,
token_program: spl_token_id(),
token_program_2022: spl_token_2022_id(),
vault_0_mint: token_a_mint,
vault_1_mint: token_b_mint,
memo_program: spl_memo_id(),
lp_mint: pool.lp_mint,
};
let withdraw_instruction = Instruction {
program_id: raydium_cp_swap::id(),
accounts: accounts.to_account_metas(None),
data: instruction.data(),
};
let result = SwapInstruction {
instruction: withdraw_instruction,
out_pubkey: user_token_a_account, // Adjust as needed
out_mint: token_a_mint,
in_amount_offset: 8,
cu_estimate: Some(40_000),
};