Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add token programs to server #328

Merged
merged 7 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions auction-server/api-types/src/opportunity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,18 @@ pub enum OpportunityCreateProgramParamsV1Svm {
/// The user wallet address which requested the quote from the wallet.
#[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)]
#[serde_as(as = "DisplayFromStr")]
user_wallet_address: Pubkey,
user_wallet_address: Pubkey,
/// The referral fee in basis points.
#[schema(example = 10, value_type = u16)]
referral_fee_bps: u16,
referral_fee_bps: u16,
/// The token program of the input mint.
#[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)]
#[serde_as(as = "DisplayFromStr")]
input_token_program: Pubkey,
anihamde marked this conversation as resolved.
Show resolved Hide resolved
/// The token program of the output mint.
#[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)]
#[serde_as(as = "DisplayFromStr")]
output_token_program: Pubkey,
},
}

Expand Down Expand Up @@ -266,6 +274,7 @@ pub enum OpportunityCreateSvm {
/// The input type for creating a new opportunity.
#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum OpportunityCreate {
#[schema(title = "evm")]
Evm(OpportunityCreateEvm),
Expand Down Expand Up @@ -345,12 +354,16 @@ pub enum OpportunityParamsV1ProgramSvm {
#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, ToResponse)]
pub enum QuoteTokens {
InputTokenSpecified {
input_token: TokenAmountSvm,
output_token: Pubkey,
input_token: TokenAmountSvm,
output_token: Pubkey,
input_token_program: Pubkey,
output_token_program: Pubkey,
},
OutputTokenSpecified {
input_token: Pubkey,
output_token: TokenAmountSvm,
input_token: Pubkey,
output_token: TokenAmountSvm,
input_token_program: Pubkey,
output_token_program: Pubkey,
},
}

Expand Down
3 changes: 3 additions & 0 deletions auction-server/config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ chains:
rpc_read_url: http://localhost:8899
rpc_tx_submission_url: http://localhost:8899
ws_addr: ws://localhost:8900
accepted_token_programs:
- TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
- TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
3 changes: 3 additions & 0 deletions auction-server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,7 @@ pub struct ConfigSvm {
/// This should be None unless the RPC `getRecentPrioritizationFees`'s supports the percentile parameter, for example Triton RPC.
/// It is an integer between 0 and 10000 with 10000 representing 100%.
pub prioritization_fee_percentile: Option<u64>,
/// List of accepted token programs for the swap instruction.
#[serde_as(as = "Vec<DisplayFromStr>")]
pub accepted_token_programs: Vec<Pubkey>,
}
41 changes: 28 additions & 13 deletions auction-server/src/opportunity/entities/opportunity_svm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ pub enum FeeToken {

#[derive(Debug, Clone, PartialEq)]
pub struct OpportunitySvmProgramSwap {
pub user_wallet_address: Pubkey,
pub fee_token: FeeToken,
pub referral_fee_bps: u16,
pub user_wallet_address: Pubkey,
pub fee_token: FeeToken,
pub referral_fee_bps: u16,
// TODO*: these really should not live here. they should live in the opportunity core fields, but we don't want to introduce a breaking change. in any case, the need for the token programs is another sign that quotes should be separated from the traditional opportunity struct.
pub input_token_program: Pubkey,
pub output_token_program: Pubkey,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -108,9 +111,11 @@ impl Opportunity for OpportunitySvm {
OpportunitySvmProgram::SwapKamino(program) => {
repository::OpportunityMetadataSvmProgram::SwapKamino(
repository::OpportunityMetadataSvmProgramSwap {
user_wallet_address: program.user_wallet_address,
fee_token: program.fee_token,
referral_fee_bps: program.referral_fee_bps,
user_wallet_address: program.user_wallet_address,
fee_token: program.fee_token,
referral_fee_bps: program.referral_fee_bps,
input_token_program: program.input_token_program,
output_token_program: program.output_token_program,
},
)
}
Expand Down Expand Up @@ -200,16 +205,20 @@ impl From<OpportunitySvm> for api::OpportunitySvm {
.expect("Failed to get sell token from opportunity svm");
let tokens = if buy_token.amount == 0 {
api::QuoteTokens::OutputTokenSpecified {
input_token: buy_token.token,
output_token: sell_token.clone().into(),
input_token: buy_token.token,
output_token: sell_token.clone().into(),
input_token_program: program.input_token_program,
output_token_program: program.output_token_program,
}
} else {
if sell_token.amount != 0 {
tracing::error!(opportunity = ?val, "Both token amounts are specified for swap opportunity");
}
api::QuoteTokens::InputTokenSpecified {
input_token: buy_token.clone().into(),
output_token: sell_token.token,
input_token: buy_token.clone().into(),
output_token: sell_token.token,
input_token_program: program.input_token_program,
output_token_program: program.output_token_program,
}
};
api::OpportunityParamsV1ProgramSvm::Swap {
Expand Down Expand Up @@ -264,9 +273,11 @@ impl TryFrom<repository::Opportunity<repository::OpportunityMetadataSvm>> for Op
}
repository::OpportunityMetadataSvmProgram::SwapKamino(program) => {
OpportunitySvmProgram::SwapKamino(OpportunitySvmProgramSwap {
user_wallet_address: program.user_wallet_address,
fee_token: program.fee_token,
referral_fee_bps: program.referral_fee_bps,
user_wallet_address: program.user_wallet_address,
fee_token: program.fee_token,
referral_fee_bps: program.referral_fee_bps,
input_token_program: program.input_token_program,
output_token_program: program.output_token_program,
})
}
};
Expand Down Expand Up @@ -303,11 +314,15 @@ impl From<api::OpportunityCreateSvm> for OpportunityCreateSvm {
api::OpportunityCreateProgramParamsV1Svm::Swap {
user_wallet_address,
referral_fee_bps,
input_token_program,
output_token_program,
} => OpportunitySvmProgram::SwapKamino(OpportunitySvmProgramSwap {
user_wallet_address,
// TODO*: see comment above about this arm
fee_token: FeeToken::InputToken,
referral_fee_bps,
input_token_program,
output_token_program,
}),
};

Expand Down
23 changes: 23 additions & 0 deletions auction-server/src/opportunity/repository/get_token_program.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use {
super::{
InMemoryStoreSvm,
Repository,
},
solana_sdk::pubkey::Pubkey,
};

impl Repository<InMemoryStoreSvm> {
pub async fn query_token_program_cache(&self, mint: Pubkey) -> Option<Pubkey> {
let cache_read = self.in_memory_store.token_program_cache.read().await;
let token_program_query = cache_read.get(&mint);
token_program_query.cloned()
}

pub async fn cache_token_program(&self, mint: Pubkey, token_program: Pubkey) {
self.in_memory_store
.token_program_cache
.write()
.await
.insert(mint, token_program);
}
}
8 changes: 6 additions & 2 deletions auction-server/src/opportunity/repository/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
super::entities,
ethers::types::Address,
solana_sdk::pubkey::Pubkey,
std::{
collections::HashMap,
ops::Deref,
Expand All @@ -15,6 +16,7 @@ mod get_in_memory_opportunities_by_key;
mod get_in_memory_opportunity_by_id;
mod get_opportunities;
mod get_spoof_info;
mod get_token_program;
mod models;
mod refresh_in_memory_opportunity;
mod remove_opportunities;
Expand Down Expand Up @@ -53,7 +55,8 @@ pub struct InMemoryStoreEvm {
pub spoof_info: RwLock<HashMap<Address, entities::SpoofState>>,
}
pub struct InMemoryStoreSvm {
pub core_fields: InMemoryStoreCoreFields<entities::OpportunitySvm>,
pub core_fields: InMemoryStoreCoreFields<entities::OpportunitySvm>,
pub token_program_cache: RwLock<HashMap<Pubkey, Pubkey>>,
}

impl InMemoryStore for InMemoryStoreEvm {
Expand All @@ -72,7 +75,8 @@ impl InMemoryStore for InMemoryStoreSvm {

fn new() -> Self {
Self {
core_fields: InMemoryStoreCoreFields::new(),
core_fields: InMemoryStoreCoreFields::new(),
token_program_cache: RwLock::new(HashMap::new()),
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions auction-server/src/opportunity/repository/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@ pub struct OpportunityMetadataSvmProgramLimo {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct OpportunityMetadataSvmProgramSwap {
#[serde_as(as = "DisplayFromStr")]
pub user_wallet_address: Pubkey,
pub fee_token: FeeToken,
pub referral_fee_bps: u16,
pub user_wallet_address: Pubkey,
pub fee_token: FeeToken,
pub referral_fee_bps: u16,
#[serde_as(as = "DisplayFromStr")]
pub input_token_program: Pubkey,
#[serde_as(as = "DisplayFromStr")]
pub output_token_program: Pubkey,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
Expand Down
30 changes: 27 additions & 3 deletions auction-server/src/opportunity/service/get_quote.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use {
super::{
get_token_program::GetTokenProgramInput,
ChainTypeSvm,
Service,
},
Expand Down Expand Up @@ -126,7 +127,7 @@ impl Service<ChainTypeSvm> {
router,
permission_account,
),
chain_id: quote_create.chain_id,
chain_id: quote_create.chain_id.clone(),
sell_tokens: vec![entities::TokenAmountSvm {
token: input_mint,
amount: input_amount,
Expand All @@ -137,13 +138,36 @@ impl Service<ChainTypeSvm> {
}],
};

let input_token_program = self
.get_token_program(GetTokenProgramInput {
chain_id: quote_create.chain_id.clone(),
mint: input_mint,
})
.await
.map_err(|err| {
tracing::error!("Failed to get input token program: {:?}", err);
RestError::BadParameters("Input token program not found".to_string())
})?;
let output_token_program = self
.get_token_program(GetTokenProgramInput {
chain_id: quote_create.chain_id.clone(),
mint: output_mint,
})
.await
.map_err(|err| {
tracing::error!("Failed to get output token program: {:?}", err);
RestError::BadParameters("Output token program not found".to_string())
})?;

let program_opportunity = match program {
ProgramSvm::SwapKamino => {
entities::OpportunitySvmProgram::SwapKamino(entities::OpportunitySvmProgramSwap {
user_wallet_address: quote_create.user_wallet_address,
// TODO*: we should determine this more intelligently
fee_token: entities::FeeToken::InputToken,
referral_fee_bps: quote_create.referral_fee_bps,
fee_token: entities::FeeToken::InputToken,
referral_fee_bps: quote_create.referral_fee_bps,
input_token_program,
output_token_program,
})
}
_ => {
Expand Down
66 changes: 66 additions & 0 deletions auction-server/src/opportunity/service/get_token_program.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use {
super::{
ChainTypeSvm,
Service,
},
crate::{
api::RestError,
kernel::entities::ChainId,
},
solana_sdk::pubkey::Pubkey,
};

pub struct GetTokenProgramInput {
pub chain_id: ChainId,
pub mint: Pubkey,
}

impl Service<ChainTypeSvm> {
/// Find the token program for a given mint.
/// Pulls from the cache if already present, otherwise queries the RPC and saves in the cache.
pub async fn get_token_program(
&self,
input: GetTokenProgramInput,
) -> Result<Pubkey, RestError> {
let config = self.get_config(&input.chain_id)?;
let token_program_query = self.repo.query_token_program_cache(input.mint).await;
let token_program = match token_program_query {
Some(program) => program,
None => {
let token_program_address = config
.rpc_client
.get_account(&input.mint)
.await
.map_err(|err| {
tracing::error!(
"Failed to retrieve owner program for mint account {mint}: {:?}",
err,
mint = input.mint
);
RestError::BadParameters(format!(
"Failed to retrieve owner program for mint account {}: {:?}",
input.mint, err
))
})?
.owner;
self.repo
.cache_token_program(input.mint, token_program_address)
.await;
token_program_address
}
};

if !config.accepted_token_programs.contains(&token_program) {
tracing::error!(
"Token program {program} for mint account {mint} is not an approved token program",
program = token_program,
mint = input.mint
);
return Err(RestError::BadParameters(format!(
"Provided mint belongs to unapproved token program {}",
token_program
)));
}
Ok(token_program)
}
}
Loading
Loading