Skip to content

Commit c8df53c

Browse files
authored
feat: add minimum platform fees (#571)
* add minimum platform fees * fix rust sdk * cleanup * address comments * fix request format in test swap
1 parent d0a30ea commit c8df53c

File tree

14 files changed

+125
-49
lines changed

14 files changed

+125
-49
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

auction-server/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "auction-server"
3-
version = "0.33.0"
3+
version = "0.34.0"
44
edition = "2021"
55
license-file = "license.txt"
66

auction-server/api-types/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "express-relay-api-types"
3-
version = "0.12.0"
3+
version = "0.12.1"
44
edition = "2021"
55
description = "Pyth Express Relay api types"
66
repository = "https://github.com/pyth-network/per"

auction-server/api-types/src/opportunity.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ pub enum OpportunityParamsV1ProgramSvm {
225225
#[serde_as(as = "DisplayFromStr")]
226226
router_account: Pubkey,
227227

228+
#[deprecated = "This field is deprecated and will be removed in a future release."]
228229
// TODO this should be deleted
229230
/// The referral fee in basis points.
230231
#[schema(example = 10)]
@@ -234,6 +235,7 @@ pub enum OpportunityParamsV1ProgramSvm {
234235
#[schema(example = 1000)]
235236
referral_fee_ppm: u64,
236237

238+
#[deprecated = "This field is deprecated and will be removed in a future release."]
237239
// TODO this should be deleted
238240
/// The platform fee in basis points.
239241
#[schema(example = 10)]

auction-server/config.sample.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,18 @@ chains:
3030
- So11111111111111111111111111111111111111112
3131
- EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
3232
allow_permissionless_quote_requests: true
33-
minimum_fee_list:
33+
minimum_referral_fee_list:
3434
profiles:
3535
- profile_id: 0b059fa2-189f-4498-a646-e7ee1ed79c3c
3636
minimum_fees:
3737
- mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
3838
fee_ppm: 100
3939
- mint: So11111111111111111111111111111111111111112
4040
fee_ppm: 200
41+
minimum_platform_fee_list:
42+
minimum_fees:
43+
- mint: So11111111111111111111111111111111111111112
44+
fee_ppm: 100
4145

4246
lazer:
4347
price_feeds:

auction-server/src/api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ impl std::fmt::Display for SwapInstructionError {
376376
),
377377
SwapInstructionError::ReferralFee { expected, found } => write!(
378378
f,
379-
"Invalid referral fee bps {} in swap instruction data. Value does not match the referral fee bps in swap opportunity {}",
379+
"Invalid referral fee ppm {} in swap instruction data. Value does not match the referral fee ppm in swap opportunity {}",
380380
found, expected
381381
),
382382
SwapInstructionError::AssociatedRouterTokenAccount { expected, found } => write!(

auction-server/src/config.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,12 @@ pub struct ConfigSvm {
175175
/// Whitelisted token mints
176176
#[serde(default)]
177177
pub token_whitelist: TokenWhitelistConfig,
178-
/// Minimum fee list
178+
/// Minimum referral fee list
179179
#[serde(default)]
180-
pub minimum_fee_list: MinimumFeeListConfig,
180+
pub minimum_referral_fee_list: MinimumReferralFeeListConfig,
181+
/// Minimum platform fee list
182+
#[serde(default)]
183+
pub minimum_platform_fee_list: MinimumPlatformFeeListConfig,
181184
/// Whether to allow permissionless quote requests.
182185
#[serde(default)]
183186
pub allow_permissionless_quote_requests: bool,
@@ -207,14 +210,22 @@ pub struct TokenWhitelistConfig {
207210
pub whitelist_mints: Vec<Pubkey>,
208211
}
209212

210-
/// Optional minimum fee list to determine validity of quote request
213+
/// Minimum referral fee list to determine validity of quote request
211214
#[serde_as]
212215
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
213-
pub struct MinimumFeeListConfig {
216+
pub struct MinimumReferralFeeListConfig {
214217
#[serde(default)]
215218
pub profiles: Vec<MinimumFeeProfile>,
216219
}
217220

221+
/// Minimum platform fee list to determine platform fees to apply to quote requests
222+
#[serde_as]
223+
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
224+
pub struct MinimumPlatformFeeListConfig {
225+
#[serde(default)]
226+
pub minimum_fees: Vec<MinimumFee>,
227+
}
228+
218229
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
219230
pub struct MinimumFeeProfile {
220231
pub profile_id: Option<Uuid>,

auction-server/src/opportunity/service/get_quote.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ use {
5353
solana_sdk::pubkey::Pubkey,
5454
spl_associated_token_account::get_associated_token_address_with_program_id,
5555
spl_token::native_mint,
56-
std::time::Duration,
56+
std::{
57+
cmp::max,
58+
time::Duration,
59+
},
5760
time::OffsetDateTime,
5861
tokio::time::sleep,
5962
};
@@ -214,6 +217,14 @@ impl Service {
214217
})
215218
.await?;
216219

220+
let platform_fee_pair_ppm = config
221+
.get_platform_fee_ppm(&mint_user, &mint_searcher)
222+
.unwrap_or(0);
223+
let platform_fee_ppm = max(
224+
platform_fee_pair_ppm,
225+
metadata.swap_platform_fee_bps * FEE_BPS_TO_PPM,
226+
);
227+
217228
let fee_token = get_fee_token(mint_user, mint_searcher, &config.ordered_fee_tokens);
218229
let (searcher_amount, user_amount) = match (quote_create.tokens.clone(), fee_token.clone())
219230
{
@@ -224,15 +235,14 @@ impl Service {
224235
// This is not exactly accurate and may overestimate the amount needed
225236
// because of floor / ceil rounding errors.
226237
let referral_fee_ppm = referral_fee_info.referral_fee_ppm;
227-
let swap_platform_fee_ppm = metadata.swap_platform_fee_bps * FEE_BPS_TO_PPM;
228-
if referral_fee_ppm + swap_platform_fee_ppm >= FEE_SPLIT_PRECISION_PPM {
238+
if referral_fee_ppm + platform_fee_ppm >= FEE_SPLIT_PRECISION_PPM {
229239
return Err(RestError::BadParameters(format!(
230240
"Referral fee ppm + platform fee ppm must be less than {}",
231241
FEE_SPLIT_PRECISION_PPM
232242
)));
233243
}
234244
let denominator: u64 =
235-
FEE_SPLIT_PRECISION_PPM - referral_fee_ppm - swap_platform_fee_ppm;
245+
FEE_SPLIT_PRECISION_PPM - referral_fee_ppm - platform_fee_ppm;
236246
let numerator = searcher_token.amount * FEE_SPLIT_PRECISION_PPM;
237247
let amount_including_fees = numerator.div_ceil(denominator);
238248
(amount_including_fees, 0u64)
@@ -349,7 +359,7 @@ impl Service {
349359
// token_account_initialization_configs.router_fee_receiver_ta =
350360
// TokenAccountInitializationConfig::Unneeded;
351361
}
352-
if metadata.swap_platform_fee_bps == 0 {
362+
if platform_fee_ppm == 0 {
353363
// If the platform fee is 0, we can skip the initialization of the express relay and relayer token accounts
354364
token_account_initialization_configs.express_relay_fee_receiver_ata =
355365
TokenAccountInitializationConfig::Unneeded;
@@ -374,8 +384,11 @@ impl Service {
374384
fee_token,
375385
referral_fee_bps,
376386
referral_fee_ppm: referral_fee_info.referral_fee_ppm,
377-
platform_fee_bps: metadata.swap_platform_fee_bps,
378-
platform_fee_ppm: metadata.swap_platform_fee_bps * FEE_BPS_TO_PPM,
387+
// TODO: we should deprecate and then get rid of the bps fields.
388+
// For now we keep them for backwards compatibility, and generally the fees are in bp units.
389+
// But if searchers are using the bps fields to calculate the ppm fields, this will lead to SwapInstructionError::PlatformFee if the fees are ever non-unit bps.
390+
platform_fee_bps: platform_fee_ppm / FEE_BPS_TO_PPM,
391+
platform_fee_ppm,
379392
token_program_user,
380393
user_mint_user_balance,
381394
token_account_initialization_configs,

auction-server/src/opportunity/service/mod.rs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use {
1212
self as auction_service,
1313
},
1414
config::{
15-
MinimumFeeListConfig,
15+
MinimumPlatformFeeListConfig,
16+
MinimumReferralFeeListConfig,
1617
TokenWhitelistConfig,
1718
},
1819
kernel::{
@@ -115,7 +116,8 @@ pub struct ConfigSvm {
115116
pub ordered_fee_tokens: Vec<Pubkey>,
116117
pub auction_service_container: AuctionServiceContainer,
117118
pub token_whitelist: TokenWhitelist,
118-
pub minimum_fee_list: MinimumFeeList,
119+
pub minimum_referral_fee_list: MinimumReferralFeeList,
120+
pub minimum_platform_fee_list: Vec<MinimumFee>,
119121
pub allow_permissionless_quote_requests: bool,
120122
pub auction_time: Duration,
121123
}
@@ -150,9 +152,14 @@ impl ConfigSvm {
150152
.token_whitelist
151153
.clone()
152154
.into(),
153-
minimum_fee_list: chain_store
155+
minimum_referral_fee_list: chain_store
154156
.config
155-
.minimum_fee_list
157+
.minimum_referral_fee_list
158+
.clone()
159+
.into(),
160+
minimum_platform_fee_list: chain_store
161+
.config
162+
.minimum_platform_fee_list
156163
.clone()
157164
.into(),
158165
allow_permissionless_quote_requests: chain_store
@@ -219,10 +226,10 @@ impl ConfigSvm {
219226
}
220227

221228
let minimum_fee_searcher = self
222-
.minimum_fee_list
229+
.minimum_referral_fee_list
223230
.get_minimum_fee(&mint_searcher, profile_id);
224231
let minimum_fee_user = self
225-
.minimum_fee_list
232+
.minimum_referral_fee_list
226233
.get_minimum_fee(&mint_user, profile_id);
227234

228235
if referral_fee_ppm
@@ -255,11 +262,26 @@ impl ConfigSvm {
255262

256263
Ok(())
257264
}
265+
266+
pub fn get_platform_fee_ppm(&self, mint_user: &Pubkey, mint_searcher: &Pubkey) -> Option<u64> {
267+
let fee_user = self
268+
.minimum_platform_fee_list
269+
.iter()
270+
.find(|fee| &fee.mint == mint_user)
271+
.map(|fee| fee.fee_ppm);
272+
let fee_searcher = self
273+
.minimum_platform_fee_list
274+
.iter()
275+
.find(|fee| &fee.mint == mint_searcher)
276+
.map(|fee| fee.fee_ppm);
277+
278+
fee_user.into_iter().chain(fee_searcher).max()
279+
}
258280
}
259281

260-
/// Optional minimum fee list for token mints
282+
/// Optional minimum referral fee list for token mints
261283
#[derive(Clone, Default)]
262-
pub struct MinimumFeeList {
284+
pub struct MinimumReferralFeeList {
263285
pub profiles: Vec<MinimumFeeProfile>,
264286
}
265287

@@ -275,7 +297,7 @@ pub struct MinimumFee {
275297
pub fee_ppm: u64,
276298
}
277299

278-
impl MinimumFeeList {
300+
impl MinimumReferralFeeList {
279301
pub fn get_minimum_fee(&self, mint: &Pubkey, profile_id: Option<Uuid>) -> Option<u64> {
280302
let mut minimum_fee = self
281303
.profiles
@@ -289,8 +311,8 @@ impl MinimumFeeList {
289311
.map(|fee| fee.fee_ppm)
290312
});
291313

292-
// The minimum fee list can include an entry with no profile_id, which can be used as a fallback if no match is found for the specific profile_id.
293-
// This allows for a default minimum fee to be applied if no specific profile is found.
314+
// The minimum referral fee list can include an entry with no profile_id, which can be used as a fallback if no match is found for the specific profile_id.
315+
// This allows for a default minimum referral fee to be applied if no specific profile is found.
294316
if minimum_fee.is_none() {
295317
minimum_fee = self
296318
.profiles
@@ -309,8 +331,8 @@ impl MinimumFeeList {
309331
}
310332
}
311333

312-
impl From<MinimumFeeListConfig> for MinimumFeeList {
313-
fn from(value: MinimumFeeListConfig) -> Self {
334+
impl From<MinimumReferralFeeListConfig> for MinimumReferralFeeList {
335+
fn from(value: MinimumReferralFeeListConfig) -> Self {
314336
Self {
315337
profiles: value
316338
.profiles
@@ -331,6 +353,19 @@ impl From<MinimumFeeListConfig> for MinimumFeeList {
331353
}
332354
}
333355

356+
impl From<MinimumPlatformFeeListConfig> for Vec<MinimumFee> {
357+
fn from(value: MinimumPlatformFeeListConfig) -> Self {
358+
value
359+
.minimum_fees
360+
.into_iter()
361+
.map(|fee| MinimumFee {
362+
mint: fee.mint,
363+
fee_ppm: fee.fee_ppm,
364+
})
365+
.collect()
366+
}
367+
}
368+
334369
/// Optional whitelist for token mints
335370
#[derive(Clone, Default)]
336371
pub struct TokenWhitelist {
@@ -433,7 +468,8 @@ pub mod tests {
433468
ordered_fee_tokens: vec![],
434469
auction_service_container: AuctionServiceContainer::new(),
435470
token_whitelist: Default::default(),
436-
minimum_fee_list: Default::default(),
471+
minimum_referral_fee_list: Default::default(),
472+
minimum_platform_fee_list: Default::default(),
437473
allow_permissionless_quote_requests: true,
438474
auction_time: config::ConfigSvm::default_auction_time(),
439475
};

integration.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,20 @@ def main():
4242
- {mint_sell}
4343
- So11111111111111111111111111111111111111112
4444
allow_permissionless_quote_requests: true
45-
minimum_fee_list:
45+
minimum_referral_fee_list:
4646
profiles:
4747
- profile_id: 4b4f8bcf-415a-4509-be21-bd803cdc8937
4848
minimum_fees:
4949
- mint: {mint_buy}
5050
fee_ppm: 200
5151
- mint: {mint_sell}
5252
fee_ppm: 0
53+
minimum_platform_fee_list:
54+
minimum_fees:
55+
- mint: {mint_buy}
56+
fee_ppm: 100
57+
- mint: {mint_sell}
58+
fee_ppm: 1099
5359
lazer:
5460
price_feeds:
5561
- id: 1

0 commit comments

Comments
 (0)