feat: This update includes the following changes:
1. Upgrade version from 0.4.7 to 0.5.0 2. Rename compute unit parameters from unit_limit/unit_price to tip_unit_limit/tip_unit_price 3. Remove creator parameter from API to simplify trading interface 4. Optimize PumpFun and PumpSwap protocol parameter structures with creator_vault support 5. Remove unnecessary constants and functions like TOTAL_SUPPLY and BONDING_CURVE_SUPPLY 6. Improve code formatting and documentation
This commit is contained in:
+6
-2
@@ -1,8 +1,12 @@
|
||||
[package]
|
||||
name = "sol-trade-sdk"
|
||||
version = "0.4.7"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
|
||||
authors = [
|
||||
"William <byteblock6@gmail.com>",
|
||||
"sgxiang <sgxiang@gmail.com>",
|
||||
"wei <1415121722@qq.com>",
|
||||
]
|
||||
repository = "https://github.com/0xfnzero/sol-trade-sdk"
|
||||
description = "Rust SDK to interact with the dex trade Solana program."
|
||||
license = "MIT"
|
||||
|
||||
@@ -33,14 +33,14 @@ Add the dependency to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
# Add to your Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.4.7" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.5.0" }
|
||||
```
|
||||
|
||||
### Use crates.io
|
||||
|
||||
```toml
|
||||
# Add to your Cargo.toml
|
||||
sol-trade-sdk = "0.4.7"
|
||||
sol-trade-sdk = "0.5.0"
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
+2
-2
@@ -33,14 +33,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
|
||||
|
||||
```toml
|
||||
# 添加到您的 Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.4.7" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.5.0" }
|
||||
```
|
||||
|
||||
### 使用 crates.io
|
||||
|
||||
```toml
|
||||
# 添加到您的 Cargo.toml
|
||||
sol-trade-sdk = "0.4.7"
|
||||
sol-trade-sdk = "0.5.0"
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
+25
-20
@@ -1,9 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
constants::trade::trade::{
|
||||
DEFAULT_BUY_TIP_FEE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE,
|
||||
DEFAULT_TIP_UNIT_LIMIT, DEFAULT_TIP_UNIT_PRICE,
|
||||
},
|
||||
swqos::{SwqosClient, SwqosConfig},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
|
||||
use serde::Deserialize;
|
||||
use crate::{constants::trade::trade::{DEFAULT_BUY_TIP_FEE, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE}, swqos::{SwqosClient, SwqosConfig}};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TradeConfig {
|
||||
@@ -16,26 +22,20 @@ pub struct TradeConfig {
|
||||
|
||||
impl TradeConfig {
|
||||
pub fn new(
|
||||
rpc_url: String,
|
||||
rpc_url: String,
|
||||
swqos_configs: Vec<SwqosConfig>,
|
||||
priority_fee: PriorityFee,
|
||||
commitment: CommitmentConfig,
|
||||
priority_fee: PriorityFee,
|
||||
commitment: CommitmentConfig,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
) -> Self {
|
||||
Self {
|
||||
rpc_url,
|
||||
swqos_configs,
|
||||
priority_fee,
|
||||
commitment,
|
||||
lookup_table_key,
|
||||
}
|
||||
Self { rpc_url, swqos_configs, priority_fee, commitment, lookup_table_key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq)]
|
||||
pub struct PriorityFee {
|
||||
pub unit_limit: u32,
|
||||
pub unit_price: u64,
|
||||
pub tip_unit_limit: u32,
|
||||
pub tip_unit_price: u64,
|
||||
pub rpc_unit_limit: u32,
|
||||
pub rpc_unit_price: u64,
|
||||
pub buy_tip_fee: f64,
|
||||
@@ -46,15 +46,15 @@ pub struct PriorityFee {
|
||||
|
||||
impl Default for PriorityFee {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
unit_limit: DEFAULT_COMPUTE_UNIT_LIMIT,
|
||||
unit_price: DEFAULT_COMPUTE_UNIT_PRICE,
|
||||
Self {
|
||||
tip_unit_limit: DEFAULT_TIP_UNIT_LIMIT,
|
||||
tip_unit_price: DEFAULT_TIP_UNIT_PRICE,
|
||||
rpc_unit_limit: DEFAULT_RPC_UNIT_LIMIT,
|
||||
rpc_unit_price: DEFAULT_RPC_UNIT_PRICE,
|
||||
buy_tip_fee: DEFAULT_BUY_TIP_FEE,
|
||||
buy_tip_fee: DEFAULT_BUY_TIP_FEE,
|
||||
buy_tip_fees: vec![],
|
||||
smart_buy_tip_fee: 0.0,
|
||||
sell_tip_fee: DEFAULT_SELL_TIP_FEE
|
||||
sell_tip_fee: DEFAULT_SELL_TIP_FEE,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,12 @@ pub struct MethodArgs {
|
||||
}
|
||||
|
||||
impl MethodArgs {
|
||||
pub fn new(payer: Arc<Keypair>, rpc: Arc<RpcClient>, nonblocking_rpc: Arc<SolanaRpcClient>, jito_client: Arc<SwqosClient>) -> Self {
|
||||
pub fn new(
|
||||
payer: Arc<Keypair>,
|
||||
rpc: Arc<RpcClient>,
|
||||
nonblocking_rpc: Arc<SolanaRpcClient>,
|
||||
jito_client: Arc<SwqosClient>,
|
||||
) -> Self {
|
||||
Self { payer, rpc, nonblocking_rpc, jito_client }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,6 @@ pub mod global_constants {
|
||||
|
||||
pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000; // 10^9 for solana lamports
|
||||
|
||||
pub const TOTAL_SUPPLY: u64 = 1_000_000_000 * SCALE; // 1 billion tokens
|
||||
|
||||
pub const BONDING_CURVE_SUPPLY: u64 = 793_100_000 * SCALE; // total supply of bonding curve tokens
|
||||
|
||||
pub const COMPLETION_LAMPORTS: u64 = 85 * LAMPORTS_PER_SOL; // ~ 85 SOL
|
||||
|
||||
/// Public key for the fee recipient
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod trade {
|
||||
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
|
||||
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
|
||||
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
|
||||
pub const DEFAULT_TIP_UNIT_LIMIT: u32 = 78000;
|
||||
pub const DEFAULT_TIP_UNIT_PRICE: u64 = 500000;
|
||||
pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006;
|
||||
pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001;
|
||||
pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 78000;
|
||||
|
||||
@@ -51,13 +51,20 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
params.sol_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
let creator_vault_pda = bonding_curve.get_creator_vault_pda();
|
||||
let creator_vault_pda = protocol_params.creator_vault;
|
||||
|
||||
let mut creator = Pubkey::default();
|
||||
if let Some(default_creator_ata) = get_creator_vault_pda(&creator) {
|
||||
if default_creator_ata != creator_vault_pda {
|
||||
creator = creator_vault_pda;
|
||||
}
|
||||
}
|
||||
|
||||
let buy_token_amount = get_buy_token_amount_from_sol_amount(
|
||||
bonding_curve.virtual_token_reserves as u128,
|
||||
bonding_curve.virtual_sol_reserves as u128,
|
||||
bonding_curve.real_token_reserves as u128,
|
||||
bonding_curve.creator,
|
||||
creator,
|
||||
params.sol_amount,
|
||||
);
|
||||
|
||||
@@ -102,13 +109,20 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
} else {
|
||||
return Err(anyhow!("Amount token is required"));
|
||||
};
|
||||
let creator_vault_pda = get_creator_vault_pda(¶ms.creator).unwrap();
|
||||
let creator_vault_pda = protocol_params.creator_vault;
|
||||
let ata = get_associated_token_address(¶ms.payer.pubkey(), ¶ms.mint);
|
||||
|
||||
let mut creator = Pubkey::default();
|
||||
if let Some(default_creator_ata) = get_creator_vault_pda(&creator) {
|
||||
if default_creator_ata != creator_vault_pda {
|
||||
creator = creator_vault_pda;
|
||||
}
|
||||
}
|
||||
|
||||
let sol_amount = get_sell_sol_amount_from_token_amount(
|
||||
bonding_curve.virtual_token_reserves as u128,
|
||||
bonding_curve.virtual_sol_reserves as u128,
|
||||
bonding_curve.creator,
|
||||
creator,
|
||||
token_amount,
|
||||
);
|
||||
let min_sol_output = calculate_with_slippage_sell(
|
||||
|
||||
+38
-12
@@ -44,6 +44,8 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
let quote_mint = protocol_params.quote_mint;
|
||||
let pool_base_token_reserves = protocol_params.pool_base_token_reserves;
|
||||
let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves;
|
||||
let coin_creator_vault_ata = protocol_params.coin_creator_vault_ata;
|
||||
let coin_creator_vault_authority = protocol_params.coin_creator_vault_authority;
|
||||
|
||||
self.build_buy_instructions_with_accounts(
|
||||
params,
|
||||
@@ -52,6 +54,8 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
protocol_params.auto_handle_wsol,
|
||||
)
|
||||
.await
|
||||
@@ -69,6 +73,8 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
let quote_mint = protocol_params.quote_mint;
|
||||
let pool_base_token_reserves = protocol_params.pool_base_token_reserves;
|
||||
let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves;
|
||||
let coin_creator_vault_ata = protocol_params.coin_creator_vault_ata;
|
||||
let coin_creator_vault_authority = protocol_params.coin_creator_vault_authority;
|
||||
|
||||
self.build_sell_instructions_with_accounts(
|
||||
params,
|
||||
@@ -77,6 +83,8 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
protocol_params.auto_handle_wsol,
|
||||
)
|
||||
.await
|
||||
@@ -93,6 +101,8 @@ impl PumpSwapInstructionBuilder {
|
||||
quote_mint: Pubkey,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
params_coin_creator_vault_ata: Pubkey,
|
||||
params_coin_creator_vault_authority: Pubkey,
|
||||
auto_handle_wsol: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
if params.rpc.is_none() {
|
||||
@@ -102,13 +112,19 @@ impl PumpSwapInstructionBuilder {
|
||||
|
||||
let mut token_amount = 0;
|
||||
let mut sol_amount = 0;
|
||||
|
||||
let mut creator = Pubkey::default();
|
||||
let default_creator_ata = coin_creator_vault_ata(creator, quote_mint);
|
||||
if default_creator_ata != params_coin_creator_vault_ata {
|
||||
creator = params_coin_creator_vault_ata;
|
||||
}
|
||||
if quote_mint_is_wsol {
|
||||
let result = buy_quote_input_internal(
|
||||
params.sol_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
&creator,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_out
|
||||
@@ -121,7 +137,7 @@ impl PumpSwapInstructionBuilder {
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
&creator,
|
||||
)
|
||||
.unwrap();
|
||||
// min_quote_amount_out
|
||||
@@ -203,8 +219,6 @@ impl PumpSwapInstructionBuilder {
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator, quote_mint);
|
||||
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
|
||||
let fee_recipient_ata = fee_recipient_ata(accounts::FEE_RECIPIENT, quote_mint);
|
||||
|
||||
// Create buy instruction
|
||||
@@ -229,8 +243,11 @@ impl PumpSwapInstructionBuilder {
|
||||
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(params_coin_creator_vault_ata, false), // coin_creator_vault_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
params_coin_creator_vault_authority,
|
||||
false,
|
||||
), // coin_creator_vault_authority (readonly)
|
||||
];
|
||||
if quote_mint_is_wsol {
|
||||
accounts.push(solana_sdk::instruction::AccountMeta::new(
|
||||
@@ -289,6 +306,8 @@ impl PumpSwapInstructionBuilder {
|
||||
quote_mint: Pubkey,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
params_coin_creator_vault_ata: Pubkey,
|
||||
params_coin_creator_vault_authority: Pubkey,
|
||||
auto_handle_wsol: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
if params.rpc.is_none() {
|
||||
@@ -303,13 +322,19 @@ impl PumpSwapInstructionBuilder {
|
||||
let mut token_amount = 0;
|
||||
let mut sol_amount = 0;
|
||||
|
||||
let mut creator = Pubkey::default();
|
||||
let default_creator_ata = coin_creator_vault_ata(creator, quote_mint);
|
||||
if default_creator_ata != params_coin_creator_vault_ata {
|
||||
creator = params_coin_creator_vault_ata;
|
||||
}
|
||||
|
||||
if quote_mint_is_wsol {
|
||||
let result = sell_base_input_internal(
|
||||
params.token_amount.unwrap(),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
&creator,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_in
|
||||
@@ -322,7 +347,7 @@ impl PumpSwapInstructionBuilder {
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
&creator,
|
||||
)
|
||||
.unwrap();
|
||||
// max_quote_amount_in
|
||||
@@ -331,8 +356,6 @@ impl PumpSwapInstructionBuilder {
|
||||
sol_amount = result.base;
|
||||
}
|
||||
|
||||
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator, quote_mint);
|
||||
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
|
||||
let fee_recipient_ata = fee_recipient_ata(accounts::FEE_RECIPIENT, quote_mint);
|
||||
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
@@ -399,8 +422,11 @@ impl PumpSwapInstructionBuilder {
|
||||
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(params_coin_creator_vault_ata, false), // coin_creator_vault_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
params_coin_creator_vault_authority,
|
||||
false,
|
||||
), // coin_creator_vault_authority (readonly)
|
||||
];
|
||||
if !quote_mint_is_wsol {
|
||||
accounts.push(solana_sdk::instruction::AccountMeta::new(
|
||||
|
||||
-89
@@ -129,7 +129,6 @@ impl SolanaTrade {
|
||||
///
|
||||
/// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk)
|
||||
/// * `mint` - The public key of the token mint to buy
|
||||
/// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None)
|
||||
/// * `sol_amount` - Amount of SOL to spend on the purchase (in lamports)
|
||||
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
|
||||
/// * `recent_blockhash` - Recent blockhash for transaction validity
|
||||
@@ -149,36 +148,10 @@ impl SolanaTrade {
|
||||
/// - The transaction fails to execute
|
||||
/// - Network or RPC errors occur
|
||||
/// - Insufficient SOL balance for the purchase
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use solana_sdk::pubkey::Pubkey;
|
||||
/// use solana_sdk::hash::Hash;
|
||||
/// use crate::trading::factory::DexType;
|
||||
///
|
||||
/// let mint = Pubkey::new_unique();
|
||||
/// let sol_amount = 1_000_000_000; // 1 SOL in lamports
|
||||
/// let slippage = Some(500); // 5% slippage
|
||||
/// let recent_blockhash = Hash::default();
|
||||
///
|
||||
/// solana_trade.buy(
|
||||
/// DexType::PumpFun,
|
||||
/// mint,
|
||||
/// None,
|
||||
/// sol_amount,
|
||||
/// slippage,
|
||||
/// recent_blockhash,
|
||||
/// None,
|
||||
/// None,
|
||||
/// Some(lookup_table_pubkey),
|
||||
/// ).await?;
|
||||
/// ```
|
||||
pub async fn buy(
|
||||
&self,
|
||||
dex_type: DexType,
|
||||
mint: Pubkey,
|
||||
creator: Option<Pubkey>,
|
||||
sol_amount: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
recent_blockhash: Hash,
|
||||
@@ -196,7 +169,6 @@ impl SolanaTrade {
|
||||
rpc: Some(self.rpc.clone()),
|
||||
payer: self.payer.clone(),
|
||||
mint: mint,
|
||||
creator: creator.unwrap_or(Pubkey::default()),
|
||||
sol_amount: sol_amount,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: self.trade_config.priority_fee.clone(),
|
||||
@@ -245,7 +217,6 @@ impl SolanaTrade {
|
||||
///
|
||||
/// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk)
|
||||
/// * `mint` - The public key of the token mint to sell
|
||||
/// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None)
|
||||
/// * `token_amount` - Amount of tokens to sell (in smallest token units)
|
||||
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
|
||||
/// * `recent_blockhash` - Recent blockhash for transaction validity
|
||||
@@ -267,37 +238,10 @@ impl SolanaTrade {
|
||||
/// - Network or RPC errors occur
|
||||
/// - Insufficient token balance for the sale
|
||||
/// - Token account doesn't exist or is not properly initialized
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use solana_sdk::pubkey::Pubkey;
|
||||
/// use solana_sdk::hash::Hash;
|
||||
/// use crate::trading::factory::DexType;
|
||||
///
|
||||
/// let mint = Pubkey::new_unique();
|
||||
/// let token_amount = 1_000_000; // Amount of tokens to sell
|
||||
/// let slippage = Some(500); // 5% slippage
|
||||
/// let recent_blockhash = Hash::default();
|
||||
///
|
||||
/// solana_trade.sell(
|
||||
/// DexType::PumpFun,
|
||||
/// mint,
|
||||
/// None,
|
||||
/// token_amount,
|
||||
/// slippage,
|
||||
/// recent_blockhash,
|
||||
/// None,
|
||||
/// false,
|
||||
/// None,
|
||||
/// Some(lookup_table_pubkey),
|
||||
/// ).await?;
|
||||
/// ```
|
||||
pub async fn sell(
|
||||
&self,
|
||||
dex_type: DexType,
|
||||
mint: Pubkey,
|
||||
creator: Option<Pubkey>,
|
||||
token_amount: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
recent_blockhash: Hash,
|
||||
@@ -316,7 +260,6 @@ impl SolanaTrade {
|
||||
rpc: Some(self.rpc.clone()),
|
||||
payer: self.payer.clone(),
|
||||
mint: mint,
|
||||
creator: creator.unwrap_or(Pubkey::default()),
|
||||
token_amount: Some(token_amount),
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: self.trade_config.priority_fee.clone(),
|
||||
@@ -372,7 +315,6 @@ impl SolanaTrade {
|
||||
///
|
||||
/// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk)
|
||||
/// * `mint` - The public key of the token mint to sell
|
||||
/// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None)
|
||||
/// * `amount_token` - Total amount of tokens available (in smallest token units)
|
||||
/// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%)
|
||||
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
|
||||
@@ -396,40 +338,10 @@ impl SolanaTrade {
|
||||
/// - Network or RPC errors occur
|
||||
/// - Insufficient token balance for the calculated sale amount
|
||||
/// - Token account doesn't exist or is not properly initialized
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use solana_sdk::pubkey::Pubkey;
|
||||
/// use solana_sdk::hash::Hash;
|
||||
/// use crate::trading::factory::DexType;
|
||||
///
|
||||
/// let mint = Pubkey::new_unique();
|
||||
/// let total_tokens = 10_000_000; // Total tokens available
|
||||
/// let percent = 50; // Sell 50% of tokens
|
||||
/// let slippage = Some(500); // 5% slippage
|
||||
/// let recent_blockhash = Hash::default();
|
||||
///
|
||||
/// // This will sell 5_000_000 tokens (50% of 10_000_000)
|
||||
/// solana_trade.sell_by_percent(
|
||||
/// DexType::PumpFun,
|
||||
/// mint,
|
||||
/// None,
|
||||
/// total_tokens,
|
||||
/// percent,
|
||||
/// slippage,
|
||||
/// recent_blockhash,
|
||||
/// None,
|
||||
/// false,
|
||||
/// None,
|
||||
/// None,
|
||||
/// ).await?;
|
||||
/// ```
|
||||
pub async fn sell_by_percent(
|
||||
&self,
|
||||
dex_type: DexType,
|
||||
mint: Pubkey,
|
||||
creator: Option<Pubkey>,
|
||||
amount_token: u64,
|
||||
percent: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
@@ -447,7 +359,6 @@ impl SolanaTrade {
|
||||
self.sell(
|
||||
dex_type,
|
||||
mint,
|
||||
creator,
|
||||
amount,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
|
||||
+2
-35
@@ -101,7 +101,6 @@ async fn test_middleware() -> AnyResult<()> {
|
||||
// You can reference LoggingMiddleware to implement the InstructionMiddleware trait for your own middleware
|
||||
let middleware_manager = MiddlewareManager::new().add_middleware(Box::new(LoggingMiddleware));
|
||||
client = client.with_middleware_manager(middleware_manager);
|
||||
let creator = Pubkey::from_str("11111111111111111111111111111111")?;
|
||||
let mint_pubkey = Pubkey::from_str("xxxxx")?;
|
||||
let buy_sol_cost = 100_000;
|
||||
let slippage_basis_points = Some(100);
|
||||
@@ -113,7 +112,6 @@ async fn test_middleware() -> AnyResult<()> {
|
||||
.buy(
|
||||
DexType::PumpSwap,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -131,7 +129,6 @@ async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> Any
|
||||
println!("Testing PumpFun trading...");
|
||||
|
||||
let client = test_create_solana_trade_client().await?;
|
||||
let creator = Pubkey::from_str("xxxxxx")?;
|
||||
let mint_pubkey = Pubkey::from_str("xxxxxx")?;
|
||||
let buy_sol_cost = 100_000;
|
||||
let slippage_basis_points = Some(100);
|
||||
@@ -143,7 +140,6 @@ async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> Any
|
||||
.buy(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -161,7 +157,6 @@ async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> Any
|
||||
.sell(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -185,7 +180,6 @@ async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) ->
|
||||
|
||||
let client = test_create_solana_trade_client().await?;
|
||||
let mint_pubkey = trade_info.mint;
|
||||
let creator = trade_info.creator;
|
||||
let slippage_basis_points = Some(100);
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
|
||||
@@ -196,18 +190,11 @@ async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) ->
|
||||
.buy(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_amount,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Box::new(PumpFunParams::from_dev_trade(
|
||||
&mint_pubkey,
|
||||
trade_info.token_amount,
|
||||
trade_info.max_sol_cost,
|
||||
creator,
|
||||
None,
|
||||
)),
|
||||
Box::new(PumpFunParams::from_trade(&trade_info, None)),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
@@ -220,19 +207,12 @@ async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) ->
|
||||
.sell(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
Box::new(PumpFunParams::from_dev_trade(
|
||||
&mint_pubkey,
|
||||
trade_info.token_amount,
|
||||
trade_info.max_sol_cost,
|
||||
creator,
|
||||
None,
|
||||
)),
|
||||
Box::new(PumpFunParams::from_trade(&trade_info, None)),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
@@ -245,7 +225,6 @@ async fn test_pumpswap() -> AnyResult<()> {
|
||||
println!("Testing PumpSwap trading...");
|
||||
|
||||
let client = test_create_solana_trade_client().await?;
|
||||
let creator = Pubkey::from_str("11111111111111111111111111111111")?;
|
||||
let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?;
|
||||
let buy_sol_cost = 100_000;
|
||||
let slippage_basis_points = Some(100);
|
||||
@@ -258,7 +237,6 @@ async fn test_pumpswap() -> AnyResult<()> {
|
||||
.buy(
|
||||
DexType::PumpSwap,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -277,7 +255,6 @@ async fn test_pumpswap() -> AnyResult<()> {
|
||||
.sell(
|
||||
DexType::PumpSwap,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -308,7 +285,6 @@ async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult
|
||||
.buy(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -326,7 +302,6 @@ async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult
|
||||
.sell(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -360,7 +335,6 @@ async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyRe
|
||||
.buy(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -378,7 +352,6 @@ async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyRe
|
||||
.sell(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -408,7 +381,6 @@ async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.buy(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -427,7 +399,6 @@ async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.sell(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -458,7 +429,6 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.buy(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -479,7 +449,6 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.sell(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -512,7 +481,6 @@ async fn test_raydium_amm_v4() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.buy(
|
||||
DexType::RaydiumAmmV4,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
@@ -531,7 +499,6 @@ async fn test_raydium_amm_v4() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.sell(
|
||||
DexType::RaydiumAmmV4,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
|
||||
@@ -27,10 +27,10 @@ pub fn add_tip_compute_budget_instructions(
|
||||
instructions
|
||||
.push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size_limit));
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
|
||||
priority_fee.unit_price,
|
||||
priority_fee.tip_unit_price,
|
||||
));
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
|
||||
priority_fee.unit_limit,
|
||||
priority_fee.tip_unit_limit,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -65,9 +65,9 @@ pub fn add_sell_tip_compute_budget_instructions(
|
||||
priority_fee: &PriorityFee,
|
||||
) {
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
|
||||
priority_fee.unit_price,
|
||||
priority_fee.tip_unit_price,
|
||||
));
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
|
||||
priority_fee.unit_limit,
|
||||
priority_fee.tip_unit_limit,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ impl TradeExecutor for GenericTradeExecutor {
|
||||
rpc: params.rpc,
|
||||
payer: params.payer.clone(),
|
||||
mint: params.mint,
|
||||
creator: params.creator,
|
||||
sol_amount: params.sol_amount,
|
||||
slippage_basis_points: params.slippage_basis_points,
|
||||
priority_fee: params.priority_fee.clone(),
|
||||
@@ -208,7 +207,6 @@ impl TradeExecutor for GenericTradeExecutor {
|
||||
rpc: params.rpc,
|
||||
payer: params.payer.clone(),
|
||||
mint: params.mint,
|
||||
creator: params.creator,
|
||||
token_amount: params.token_amount,
|
||||
slippage_basis_points: params.slippage_basis_points,
|
||||
priority_fee: params.priority_fee.clone(),
|
||||
|
||||
+29
-16
@@ -5,7 +5,6 @@ use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::{
|
||||
PumpSwapBuyEvent, PumpSwapSellEvent,
|
||||
};
|
||||
use solana_streamer_sdk::streaming::event_parser::protocols::raydium_amm_v4::types::AmmInfo;
|
||||
use solana_streamer_sdk::streaming::event_parser::protocols::raydium_amm_v4::RaydiumAmmV4SwapEvent;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::traits::ProtocolParams;
|
||||
@@ -22,7 +21,9 @@ use crate::trading::bonk::common::{
|
||||
get_platform_associated_account,
|
||||
};
|
||||
use crate::trading::common::get_multi_token_balances;
|
||||
use crate::trading::pumpswap::common::get_token_balances;
|
||||
use crate::trading::pumpswap::common::{
|
||||
coin_creator_vault_ata, coin_creator_vault_authority, get_token_balances,
|
||||
};
|
||||
use crate::trading::raydium_cpmm::common::get_pool_token_balances;
|
||||
|
||||
/// Common buy parameters
|
||||
@@ -32,7 +33,6 @@ pub struct BuyParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
pub payer: Arc<Keypair>,
|
||||
pub mint: Pubkey,
|
||||
pub creator: Pubkey,
|
||||
pub sol_amount: u64,
|
||||
pub slippage_basis_points: Option<u64>,
|
||||
pub priority_fee: PriorityFee,
|
||||
@@ -51,7 +51,6 @@ pub struct BuyWithTipParams {
|
||||
pub swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
pub payer: Arc<Keypair>,
|
||||
pub mint: Pubkey,
|
||||
pub creator: Pubkey,
|
||||
pub sol_amount: u64,
|
||||
pub slippage_basis_points: Option<u64>,
|
||||
pub priority_fee: PriorityFee,
|
||||
@@ -69,7 +68,6 @@ pub struct SellParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
pub payer: Arc<Keypair>,
|
||||
pub mint: Pubkey,
|
||||
pub creator: Pubkey,
|
||||
pub token_amount: Option<u64>,
|
||||
pub slippage_basis_points: Option<u64>,
|
||||
pub priority_fee: PriorityFee,
|
||||
@@ -87,7 +85,6 @@ pub struct SellWithTipParams {
|
||||
pub swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
pub payer: Arc<Keypair>,
|
||||
pub mint: Pubkey,
|
||||
pub creator: Pubkey,
|
||||
pub token_amount: Option<u64>,
|
||||
pub slippage_basis_points: Option<u64>,
|
||||
pub priority_fee: PriorityFee,
|
||||
@@ -102,29 +99,33 @@ pub struct SellWithTipParams {
|
||||
#[derive(Clone)]
|
||||
pub struct PumpFunParams {
|
||||
pub bonding_curve: Arc<BondingCurveAccount>,
|
||||
pub creator_vault: Pubkey,
|
||||
/// Whether to close token account when selling, only effective during sell operations
|
||||
pub close_token_account_when_sell: Option<bool>,
|
||||
}
|
||||
|
||||
impl PumpFunParams {
|
||||
pub fn immediate_sell(creator: Pubkey, close_token_account_when_sell: bool) -> Self {
|
||||
pub fn immediate_sell(creator_vault: Pubkey, close_token_account_when_sell: bool) -> Self {
|
||||
Self {
|
||||
bonding_curve: Arc::new(BondingCurveAccount { creator, ..Default::default() }),
|
||||
bonding_curve: Arc::new(BondingCurveAccount { ..Default::default() }),
|
||||
creator_vault: creator_vault,
|
||||
close_token_account_when_sell: Some(close_token_account_when_sell),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_dev_trade(
|
||||
mint: &Pubkey,
|
||||
dev_token_amount: u64,
|
||||
dev_sol_amount: u64,
|
||||
creator: Pubkey,
|
||||
event: &PumpFunTradeEvent,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
) -> Self {
|
||||
let bonding_curve =
|
||||
BondingCurveAccount::from_dev_trade(mint, dev_token_amount, dev_sol_amount, creator);
|
||||
let bonding_curve = BondingCurveAccount::from_dev_trade(
|
||||
&event.mint,
|
||||
event.token_amount,
|
||||
event.max_sol_cost,
|
||||
event.creator,
|
||||
);
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
creator_vault: event.creator_vault,
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
}
|
||||
}
|
||||
@@ -136,6 +137,7 @@ impl PumpFunParams {
|
||||
let bonding_curve = BondingCurveAccount::from_trade(event);
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
creator_vault: event.creator_vault,
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
}
|
||||
}
|
||||
@@ -173,6 +175,10 @@ pub struct PumpSwapParams {
|
||||
pub pool_base_token_reserves: u64,
|
||||
/// Quote token reserves in the pool
|
||||
pub pool_quote_token_reserves: u64,
|
||||
/// Coin creator vault ATA
|
||||
pub coin_creator_vault_ata: Pubkey,
|
||||
/// Coin creator vault authority
|
||||
pub coin_creator_vault_authority: Pubkey,
|
||||
/// Automatically handle WSOL wrapping
|
||||
/// When true, automatically handles wrapping and unwrapping operations between SOL and WSOL
|
||||
pub auto_handle_wsol: bool,
|
||||
@@ -186,6 +192,8 @@ impl PumpSwapParams {
|
||||
quote_mint: event.quote_mint,
|
||||
pool_base_token_reserves: event.pool_base_token_reserves,
|
||||
pool_quote_token_reserves: event.pool_quote_token_reserves,
|
||||
coin_creator_vault_ata: event.coin_creator_vault_ata,
|
||||
coin_creator_vault_authority: event.coin_creator_vault_authority,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
}
|
||||
@@ -197,6 +205,8 @@ impl PumpSwapParams {
|
||||
quote_mint: event.quote_mint,
|
||||
pool_base_token_reserves: event.pool_base_token_reserves,
|
||||
pool_quote_token_reserves: event.pool_quote_token_reserves,
|
||||
coin_creator_vault_ata: event.coin_creator_vault_ata,
|
||||
coin_creator_vault_authority: event.coin_creator_vault_authority,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
}
|
||||
@@ -208,12 +218,17 @@ impl PumpSwapParams {
|
||||
let pool_data = crate::trading::pumpswap::common::fetch_pool(rpc, pool_address).await?;
|
||||
let (pool_base_token_reserves, pool_quote_token_reserves) =
|
||||
get_token_balances(&pool_data, rpc).await?;
|
||||
let creator = pool_data.creator;
|
||||
let coin_creator_vault_ata = coin_creator_vault_ata(creator, pool_data.quote_mint);
|
||||
let coin_creator_vault_authority = coin_creator_vault_authority(creator);
|
||||
Ok(Self {
|
||||
pool: pool_address.clone(),
|
||||
base_mint: pool_data.base_mint,
|
||||
quote_mint: pool_data.quote_mint,
|
||||
pool_base_token_reserves: pool_base_token_reserves,
|
||||
pool_quote_token_reserves: pool_quote_token_reserves,
|
||||
coin_creator_vault_ata: coin_creator_vault_ata,
|
||||
coin_creator_vault_authority: coin_creator_vault_authority,
|
||||
auto_handle_wsol: true,
|
||||
})
|
||||
}
|
||||
@@ -495,7 +510,6 @@ impl BuyParams {
|
||||
swqos_clients,
|
||||
payer: self.payer,
|
||||
mint: self.mint,
|
||||
creator: self.creator,
|
||||
sol_amount: self.sol_amount,
|
||||
slippage_basis_points: self.slippage_basis_points,
|
||||
priority_fee: self.priority_fee,
|
||||
@@ -517,7 +531,6 @@ impl SellParams {
|
||||
swqos_clients,
|
||||
payer: self.payer,
|
||||
mint: self.mint,
|
||||
creator: self.creator,
|
||||
token_amount: self.token_amount,
|
||||
slippage_basis_points: self.slippage_basis_points,
|
||||
priority_fee: self.priority_fee,
|
||||
|
||||
@@ -16,15 +16,6 @@ lazy_static::lazy_static! {
|
||||
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<GlobalAccount>>> = RwLock::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instruction> {
|
||||
let mut instructions = Vec::with_capacity(2);
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit));
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price));
|
||||
|
||||
instructions
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_global_pda() -> Pubkey {
|
||||
static GLOBAL_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
|
||||
|
||||
Reference in New Issue
Block a user