From 76d65c2b289f8c26770a0fc033dff8a22c4bca85 Mon Sep 17 00:00:00 2001 From: Anirudh Suresh Date: Fri, 10 Jan 2025 09:11:08 +0000 Subject: [PATCH] Fix/improve server swap (#315) * remove unnecessary TODO * get rid of wallet_program_router_account * parse deadline * add fee token specification in Opportunity * add referral fees to quot e api & opportunity * change location of TODO * rename bid payment type * nit improve todo * feat: create quote response submit method, typescript (#312) * create quote response submit method, typescript * ignore generated files in linting * bump package version * convert to date * add todo * make referral_fee_bps optional * fix the optional/default behavior in server + client * address comments 1 * add a todo around distinguishing enum variants --- Cargo.lock | 4 +- auction-server/Cargo.toml | 2 +- auction-server/api-types/Cargo.toml | 2 +- auction-server/api-types/src/bid.rs | 2 +- auction-server/api-types/src/lib.rs | 2 +- auction-server/api-types/src/opportunity.rs | 12 +++- auction-server/config.sample.yaml | 1 - auction-server/src/auction/entities/bid.rs | 42 ++++++++++--- .../src/auction/repository/models.rs | 19 ++++-- .../src/auction/service/auction_manager.rs | 49 +++++++-------- auction-server/src/auction/service/mod.rs | 1 - .../src/auction/service/verification.rs | 63 +++++++++---------- auction-server/src/config.rs | 3 - auction-server/src/kernel/entities.rs | 2 +- .../opportunity/entities/opportunity_svm.rs | 20 ++++++ .../src/opportunity/entities/quote.rs | 2 + .../src/opportunity/repository/models.rs | 7 ++- .../src/opportunity/service/get_quote.rs | 32 +++++----- auction-server/src/opportunity/service/mod.rs | 9 +-- auction-server/src/server.rs | 3 - auction-server/src/state.rs | 9 +-- .../svm/programs/express_relay/src/error.rs | 2 + .../svm/programs/express_relay/src/lib.rs | 2 +- .../svm/programs/express_relay/src/swap.rs | 8 ++- integration.py | 1 - sdk/js/package.json | 2 +- sdk/js/src/index.ts | 1 + sdk/js/src/serverTypes.d.ts | 20 +++++- sdk/js/src/types.ts | 5 ++ 29 files changed, 205 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4af9000b..9a2dc466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,7 +719,7 @@ dependencies = [ [[package]] name = "auction-server" -version = "0.15.0" +version = "0.15.1" dependencies = [ "anchor-lang", "anchor-lang-idl", @@ -2659,7 +2659,7 @@ dependencies = [ [[package]] name = "express-relay-api-types" -version = "0.2.0" +version = "0.2.1" dependencies = [ "base64 0.22.1", "bincode", diff --git a/auction-server/Cargo.toml b/auction-server/Cargo.toml index d8df8fc7..1fff9e21 100644 --- a/auction-server/Cargo.toml +++ b/auction-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "auction-server" -version = "0.15.0" +version = "0.15.1" edition = "2021" license-file = "license.txt" diff --git a/auction-server/api-types/Cargo.toml b/auction-server/api-types/Cargo.toml index e54033c4..9cd637cf 100644 --- a/auction-server/api-types/Cargo.toml +++ b/auction-server/api-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "express-relay-api-types" -version = "0.2.0" +version = "0.2.1" edition = "2021" description = "Pyth Express Relay api types" repository = "https://github.com/pyth-network/per" diff --git a/auction-server/api-types/src/bid.rs b/auction-server/api-types/src/bid.rs index f0b85d9a..82163653 100644 --- a/auction-server/api-types/src/bid.rs +++ b/auction-server/api-types/src/bid.rs @@ -175,7 +175,7 @@ pub struct BidSvm { #[schema(example = "1000", value_type = u64)] pub bid_amount: BidAmountSvm, /// The permission key for bid in base64 format. - /// This is the concatenation of the permission account and the router account. + /// This is the concatenation of the opportunity type, the router, and the permission account. #[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)] pub permission_key: PermissionKeySvm, } diff --git a/auction-server/api-types/src/lib.rs b/auction-server/api-types/src/lib.rs index d29c1c46..4722a8bb 100644 --- a/auction-server/api-types/src/lib.rs +++ b/auction-server/api-types/src/lib.rs @@ -33,7 +33,7 @@ pub type MicroLamports = u64; pub type ChainId = String; pub type PermissionKeyEvm = Bytes; #[derive(Clone, Debug)] -pub struct PermissionKeySvm(pub [u8; 64]); +pub struct PermissionKeySvm(pub [u8; 65]); impl Serialize for PermissionKeySvm { fn serialize(&self, serializer: S) -> Result where diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index 30589a39..4f1a90d5 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -217,11 +217,13 @@ pub enum OpportunityCreateProgramParamsV1Svm { #[serde(rename = "swap")] #[schema(title = "swap")] Swap { - // TODO*: we should make this more generic, a la `Swap` /// 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, + /// The referral fee in basis points. + #[schema(example = 10, value_type = u16)] + referral_fee_bps: u16, }, } @@ -500,11 +502,19 @@ pub struct QuoteCreateV1SvmParams { #[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)] #[serde_as(as = "DisplayFromStr")] pub router: Pubkey, + /// The referral fee in basis points. If not provided, the referral fee will default to 0. + #[serde(default = "default_referral_fee_bps")] + #[schema(example = 10, value_type = u16)] + pub referral_fee_bps: u16, /// The chain id for creating the quote. #[schema(example = "solana", value_type = String)] pub chain_id: ChainId, } +fn default_referral_fee_bps() -> u16 { + 0 +} + #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] #[serde(tag = "side")] pub enum SpecifiedTokenAmount { diff --git a/auction-server/config.sample.yaml b/auction-server/config.sample.yaml index f6e502ad..5d4a5051 100644 --- a/auction-server/config.sample.yaml +++ b/auction-server/config.sample.yaml @@ -15,4 +15,3 @@ chains: rpc_read_url: http://localhost:8899 rpc_tx_submission_url: http://localhost:8899 ws_addr: ws://localhost:8900 - wallet_program_router_account: 3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn diff --git a/auction-server/src/auction/entities/bid.rs b/auction-server/src/auction/entities/bid.rs index 0f6d3f8b..f0e88b46 100644 --- a/auction-server/src/auction/entities/bid.rs +++ b/auction-server/src/auction/entities/bid.rs @@ -38,6 +38,7 @@ use { }, hash::Hash, }, + strum::FromRepr, time::OffsetDateTime, uuid::Uuid, }; @@ -180,9 +181,10 @@ pub trait BidChainData: Send + Sync + Clone + Debug + PartialEq { #[derive(Clone, Debug, PartialEq)] pub struct BidChainDataSvm { - pub transaction: VersionedTransaction, - pub router: Pubkey, - pub permission_account: Pubkey, + pub transaction: VersionedTransaction, + pub bid_payment_instruction_type: BidPaymentInstructionType, + pub router: Pubkey, + pub permission_account: Pubkey, } #[derive(Clone, Debug, PartialEq)] @@ -197,9 +199,10 @@ impl BidChainData for BidChainDataSvm { type PermissionKey = PermissionKeySvm; fn get_permission_key(&self) -> Self::PermissionKey { - let mut permission_key = [0; 64]; - permission_key[..32].copy_from_slice(&self.router.to_bytes()); - permission_key[32..].copy_from_slice(&self.permission_account.to_bytes()); + let mut permission_key = [0; 65]; + permission_key[0] = self.bid_payment_instruction_type.clone().into(); + permission_key[1..33].copy_from_slice(&self.router.to_bytes()); + permission_key[33..].copy_from_slice(&self.permission_account.to_bytes()); PermissionKeySvm(permission_key) } } @@ -212,16 +215,37 @@ impl BidChainData for BidChainDataEvm { } } +#[derive(Clone, Debug, PartialEq, FromRepr)] +pub enum BidPaymentInstructionType { + SubmitBid, + Swap, +} + +impl From for u8 { + fn from(instruction: BidPaymentInstructionType) -> Self { + match instruction { + BidPaymentInstructionType::SubmitBid => 0, + BidPaymentInstructionType::Swap => 1, + } + } +} + impl BidChainDataSvm { + pub fn get_bid_payment_instruction_type( + permission_key: &PermissionKeySvm, + ) -> Option { + BidPaymentInstructionType::from_repr(permission_key.0[0].into()) + } + pub fn get_router(permission_key: &PermissionKeySvm) -> Pubkey { - let slice: [u8; 32] = permission_key.0[..32] + let slice: [u8; 32] = permission_key.0[1..33] .try_into() - .expect("Failed to extract first 32 bytes from permission key"); + .expect("Failed to extract bytes 1 through 33 from permission key"); Pubkey::new_from_array(slice) } pub fn get_permission_account(permission_key: &PermissionKeySvm) -> Pubkey { - let slice: [u8; 32] = permission_key.0[32..] + let slice: [u8; 32] = permission_key.0[33..] .try_into() .expect("Failed to extract last 32 bytes from permission key"); Pubkey::new_from_array(slice) diff --git a/auction-server/src/auction/repository/models.rs b/auction-server/src/auction/repository/models.rs index f5d6233a..c52a3a68 100644 --- a/auction-server/src/auction/repository/models.rs +++ b/auction-server/src/auction/repository/models.rs @@ -404,15 +404,26 @@ impl ModelTrait for Svm { } fn get_chain_data_entity(bid: &Bid) -> anyhow::Result { - let slice: [u8; 64] = + let slice: [u8; 65] = bid.permission_key.clone().try_into().map_err(|e| { anyhow::anyhow!("Failed to convert permission key to slice {:?}", e) })?; let permission_key: PermissionKeySvm = PermissionKeySvm(slice); Ok(entities::BidChainDataSvm { - transaction: bid.metadata.transaction.clone(), - router: entities::BidChainDataSvm::get_router(&permission_key), - permission_account: entities::BidChainDataSvm::get_permission_account(&permission_key), + transaction: bid.metadata.transaction.clone(), + bid_payment_instruction_type: + match entities::BidChainDataSvm::get_bid_payment_instruction_type(&permission_key) { + Some(bid_payment_instruction_type) => bid_payment_instruction_type, + None => { + return Err(anyhow::anyhow!( + "Failed to get bid payment instruction type from permission key, due to invalid data" + )) + } + }, + router: entities::BidChainDataSvm::get_router(&permission_key), + permission_account: entities::BidChainDataSvm::get_permission_account( + &permission_key, + ), }) } diff --git a/auction-server/src/auction/service/auction_manager.rs b/auction-server/src/auction/service/auction_manager.rs index 0bd30d91..75fb6f14 100644 --- a/auction-server/src/auction/service/auction_manager.rs +++ b/auction-server/src/auction/service/auction_manager.rs @@ -4,9 +4,10 @@ use { Service, }, crate::{ - auction::{ - entities, - entities::BidStatusAuction, + auction::entities::{ + self, + BidPaymentInstructionType, + BidStatusAuction, }, kernel::{ contracts::MulticallIssuedFilter, @@ -542,30 +543,26 @@ impl AuctionManager for Service { &self, permission_key: &entities::PermissionKey, ) -> entities::SubmitType { - if permission_key.0.starts_with( - &self - .config - .chain_config - .wallet_program_router_account - .to_bytes(), - ) { - if self - .opportunity_service - .get_live_opportunities(GetLiveOpportunitiesInput { - key: opportunity::entities::OpportunityKey( - self.config.chain_id.clone(), - Bytes::from(permission_key.0), - ), - }) - .await - .is_empty() - { - entities::SubmitType::Invalid - } else { - entities::SubmitType::ByOther + match entities::BidChainDataSvm::get_bid_payment_instruction_type(permission_key) { + Some(BidPaymentInstructionType::Swap) => { + if self + .opportunity_service + .get_live_opportunities(GetLiveOpportunitiesInput { + key: opportunity::entities::OpportunityKey( + self.config.chain_id.clone(), + Bytes::from(permission_key.0), + ), + }) + .await + .is_empty() + { + entities::SubmitType::Invalid + } else { + entities::SubmitType::ByOther + } } - } else { - entities::SubmitType::ByServer + Some(BidPaymentInstructionType::SubmitBid) => entities::SubmitType::ByServer, + None => entities::SubmitType::Invalid, // TODO: may want to distinguish this arm from the prior Invalid SubmitType. Maybe two different enum variants? } } diff --git a/auction-server/src/auction/service/mod.rs b/auction-server/src/auction/service/mod.rs index 3ee83c67..95990c50 100644 --- a/auction-server/src/auction/service/mod.rs +++ b/auction-server/src/auction/service/mod.rs @@ -97,7 +97,6 @@ pub struct ExpressRelaySvm { pub struct ConfigSvm { pub client: RpcClient, - pub wallet_program_router_account: Pubkey, pub express_relay: ExpressRelaySvm, pub simulator: Simulator, pub ws_address: String, diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index fe86ef2a..bdd24efe 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -10,6 +10,7 @@ use { entities::{ self, BidChainData, + BidPaymentInstructionType, SubmitType, }, service::get_live_bids::GetLiveBidsInput, @@ -282,11 +283,6 @@ struct BidDataSvm { const BID_MINIMUM_LIFE_TIME_SVM_SERVER: Duration = Duration::from_secs(5); const BID_MINIMUM_LIFE_TIME_SVM_OTHER: Duration = Duration::from_secs(10); -pub enum BidPaymentInstruction { - SubmitBid, - Swap, -} - impl Service { //TODO: merge this logic with simulator logic async fn query_lookup_table(&self, table: &Pubkey, index: usize) -> Result { @@ -424,13 +420,13 @@ impl Service { pub fn extract_express_relay_bid_instruction( &self, transaction: VersionedTransaction, - instruction_type: BidPaymentInstruction, + instruction_type: BidPaymentInstructionType, ) -> Result { let discriminator = match instruction_type { - BidPaymentInstruction::SubmitBid => { + BidPaymentInstructionType::SubmitBid => { express_relay_svm::instruction::SubmitBid::DISCRIMINATOR } - BidPaymentInstruction::Swap => express_relay_svm::instruction::Swap::DISCRIMINATOR, + BidPaymentInstructionType::Swap => express_relay_svm::instruction::Swap::DISCRIMINATOR, }; let instructions = transaction .message @@ -489,11 +485,11 @@ impl Service { ) -> Result { let submit_bid_instruction_result = self.extract_express_relay_bid_instruction( transaction.clone(), - BidPaymentInstruction::SubmitBid, + BidPaymentInstructionType::SubmitBid, ); let swap_instruction_result = self.extract_express_relay_bid_instruction( transaction.clone(), - BidPaymentInstruction::Swap, + BidPaymentInstructionType::Swap, ); match ( @@ -523,12 +519,6 @@ impl Service { .router_account_position_submit_bid, ) .await?; - if router == self.config.chain_config.wallet_program_router_account { - return Err(RestError::BadParameters( - "Using swap router account is not allowed for submit_bid instruction" - .to_string(), - )); - } Ok(BidDataSvm { amount: submit_bid_data.bid_amount, permission_account, @@ -556,11 +546,6 @@ impl Service { .router_account_position_swap, ) .await?; - if router != self.config.chain_config.wallet_program_router_account { - return Err(RestError::BadParameters( - "Must use approved router for swap instruction".to_string(), - )); - } let user_wallet = self .extract_account( @@ -617,22 +602,21 @@ impl Service { ), }; - let permission_account = get_quote_permission_key(&tokens, &user_wallet); + let permission_account = + get_quote_permission_key(&tokens, &user_wallet, swap_data.referral_fee_bps); Ok(BidDataSvm { amount: bid_amount, permission_account, router, - // TODO*: to fix once deadline param added to swap instruction--just set this way to make sure compiles - deadline: OffsetDateTime::now_utc(), - // deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err( - // |e| { - // RestError::BadParameters(format!( - // "Invalid deadline: {:?} {:?}", - // swap_data.deadline, e - // )) - // }, - // )?, + deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err( + |e| { + RestError::BadParameters(format!( + "Invalid deadline: {:?} {:?}", + swap_data.deadline, e + )) + }, + )?, submit_type: SubmitType::ByOther, }) } @@ -841,10 +825,21 @@ impl Verification for Service { let bid_data = self .extract_bid_data(bid.chain_data.transaction.clone()) .await?; + let bid_payment_instruction_type = match bid_data.submit_type { + SubmitType::ByServer => BidPaymentInstructionType::SubmitBid, + // TODO*: we should verify all components of the swap here (token amounts, fee token side, referral fee) + SubmitType::ByOther => BidPaymentInstructionType::Swap, + SubmitType::Invalid => { + return Err(RestError::BadParameters( + "Invalid submit type for bid".to_string(), + )); + } + }; let bid_chain_data = entities::BidChainDataSvm { permission_account: bid_data.permission_account, - router: bid_data.router, - transaction: bid.chain_data.transaction.clone(), + router: bid_data.router, + bid_payment_instruction_type, + transaction: bid.chain_data.transaction.clone(), }; let permission_key = bid_chain_data.get_permission_key(); tracing::Span::current().record("permission_key", bid_data.permission_account.to_string()); diff --git a/auction-server/src/config.rs b/auction-server/src/config.rs index 4c322538..1f8818a4 100644 --- a/auction-server/src/config.rs +++ b/auction-server/src/config.rs @@ -164,9 +164,6 @@ pub struct ConfigSvm { /// Timeout for RPC requests in seconds. #[serde(default = "default_rpc_timeout_svm")] pub rpc_timeout: u64, - /// The router account for swap program. // TODO*: we should work to remove this and fully identify swaps by ix type instead of router account - #[serde_as(as = "DisplayFromStr")] - pub wallet_program_router_account: Pubkey, #[serde(default)] /// Percentile of prioritization fees to query from the `rpc_read_url`. /// This should be None unless the RPC `getRecentPrioritizationFees`'s supports the percentile parameter, for example Triton RPC. diff --git a/auction-server/src/kernel/entities.rs b/auction-server/src/kernel/entities.rs index c9b37478..6b856a10 100644 --- a/auction-server/src/kernel/entities.rs +++ b/auction-server/src/kernel/entities.rs @@ -14,7 +14,7 @@ pub type ChainId = String; pub type PermissionKey = Bytes; #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct PermissionKeySvm(pub [u8; 64]); +pub struct PermissionKeySvm(pub [u8; 65]); impl Display for PermissionKeySvm { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index cef6888e..5fc343ee 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -14,6 +14,10 @@ use { opportunity::repository, }, express_relay_api_types::opportunity as api, + serde::{ + Deserialize, + Serialize, + }, solana_sdk::{ clock::Slot, pubkey::Pubkey, @@ -31,9 +35,17 @@ pub struct OpportunitySvmProgramLimo { pub order_address: Pubkey, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum FeeToken { + InputToken, + OutputToken, +} + #[derive(Debug, Clone, PartialEq)] pub struct OpportunitySvmProgramSwap { pub user_wallet_address: Pubkey, + pub fee_token: FeeToken, + pub referral_fee_bps: u16, } #[derive(Debug, Clone, PartialEq)] @@ -96,6 +108,8 @@ impl Opportunity for OpportunitySvm { repository::OpportunityMetadataSvmProgram::SwapKamino( repository::OpportunityMetadataSvmProgramSwap { user_wallet_address: program.user_wallet_address, + fee_token: program.fee_token, + referral_fee_bps: program.referral_fee_bps, }, ) } @@ -250,6 +264,8 @@ impl TryFrom> 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, }) } }; @@ -285,8 +301,12 @@ impl From for OpportunityCreateSvm { // TODO*: this arm doesn't matter bc this conversion is only called in `post_opportunity` in api.rs. but we should handle this better api::OpportunityCreateProgramParamsV1Svm::Swap { user_wallet_address, + referral_fee_bps, } => OpportunitySvmProgram::SwapKamino(OpportunitySvmProgramSwap { user_wallet_address, + // TODO*: see comment above about this arm + fee_token: FeeToken::InputToken, + referral_fee_bps, }), }; diff --git a/auction-server/src/opportunity/entities/quote.rs b/auction-server/src/opportunity/entities/quote.rs index d11df471..b3286e3d 100644 --- a/auction-server/src/opportunity/entities/quote.rs +++ b/auction-server/src/opportunity/entities/quote.rs @@ -21,6 +21,7 @@ pub struct Quote { #[derive(Debug, Clone, PartialEq)] pub struct QuoteCreate { pub user_wallet_address: Pubkey, + pub referral_fee_bps: u16, pub tokens: QuoteTokens, pub router: Pubkey, pub chain_id: ChainId, @@ -63,6 +64,7 @@ impl From for QuoteCreate { Self { user_wallet_address: params.user_wallet_address, + referral_fee_bps: params.referral_fee_bps, tokens, router: params.router, chain_id: params.chain_id, diff --git a/auction-server/src/opportunity/repository/models.rs b/auction-server/src/opportunity/repository/models.rs index 3270d642..16d5c4f7 100644 --- a/auction-server/src/opportunity/repository/models.rs +++ b/auction-server/src/opportunity/repository/models.rs @@ -1,5 +1,8 @@ use { - crate::models::ChainType, + crate::{ + models::ChainType, + opportunity::entities::FeeToken, + }, ethers::types::{ Address, Bytes, @@ -60,6 +63,8 @@ pub struct OpportunityMetadataSvmProgramLimo { pub struct OpportunityMetadataSvmProgramSwap { #[serde_as(as = "DisplayFromStr")] pub user_wallet_address: Pubkey, + pub fee_token: FeeToken, + pub referral_fee_bps: u16, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 7814a0e3..7650eaf6 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -8,6 +8,7 @@ use { auction::{ entities::{ Auction, + BidPaymentInstructionType, BidStatus, BidStatusAuction, }, @@ -17,7 +18,6 @@ use { get_live_bids::GetLiveBidsInput, update_bid_status::UpdateBidStatusInput, update_submitted_auction::UpdateSubmittedAuctionInput, - verification::BidPaymentInstruction, Service as AuctionService, }, }, @@ -51,10 +51,12 @@ pub struct GetQuoteInput { pub fn get_quote_permission_key( tokens: &entities::QuoteTokens, user_wallet_address: &Pubkey, + referral_fee_bps: u16, ) -> Pubkey { - // get pda seeded by user_wallet_address, mints, and token amount + // get pda seeded by user_wallet_address, referral_fee_bps, mints, and token amount let input_token_amount: [u8; 8]; let output_token_amount: [u8; 8]; + let referral_fee_bps = referral_fee_bps.to_le_bytes(); let seeds = match tokens { entities::QuoteTokens::InputTokenSpecified { input_token, @@ -68,6 +70,7 @@ pub fn get_quote_permission_key( input_token_mint, input_token_amount.as_ref(), output_token_mint, + referral_fee_bps.as_ref(), ] } entities::QuoteTokens::OutputTokenSpecified { @@ -82,6 +85,7 @@ pub fn get_quote_permission_key( input_token_mint, output_token_mint, output_token_amount.as_ref(), + referral_fee_bps.as_ref(), ] } }; @@ -97,12 +101,11 @@ impl Service { program: &ProgramSvm, ) -> Result { let router = quote_create.router; - let chain_config = self.get_config("e_create.chain_id)?; - if router != chain_config.wallet_program_router_account { - return Err(RestError::Forbidden); - } - let permission_account = - get_quote_permission_key("e_create.tokens, "e_create.user_wallet_address); + let permission_account = get_quote_permission_key( + "e_create.tokens, + "e_create.user_wallet_address, + quote_create.referral_fee_bps, + ); // TODO*: we should fix the Opportunity struct (or create a new format) to more clearly distinguish Swap opps from traditional opps // currently, we are using the same struct and just setting the unspecified token amount to 0 @@ -137,6 +140,9 @@ impl Service { ProgramSvm::SwapKamino => { entities::OpportunitySvmProgram::SwapKamino(entities::OpportunitySvmProgramSwap { user_wallet_address: quote_create.user_wallet_address, + // TODO*: we should eventually determine this more intelligently + fee_token: entities::FeeToken::InputToken, + referral_fee_bps: quote_create.referral_fee_bps, }) } _ => { @@ -179,7 +185,7 @@ impl Service { } // NOTE: This part will be removed after refactoring the permission key type - let slice: [u8; 64] = opportunity + let slice: [u8; 65] = opportunity .permission_key .to_vec() .try_into() @@ -230,19 +236,17 @@ impl Service { let swap_instruction = auction_service .extract_express_relay_bid_instruction( winner_bid.chain_data.transaction.clone(), - BidPaymentInstruction::Swap, + BidPaymentInstructionType::Swap, ) .map_err(|e| { tracing::error!("Failed to verify swap instruction: {:?}", e); RestError::TemporarilyUnavailable })?; - let _swap_data = AuctionService::extract_swap_data(&swap_instruction).map_err(|e| { + let swap_data = AuctionService::extract_swap_data(&swap_instruction).map_err(|e| { tracing::error!("Failed to extract swap data: {:?}", e); RestError::TemporarilyUnavailable })?; - let deadline = i64::MAX; - // TODO*: to fix once deadline param added to swap instruction--just set this way to make sure compiles - // let deadline = swap_data.deadline; + let deadline = swap_data.deadline; // Bids is not empty let auction = Auction::try_new(bids.clone(), bid_collection_time) diff --git a/auction-server/src/opportunity/service/mod.rs b/auction-server/src/opportunity/service/mod.rs index 72e903ae..fa3852ab 100644 --- a/auction-server/src/opportunity/service/mod.rs +++ b/auction-server/src/opportunity/service/mod.rs @@ -31,7 +31,6 @@ use { types::Address, }, futures::future::try_join_all, - solana_sdk::pubkey::Pubkey, std::{ collections::HashMap, sync::Arc, @@ -82,8 +81,7 @@ impl ConfigEvm { // NOTE: Do not implement debug here. it has a circular reference to auction_service pub struct ConfigSvm { - pub auction_service: RwLock>>, - pub wallet_program_router_account: Pubkey, + pub auction_service: RwLock>>, } impl ConfigSvm { @@ -196,12 +194,11 @@ impl ConfigSvm { ) -> anyhow::Result> { Ok(chains .iter() - .map(|(chain_id, config)| { + .map(|(chain_id, _)| { ( chain_id.clone(), Self { - auction_service: RwLock::new(None), - wallet_program_router_account: config.wallet_program_router_account, + auction_service: RwLock::new(None), }, ) }) diff --git a/auction-server/src/server.rs b/auction-server/src/server.rs index 910f6a7b..fe7d0653 100644 --- a/auction-server/src/server.rs +++ b/auction-server/src/server.rs @@ -319,9 +319,6 @@ pub async fn start_server(run_options: RunOptions) -> Result<()> { RpcClientConfig::with_commitment(CommitmentConfig::processed()), ), ), - wallet_program_router_account: chain_store - .config - .wallet_program_router_account, express_relay: auction_service::ExpressRelaySvm { program_id: chain_store .config diff --git a/auction-server/src/state.rs b/auction-server/src/state.rs index 7a948213..30a2bd44 100644 --- a/auction-server/src/state.rs +++ b/auction-server/src/state.rs @@ -31,7 +31,6 @@ use { Response, RpcLogsResponse, }, - solana_sdk::pubkey::Pubkey, std::{ collections::HashMap, sync::Arc, @@ -104,11 +103,10 @@ impl ChainStoreEvm { } pub struct ChainStoreSvm { - pub log_sender: Sender>, + pub log_sender: Sender>, // only to avoid closing the channel - pub _dummy_log_receiver: Receiver>, - pub config: ConfigSvm, - pub wallet_program_router_account: Pubkey, + pub _dummy_log_receiver: Receiver>, + pub config: ConfigSvm, } impl ChainStoreSvm { @@ -118,7 +116,6 @@ impl ChainStoreSvm { Self { log_sender: tx, _dummy_log_receiver: rx, - wallet_program_router_account: config.wallet_program_router_account, config, } } diff --git a/contracts/svm/programs/express_relay/src/error.rs b/contracts/svm/programs/express_relay/src/error.rs index 2950b4ec..9350b5bd 100644 --- a/contracts/svm/programs/express_relay/src/error.rs +++ b/contracts/svm/programs/express_relay/src/error.rs @@ -24,4 +24,6 @@ pub enum ErrorCode { InvalidMint, #[msg("A token account belongs to the wrong token program")] InvalidTokenProgram, + #[msg("Invalid referral fee")] + InvalidReferralFee, } diff --git a/contracts/svm/programs/express_relay/src/lib.rs b/contracts/svm/programs/express_relay/src/lib.rs index e23080cc..145dab4f 100644 --- a/contracts/svm/programs/express_relay/src/lib.rs +++ b/contracts/svm/programs/express_relay/src/lib.rs @@ -380,7 +380,7 @@ pub struct SwapArgs { pub amount_input: u64, pub amount_output: u64, // The referral fee is specified in basis points - pub referral_fee_bps: u64, + pub referral_fee_bps: u16, // Token in which the fees will be paid pub fee_token: FeeToken, } diff --git a/contracts/svm/programs/express_relay/src/swap.rs b/contracts/svm/programs/express_relay/src/swap.rs index cc8391dc..33996b3a 100644 --- a/contracts/svm/programs/express_relay/src/swap.rs +++ b/contracts/svm/programs/express_relay/src/swap.rs @@ -1,5 +1,6 @@ use { crate::{ + error::ErrorCode, state::ExpressRelayMetadata, token::transfer_token_if_needed, FeeToken, @@ -116,11 +117,14 @@ pub struct SwapFees { impl ExpressRelayMetadata { pub fn compute_swap_fees( &self, - referral_fee_bps: u64, + referral_fee_bps: u16, amount: u64, ) -> Result { + if u64::from(referral_fee_bps) > FEE_SPLIT_PRECISION { + return Err(ErrorCode::InvalidReferralFee.into()); + } let router_fee = amount - .checked_mul(referral_fee_bps) + .checked_mul(referral_fee_bps.into()) .ok_or(ProgramError::ArithmeticOverflow)? / FEE_SPLIT_PRECISION; let platform_fee = amount diff --git a/integration.py b/integration.py index fa52bb6d..df4e7d44 100644 --- a/integration.py +++ b/integration.py @@ -47,7 +47,6 @@ def main(): rpc_read_url: http://localhost:8899 rpc_tx_submission_url: http://localhost:8899 ws_addr: ws://localhost:8900 - wallet_program_router_account: 3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn ''' with open('auction-server/config.yaml', 'w') as f: f.write(template) diff --git a/sdk/js/package.json b/sdk/js/package.json index d411d1b8..076dbf74 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/express-relay-js", - "version": "0.17.2", + "version": "0.17.3", "description": "Utilities for interacting with the express relay protocol", "homepage": "https://github.com/pyth-network/per/tree/main/sdk/js", "author": "Douro Labs", diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index cbc30617..65785508 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -488,6 +488,7 @@ export class Client { input_token_mint: quoteRequest.inputTokenMint.toBase58(), output_token_mint: quoteRequest.outputTokenMint.toBase58(), router: quoteRequest.router.toBase58(), + referral_fee_bps: quoteRequest.referralFeeBps, specified_token_amount: quoteRequest.specifiedTokenAmount, user_wallet_address: quoteRequest.userWallet.toBase58(), version: "v1" as const, diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 13602705..eca0f552 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -313,7 +313,7 @@ export interface components { bid_amount: number; /** * @description The permission key for bid in base64 format. - * This is the concatenation of the permission account and the router account. + * This is the concatenation of the opportunity type, the router, and the permission account. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ permission_key: string; @@ -431,6 +431,12 @@ export interface components { | { /** @enum {string} */ program: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -498,6 +504,12 @@ export interface components { | { /** @enum {string} */ program: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -712,6 +724,12 @@ export interface components { * @example EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v */ output_token_mint: string; + /** + * Format: int32 + * @description The referral fee in basis points. If not provided, the referral fee will default to 0. + * @example 10 + */ + referral_fee_bps?: number; /** * @description The router account to send referral fees to. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index 8cc2c3ff..74ba9005 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -309,6 +309,11 @@ export type QuoteRequest = { * @example 11111111111111111111111111111111 */ router: PublicKey; + /** + * @description The referral fee for the swap in bps. If not provided, the referral fee will be set to the default value. + * @example 10 + */ + referralFeeBps?: number; /** * @description The specified token amount for the swap */