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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
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
21 changes: 17 additions & 4 deletions auction-server/src/opportunity/service/get_quote.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
super::{
ChainTypeSvm,
ConfigSvm,
Service,
},
crate::{
Expand Down Expand Up @@ -99,6 +100,7 @@ impl Service<ChainTypeSvm> {
&self,
quote_create: entities::QuoteCreate,
program: &ProgramSvm,
config: &ConfigSvm,
) -> Result<entities::OpportunityCreateSvm, RestError> {
let router = quote_create.router;
let permission_account = get_quote_permission_key(
Expand Down Expand Up @@ -126,7 +128,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 +139,24 @@ impl Service<ChainTypeSvm> {
}],
};

let input_token_program = config.get_token_program(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 = config.get_token_program(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 Expand Up @@ -187,7 +200,7 @@ impl Service<ChainTypeSvm> {
tracing::info!(quote_create = ?input.quote_create, "Received request to get quote");

let opportunity_create = self
.get_opportunity_create_for_quote(input.quote_create.clone(), &input.program)
.get_opportunity_create_for_quote(input.quote_create.clone(), &input.program, config)
.await?;
let opportunity = self
.add_opportunity(AddOpportunityInput {
Expand Down
79 changes: 72 additions & 7 deletions auction-server/src/opportunity/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,28 @@ use {
Svm,
},
traced_client::TracedClient,
traced_sender_svm::TracedSenderSvm,
},
state::{
ChainStoreEvm,
ChainStoreSvm,
Store,
},
},
anyhow::anyhow,
ethers::{
providers::Provider,
types::Address,
},
futures::future::try_join_all,
solana_client::{
nonblocking::rpc_client::RpcClient,
rpc_client::RpcClientConfig,
},
solana_sdk::{
commitment_config::CommitmentConfig,
pubkey::Pubkey,
},
std::{
collections::HashMap,
sync::Arc,
Expand Down Expand Up @@ -67,8 +77,8 @@ pub struct ConfigEvm {
impl ConfigEvm {
// TODO Move these to config trait?
pub async fn inject_auction_service(&self, service: auction_service::Service<Evm>) {
let mut write_gaurd = self.auction_service.write().await;
*write_gaurd = Some(service);
let mut write_guard = self.auction_service.write().await;
*write_guard = Some(service);
}
pub async fn get_auction_service(&self) -> auction_service::Service<Evm> {
self.auction_service
Expand All @@ -81,14 +91,17 @@ impl ConfigEvm {

// NOTE: Do not implement debug here. it has a circular reference to auction_service
pub struct ConfigSvm {
pub auction_service: RwLock<Option<auction_service::Service<Svm>>>,
pub auction_service: RwLock<Option<auction_service::Service<Svm>>>,
pub rpc_client: RpcClient,
pub token_program_cache: RwLock<HashMap<Pubkey, Pubkey>>,
pub accepted_token_programs: Vec<Pubkey>,
}

impl ConfigSvm {
// TODO Move these to config trait?
pub async fn inject_auction_service(&self, service: auction_service::Service<Svm>) {
let mut write_gaurd = self.auction_service.write().await;
*write_gaurd = Some(service);
let mut write_guard = self.auction_service.write().await;
*write_guard = Some(service);
}
pub async fn get_auction_service(&self) -> auction_service::Service<Svm> {
self.auction_service
Expand Down Expand Up @@ -194,16 +207,68 @@ impl ConfigSvm {
) -> anyhow::Result<HashMap<ChainId, Self>> {
Ok(chains
.iter()
.map(|(chain_id, _)| {
.map(|(chain_id, chain_store)| {
(
chain_id.clone(),
Self {
auction_service: RwLock::new(None),
auction_service: RwLock::new(None),
rpc_client: TracedSenderSvm::new_client(
chain_id.clone(),
chain_store.config.rpc_read_url.as_str(),
chain_store.config.rpc_timeout,
RpcClientConfig::with_commitment(CommitmentConfig::processed()),
),
token_program_cache: RwLock::new(HashMap::new()),
accepted_token_programs: chain_store.config.accepted_token_programs.clone(),
},
)
})
.collect())
}

pub async fn get_token_program(&self, mint: Pubkey) -> anyhow::Result<Pubkey> {
if let Some(program) = self.token_program_cache.read().await.get(&mint) {
let token_program = *program;
if !self.accepted_token_programs.contains(&token_program) {
return Err(anyhow!(
"Token program {program} for mint account {mint} is not an approved token program",
program = token_program,
mint = mint
));
}
return Ok(token_program);
}

let token_program = self
.rpc_client
.get_account(&mint)
.await
.map_err(|err| {
tracing::error!(
"Failed to retrieve owner program for mint account {mint}: {:?}",
err,
mint = mint
);
anyhow!(
"Failed to retrieve owner program for mint account {mint}: {:?}",
err,
mint = mint
)
})?
.owner;
self.token_program_cache
.write()
.await
.insert(mint, token_program);
if !self.accepted_token_programs.contains(&token_program) {
return Err(anyhow!(
"Token program {program} for mint account {mint} is not an approved token program",
program = token_program,
mint = mint
));
}
Ok(token_program)
}
}

pub trait ChainType: Send + Sync {
Expand Down
3 changes: 3 additions & 0 deletions integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def main():
rpc_read_url: http://localhost:8899
rpc_tx_submission_url: http://localhost:8899
ws_addr: ws://localhost:8900
accepted_token_programs:
- TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
- TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
'''
with open('auction-server/config.yaml', 'w') as f:
f.write(template)
Expand Down
Loading