feat: pass is_cashback_coin from events; PumpFun examples use sol-parser-sdk only
- PumpFunParams/BondingCurve: from_trade/from_dev_trade take is_cashback_coin parameter - pumpfun_copy_trading and pumpfun_sniper_trading use sol-parser-sdk for gRPC, pass e.is_cashback_coin - address_lookup/nonce_cache call sites updated; README EN/CN Cashback and example notes Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -47,7 +47,7 @@
|
|||||||
- [📋 Example Usage](#-example-usage)
|
- [📋 Example Usage](#-example-usage)
|
||||||
- [⚡ Trading Parameters](#-trading-parameters)
|
- [⚡ Trading Parameters](#-trading-parameters)
|
||||||
- [📊 Usage Examples Summary Table](#-usage-examples-summary-table)
|
- [📊 Usage Examples Summary Table](#-usage-examples-summary-table)
|
||||||
- [⚙️ SWQOS Service Configuration](#️-swqos-service-configuration)
|
- [⚙️ SWQoS Service Configuration](#️-swqos-service-configuration)
|
||||||
- [🔧 Middleware System](#-middleware-system)
|
- [🔧 Middleware System](#-middleware-system)
|
||||||
- [🔍 Address Lookup Tables](#-address-lookup-tables)
|
- [🔍 Address Lookup Tables](#-address-lookup-tables)
|
||||||
- [🔍 Nonce Cache](#-nonce-cache)
|
- [🔍 Nonce Cache](#-nonce-cache)
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail
|
8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail
|
||||||
9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations
|
9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations
|
||||||
10. **Middleware System**: Support for custom instruction middleware to modify, add, or remove instructions before transaction execution
|
10. **Middleware System**: Support for custom instruction middleware to modify, add, or remove instructions before transaction execution
|
||||||
11. **Shared Infrastructure**: Share expensive RPC and SWQOS clients across multiple wallets for reduced resource usage
|
11. **Shared Infrastructure**: Share expensive RPC and SWQoS clients across multiple wallets for reduced resource usage
|
||||||
|
|
||||||
## 📦 Installation
|
## 📦 Installation
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
|
|||||||
// RPC URL
|
// RPC URL
|
||||||
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
|
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
|
||||||
let commitment = CommitmentConfig::processed();
|
let commitment = CommitmentConfig::processed();
|
||||||
// Multiple SWQOS services can be configured
|
// Multiple SWQoS services can be configured
|
||||||
let swqos_configs: Vec<SwqosConfig> = vec |
|
| Seed trading example | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) |
|
||||||
| Gas fee strategy example | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) |
|
| Gas fee strategy example | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) |
|
||||||
|
|
||||||
### ⚙️ SWQOS Service Configuration
|
### ⚙️ SWQoS Service Configuration
|
||||||
|
|
||||||
When configuring SWQOS services, note the different parameter requirements for each service:
|
When configuring SWQoS services, note the different parameter requirements for each service:
|
||||||
|
|
||||||
- **Jito**: The first parameter is UUID (if no UUID, pass an empty string `""`)
|
- **Jito**: The first parameter is UUID (if no UUID, pass an empty string `""`)
|
||||||
- **Other MEV services**: The first parameter is the API Token
|
- **Other MEV services**: The first parameter is the API Token
|
||||||
|
|
||||||
#### Custom URL Support
|
#### Custom URL Support
|
||||||
|
|
||||||
Each SWQOS service now supports an optional custom URL parameter:
|
Each SWQoS service now supports an optional custom URL parameter:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Using custom URL (third parameter)
|
// Using custom URL (third parameter)
|
||||||
@@ -280,10 +280,14 @@ Use Durable Nonce to implement transaction replay protection and optimize transa
|
|||||||
|
|
||||||
## 💰 Cashback Support (PumpFun / PumpSwap)
|
## 💰 Cashback Support (PumpFun / PumpSwap)
|
||||||
|
|
||||||
PumpFun and PumpSwap support **cashback** for eligible tokens: part of the trading fee can be returned to the user. When you use this SDK to execute `buy` or `sell`, the transaction is submitted as usual; if the token has cashback enabled, the protocol will credit cashback according to its rules.
|
PumpFun and PumpSwap support **cashback** for eligible tokens: part of the trading fee can be returned to the user. The SDK **must know** whether the token has cashback enabled so that buy/sell instructions include the correct accounts (e.g. `UserVolumeAccumulator` as remaining account for cashback coins).
|
||||||
|
|
||||||
- **Trading**: No change to your code—use `TradeBuyParams` / `TradeSellParams` as normal. Cashback is handled on-chain.
|
- **When params come from RPC**: If you use `PumpFunParams::from_mint_by_rpc` or `PumpSwapParams::from_pool_address_by_rpc` / `from_mint_by_rpc`, the SDK reads `is_cashback_coin` from chain—no extra step.
|
||||||
- **Event parsing**: If you consume chain events (e.g. via [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk)), trade events can expose cashback-related fields (e.g. `cashback_fee_basis_points`, `cashback`, `is_cashback_enabled`) so your strategy or analytics can be cashback-aware.
|
- **When params come from event/parser**: If you build params from trade events (e.g. [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk)), you **must** pass the cashback flag into the SDK:
|
||||||
|
- **PumpFun**: `PumpFunParams::from_trade(..., is_cashback_coin)` and `PumpFunParams::from_dev_trade(..., is_cashback_coin)` take an `is_cashback_coin` parameter. Set it from the parsed event (e.g. CreateEvent’s `is_cashback_enabled` or BondingCurve’s `is_cashback_coin`).
|
||||||
|
- **PumpSwap**: `PumpSwapParams` has a field `is_cashback_coin`. When constructing params manually (e.g. from pool/trade events), set it from the parsed pool or event data.
|
||||||
|
- The **pumpfun_copy_trading** and **pumpfun_sniper_trading** examples use sol-parser-sdk for gRPC subscription and pass `e.is_cashback_coin` when building params.
|
||||||
|
- **Claim**: Use `client.claim_cashback_pumpfun()` and `client.claim_cashback_pumpswap(...)` to claim accumulated cashback.
|
||||||
|
|
||||||
## 🛡️ MEV Protection Services
|
## 🛡️ MEV Protection Services
|
||||||
|
|
||||||
|
|||||||
+13
-9
@@ -47,7 +47,7 @@
|
|||||||
- [📋 使用示例](#-使用示例)
|
- [📋 使用示例](#-使用示例)
|
||||||
- [⚡ 交易参数](#-交易参数)
|
- [⚡ 交易参数](#-交易参数)
|
||||||
- [📊 使用示例汇总表格](#-使用示例汇总表格)
|
- [📊 使用示例汇总表格](#-使用示例汇总表格)
|
||||||
- [⚙️ SWQOS 服务配置说明](#️-swqos-服务配置说明)
|
- [⚙️ SWQoS 服务配置说明](#️-swqos-服务配置说明)
|
||||||
- [🔧 中间件系统说明](#-中间件系统说明)
|
- [🔧 中间件系统说明](#-中间件系统说明)
|
||||||
- [🔍 地址查找表](#-地址查找表)
|
- [🔍 地址查找表](#-地址查找表)
|
||||||
- [🔍 Nonce 缓存](#-nonce-缓存)
|
- [🔍 Nonce 缓存](#-nonce-缓存)
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败
|
8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败
|
||||||
9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作
|
9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作
|
||||||
10. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除
|
10. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除
|
||||||
11. **共享基础设施**: 多钱包可共享同一套 RPC 与 SWQOS 客户端,降低资源占用
|
11. **共享基础设施**: 多钱包可共享同一套 RPC 与 SWQoS 客户端,降低资源占用
|
||||||
|
|
||||||
## 📦 安装
|
## 📦 安装
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
|
|||||||
// RPC 地址
|
// RPC 地址
|
||||||
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
|
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
|
||||||
let commitment = CommitmentConfig::processed();
|
let commitment = CommitmentConfig::processed();
|
||||||
// 可配置多个 SWQOS 服务
|
// 可配置多个 SWQoS 服务
|
||||||
let swqos_configs: Vec<SwqosConfig> = vec |
|
| Seed 优化交易示例 | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) |
|
||||||
| Gas费用策略示例 | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) |
|
| Gas费用策略示例 | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) |
|
||||||
|
|
||||||
### ⚙️ SWQOS 服务配置说明
|
### ⚙️ SWQoS 服务配置说明
|
||||||
|
|
||||||
在配置 SWQOS 服务时,需要注意不同服务的参数要求:
|
在配置 SWQoS 服务时,需要注意不同服务的参数要求:
|
||||||
|
|
||||||
- **Jito**: 第一个参数为 UUID(如无 UUID 请传入空字符串 `""`)
|
- **Jito**: 第一个参数为 UUID(如无 UUID 请传入空字符串 `""`)
|
||||||
- 其他的MEV服务,第一个参数为 API Token
|
- 其他的MEV服务,第一个参数为 API Token
|
||||||
|
|
||||||
#### 自定义 URL 支持
|
#### 自定义 URL 支持
|
||||||
|
|
||||||
每个 SWQOS 服务现在都支持可选的自定义 URL 参数:
|
每个 SWQoS 服务现在都支持可选的自定义 URL 参数:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// 使用自定义 URL(第三个参数)
|
// 使用自定义 URL(第三个参数)
|
||||||
@@ -279,10 +279,14 @@ let middleware_manager = MiddlewareManager::new()
|
|||||||
|
|
||||||
## 💰 Cashback 支持(PumpFun / PumpSwap)
|
## 💰 Cashback 支持(PumpFun / PumpSwap)
|
||||||
|
|
||||||
PumpFun 与 PumpSwap 支持**返现(Cashback)**:部分手续费可返还给用户。使用本 SDK 执行 `buy` / `sell` 时,按正常方式提交交易即可;若代币已开启返现,协议会按规则自动结算返现。
|
PumpFun 与 PumpSwap 支持**返现(Cashback)**:部分手续费可返还给用户。SDK **必须知道**该代币是否开启返现,才能为 buy/sell 指令传入正确的账户(例如返现代币需要把 `UserVolumeAccumulator` 作为 remaining account)。
|
||||||
|
|
||||||
- **交易侧**:无需改代码,照常使用 `TradeBuyParams` / `TradeSellParams`,返现由链上处理。
|
- **参数来自 RPC 时**:使用 `PumpFunParams::from_mint_by_rpc` 或 `PumpSwapParams::from_pool_address_by_rpc` / `from_mint_by_rpc` 时,SDK 会从链上读取 `is_cashback_coin`,无需额外传入。
|
||||||
- **事件解析**:若通过事件驱动(如 [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk))消费链上事件,可获取返现相关字段(如 `cashback_fee_basis_points`、`cashback`、`is_cashback_enabled`),便于策略或统计与返现逻辑结合。
|
- **参数来自事件/解析器时**:若根据交易事件(如 [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk))构建参数,**必须**把返现标志传给 SDK:
|
||||||
|
- **PumpFun**:`PumpFunParams::from_trade(..., is_cashback_coin)` 与 `PumpFunParams::from_dev_trade(..., is_cashback_coin)` 最后一个参数为 `is_cashback_coin`。从解析出的事件传入(如 sol-parser-sdk 的 `PumpFunTradeEvent.is_cashback_coin`)。
|
||||||
|
- **PumpSwap**:`PumpSwapParams` 有字段 `is_cashback_coin`。手动构造参数(如从池/交易事件)时,从解析到的池或事件数据中设置该字段。
|
||||||
|
- **pumpfun_copy_trading**、**pumpfun_sniper_trading** 示例使用 sol-parser-sdk 订阅 gRPC 事件,并在构造参数时传入 `e.is_cashback_coin`。
|
||||||
|
- **领取返现**:使用 `client.claim_cashback_pumpfun()` 和 `client.claim_cashback_pumpswap(...)` 领取累计的返现。
|
||||||
|
|
||||||
## 🛡️ MEV 保护服务
|
## 🛡️ MEV 保护服务
|
||||||
|
|
||||||
|
|||||||
@@ -143,13 +143,14 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
|
|||||||
trade_info.mint,
|
trade_info.mint,
|
||||||
trade_info.creator,
|
trade_info.creator,
|
||||||
trade_info.creator_vault,
|
trade_info.creator_vault,
|
||||||
trade_info.virtual_sol_reserves,
|
trade_info.virtual_token_reserves,
|
||||||
trade_info.virtual_sol_reserves,
|
trade_info.virtual_sol_reserves,
|
||||||
trade_info.real_token_reserves,
|
trade_info.real_token_reserves,
|
||||||
trade_info.real_sol_reserves,
|
trade_info.real_sol_reserves,
|
||||||
None,
|
None,
|
||||||
trade_info.fee_recipient,
|
trade_info.fee_recipient,
|
||||||
trade_info.token_program,
|
trade_info.token_program,
|
||||||
|
false, // is_cashback_coin: set from event/parser when available
|
||||||
)),
|
)),
|
||||||
address_lookup_table_account: address_lookup_table_account,
|
address_lookup_table_account: address_lookup_table_account,
|
||||||
wait_transaction_confirmed: true,
|
wait_transaction_confirmed: true,
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
|
|||||||
None,
|
None,
|
||||||
trade_info.fee_recipient,
|
trade_info.fee_recipient,
|
||||||
trade_info.token_program,
|
trade_info.token_program,
|
||||||
|
false, // is_cashback_coin: set from event/parser when available
|
||||||
)),
|
)),
|
||||||
address_lookup_table_account: None,
|
address_lookup_table_account: None,
|
||||||
wait_transaction_confirmed: true,
|
wait_transaction_confirmed: true,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sol-trade-sdk = { path = "../.." }
|
sol-trade-sdk = { path = "../.." }
|
||||||
solana-streamer-sdk = "0.5.0"
|
sol-parser-sdk = { path = "../../../sol-parser-sdk" }
|
||||||
solana-sdk = "3.0.0"
|
solana-sdk = "3.0.0"
|
||||||
solana-commitment-config = { version = "3.0.0", features = ["serde"] }
|
solana-commitment-config = { version = "3.0.0", features = ["serde"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
use std::sync::{
|
//! PumpFun 跟单示例(仅使用 sol-parser-sdk 订阅 gRPC 事件)
|
||||||
atomic::{AtomicBool, Ordering},
|
//!
|
||||||
Arc,
|
//! 收到 PumpFun 买卖事件后,用事件中的参数(含 is_cashback_coin)构造交易并执行一次买+卖。
|
||||||
};
|
|
||||||
|
|
||||||
|
use std::sync::{atomic::{AtomicBool, Ordering}, Arc};
|
||||||
|
|
||||||
|
use sol_parser_sdk::grpc::{
|
||||||
|
AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol,
|
||||||
|
TransactionFilter, YellowstoneGrpc,
|
||||||
|
};
|
||||||
|
use sol_parser_sdk::DexEvent;
|
||||||
use sol_trade_sdk::common::{
|
use sol_trade_sdk::common::{
|
||||||
fast_fn::get_associated_token_address_with_program_id_fast_use_seed, TradeConfig,
|
fast_fn::get_associated_token_address_with_program_id_fast_use_seed, TradeConfig,
|
||||||
};
|
};
|
||||||
@@ -16,143 +22,128 @@ use sol_trade_sdk::{
|
|||||||
use solana_commitment_config::CommitmentConfig;
|
use solana_commitment_config::CommitmentConfig;
|
||||||
use solana_sdk::signature::Keypair;
|
use solana_sdk::signature::Keypair;
|
||||||
use solana_sdk::signer::Signer;
|
use solana_sdk::signer::Signer;
|
||||||
use solana_streamer_sdk::match_event;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::common::EventType;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent};
|
|
||||||
use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter};
|
|
||||||
use solana_streamer_sdk::streaming::YellowstoneGrpc;
|
|
||||||
|
|
||||||
// Global static flag to ensure transaction is executed only once
|
|
||||||
static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false);
|
static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("Subscribing to GRPC events...");
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
println!("PumpFun 跟单示例(sol-parser-sdk gRPC)...");
|
||||||
|
|
||||||
let grpc = YellowstoneGrpc::new(
|
let config = ClientConfig {
|
||||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
enable_metrics: false,
|
||||||
None,
|
connection_timeout_ms: 10000,
|
||||||
)?;
|
request_timeout_ms: 30000,
|
||||||
|
enable_tls: true,
|
||||||
let callback = create_event_callback();
|
order_mode: OrderMode::Unordered,
|
||||||
let protocols = vec![Protocol::PumpFun];
|
..Default::default()
|
||||||
// Filter accounts
|
|
||||||
let account_include = vec![
|
|
||||||
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
|
|
||||||
];
|
|
||||||
let account_exclude = vec![];
|
|
||||||
let account_required = vec![];
|
|
||||||
|
|
||||||
// Listen to transaction data
|
|
||||||
let transaction_filter = TransactionFilter {
|
|
||||||
account_include: account_include.clone(),
|
|
||||||
account_exclude,
|
|
||||||
account_required,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to account data belonging to owner programs -> account event monitoring
|
let grpc_endpoint = std::env::var("GRPC_ENDPOINT")
|
||||||
let account_filter = AccountFilter { account: vec![], owner: vec![], filters: vec![] };
|
.unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string());
|
||||||
|
let grpc = YellowstoneGrpc::new_with_config(
|
||||||
|
grpc_endpoint.clone(),
|
||||||
|
std::env::var("GRPC_AUTH_TOKEN").ok(),
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
|
||||||
// listen to specific event type
|
let protocols = vec![Protocol::PumpFun];
|
||||||
let event_type_filter =
|
let transaction_filter = TransactionFilter::for_protocols(&protocols);
|
||||||
EventTypeFilter { include: vec![EventType::PumpFunBuy, EventType::PumpFunSell] };
|
let account_filter = AccountFilter::for_protocols(&protocols);
|
||||||
|
let event_filter = EventTypeFilter::include_only(vec![
|
||||||
|
EventType::PumpFunBuy,
|
||||||
|
EventType::PumpFunSell,
|
||||||
|
EventType::PumpFunBuyExactSolIn,
|
||||||
|
EventType::PumpFunTrade,
|
||||||
|
]);
|
||||||
|
|
||||||
grpc.subscribe_events_immediate(
|
let queue = grpc
|
||||||
protocols,
|
.subscribe_dex_events(vec![transaction_filter], vec![account_filter], Some(event_filter))
|
||||||
None,
|
.await?;
|
||||||
vec![transaction_filter],
|
|
||||||
vec![account_filter],
|
println!("订阅已启动,等待一条 PumpFun 交易后执行跟单(仅一次)...\n");
|
||||||
Some(event_type_filter),
|
|
||||||
None,
|
loop {
|
||||||
callback,
|
if let Some(event) = queue.pop() {
|
||||||
)
|
let run = match &event {
|
||||||
.await?;
|
DexEvent::PumpFunBuy(e) | DexEvent::PumpFunSell(e) | DexEvent::PumpFunBuyExactSolIn(e) => {
|
||||||
|
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
|
||||||
|
Some(e.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DexEvent::PumpFunTrade(e) => {
|
||||||
|
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
|
||||||
|
Some(e.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(e) = run {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(err) = pumpfun_copy_trade(e).await {
|
||||||
|
eprintln!("跟单执行错误: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tokio::signal::ctrl_c().await?;
|
tokio::signal::ctrl_c().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an event callback function that handles different types of events
|
|
||||||
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|
|
||||||
|event: Box<dyn UnifiedEvent>| {
|
|
||||||
match_event!(event, {
|
|
||||||
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
|
|
||||||
// Test code, only test one transaction
|
|
||||||
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
|
|
||||||
let event_clone = e.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Err(err) = pumpfun_copy_trade_with_grpc(event_clone).await {
|
|
||||||
eprintln!("Error in copy trade: {:?}", err);
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create SolanaTrade client
|
|
||||||
/// Initializes a new SolanaTrade client with configuration
|
|
||||||
async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
|
async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
|
||||||
println!("🚀 Initializing SolanaTrade client...");
|
|
||||||
let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
|
let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
|
||||||
let rpc_url = "https://api.mainnet-beta.solana.com".to_string();
|
let rpc_url = std::env::var("RPC_URL").unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
|
||||||
let commitment = CommitmentConfig::confirmed();
|
let commitment = CommitmentConfig::confirmed();
|
||||||
let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
|
let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
|
||||||
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
|
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
|
||||||
let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await;
|
Ok(SolanaTrade::new(Arc::new(payer), trade_config).await)
|
||||||
println!("✅ SolanaTrade client initialized successfully!");
|
|
||||||
Ok(solana_trade)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PumpFun sniper trade
|
async fn pumpfun_copy_trade(
|
||||||
/// This function demonstrates how to snipe a new token from a PumpFun trade event
|
e: sol_parser_sdk::core::events::PumpFunTradeEvent,
|
||||||
async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> {
|
) -> AnyResult<()> {
|
||||||
println!("Testing PumpFun trading...");
|
|
||||||
|
|
||||||
let client = create_solana_trade_client().await?;
|
let client = create_solana_trade_client().await?;
|
||||||
let mint_pubkey = trade_info.mint;
|
let mint_pubkey = e.mint;
|
||||||
let slippage_basis_points = Some(100);
|
let slippage_basis_points = Some(100u64);
|
||||||
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
|
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
|
||||||
|
|
||||||
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
||||||
gas_fee_strategy.set_global_fee_strategy(
|
gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001);
|
||||||
150000,
|
|
||||||
150000,
|
|
||||||
500000,
|
|
||||||
500000,
|
|
||||||
0.001,
|
|
||||||
0.001,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Buy tokens
|
// 买入:使用事件参数,含 is_cashback_coin(来自 sol-parser-sdk 解析)
|
||||||
println!("Buying tokens from PumpFun...");
|
let buy_sol_amount = 100_000u64;
|
||||||
let buy_sol_amount = 100_000;
|
|
||||||
let buy_params = sol_trade_sdk::TradeBuyParams {
|
let buy_params = sol_trade_sdk::TradeBuyParams {
|
||||||
dex_type: DexType::PumpFun,
|
dex_type: DexType::PumpFun,
|
||||||
input_token_type: TradeTokenType::SOL,
|
input_token_type: TradeTokenType::SOL,
|
||||||
mint: mint_pubkey,
|
mint: mint_pubkey,
|
||||||
input_token_amount: buy_sol_amount,
|
input_token_amount: buy_sol_amount,
|
||||||
slippage_basis_points: slippage_basis_points,
|
slippage_basis_points,
|
||||||
recent_blockhash: Some(recent_blockhash),
|
recent_blockhash: Some(recent_blockhash),
|
||||||
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
|
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
|
||||||
trade_info.bonding_curve,
|
e.bonding_curve,
|
||||||
trade_info.associated_bonding_curve,
|
e.associated_bonding_curve,
|
||||||
trade_info.mint,
|
e.mint,
|
||||||
trade_info.creator,
|
e.creator,
|
||||||
trade_info.creator_vault,
|
e.creator_vault,
|
||||||
trade_info.virtual_token_reserves,
|
e.virtual_token_reserves,
|
||||||
trade_info.virtual_sol_reserves,
|
e.virtual_sol_reserves,
|
||||||
trade_info.real_token_reserves,
|
e.real_token_reserves,
|
||||||
trade_info.real_sol_reserves,
|
e.real_sol_reserves,
|
||||||
None,
|
None,
|
||||||
trade_info.fee_recipient,
|
e.fee_recipient,
|
||||||
trade_info.token_program,
|
e.token_program,
|
||||||
|
e.is_cashback_coin,
|
||||||
)),
|
)),
|
||||||
address_lookup_table_account: None,
|
address_lookup_table_account: None,
|
||||||
wait_transaction_confirmed: true,
|
wait_transaction_confirmed: true,
|
||||||
@@ -167,43 +158,40 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
|
|||||||
};
|
};
|
||||||
client.buy(buy_params).await?;
|
client.buy(buy_params).await?;
|
||||||
|
|
||||||
// Sell tokens
|
// 卖出:查询余额后卖出,同样传入 is_cashback_coin
|
||||||
println!("Selling tokens from PumpFun...");
|
|
||||||
|
|
||||||
let rpc = client.infrastructure.rpc.clone();
|
let rpc = client.infrastructure.rpc.clone();
|
||||||
let payer = client.payer.pubkey();
|
let payer = client.payer.pubkey();
|
||||||
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
||||||
&payer,
|
&payer,
|
||||||
&mint_pubkey,
|
&mint_pubkey,
|
||||||
&trade_info.token_program,
|
&e.token_program,
|
||||||
client.use_seed_optimize,
|
client.use_seed_optimize,
|
||||||
);
|
);
|
||||||
let balance = rpc.get_token_account_balance(&account).await?;
|
let balance = rpc.get_token_account_balance(&account).await?;
|
||||||
println!("Balance: {:?}", balance);
|
|
||||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||||
|
|
||||||
println!("Selling {} tokens", amount_token);
|
|
||||||
let sell_params = sol_trade_sdk::TradeSellParams {
|
let sell_params = sol_trade_sdk::TradeSellParams {
|
||||||
dex_type: DexType::PumpFun,
|
dex_type: DexType::PumpFun,
|
||||||
output_token_type: TradeTokenType::SOL,
|
output_token_type: TradeTokenType::SOL,
|
||||||
mint: mint_pubkey,
|
mint: mint_pubkey,
|
||||||
input_token_amount: amount_token,
|
input_token_amount: amount_token,
|
||||||
slippage_basis_points: slippage_basis_points,
|
slippage_basis_points,
|
||||||
recent_blockhash: Some(recent_blockhash),
|
recent_blockhash: Some(recent_blockhash),
|
||||||
with_tip: false,
|
with_tip: false,
|
||||||
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
|
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
|
||||||
trade_info.bonding_curve,
|
e.bonding_curve,
|
||||||
trade_info.associated_bonding_curve,
|
e.associated_bonding_curve,
|
||||||
trade_info.mint,
|
e.mint,
|
||||||
trade_info.creator,
|
e.creator,
|
||||||
trade_info.creator_vault,
|
e.creator_vault,
|
||||||
trade_info.virtual_token_reserves,
|
e.virtual_token_reserves,
|
||||||
trade_info.virtual_sol_reserves,
|
e.virtual_sol_reserves,
|
||||||
trade_info.real_token_reserves,
|
e.real_token_reserves,
|
||||||
trade_info.real_sol_reserves,
|
e.real_sol_reserves,
|
||||||
Some(true),
|
Some(true),
|
||||||
trade_info.fee_recipient,
|
e.fee_recipient,
|
||||||
trade_info.token_program,
|
e.token_program,
|
||||||
|
e.is_cashback_coin,
|
||||||
)),
|
)),
|
||||||
address_lookup_table_account: None,
|
address_lookup_table_account: None,
|
||||||
wait_transaction_confirmed: true,
|
wait_transaction_confirmed: true,
|
||||||
@@ -212,14 +200,11 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
|
|||||||
close_mint_token_ata: false,
|
close_mint_token_ata: false,
|
||||||
durable_nonce: None,
|
durable_nonce: None,
|
||||||
fixed_output_token_amount: None,
|
fixed_output_token_amount: None,
|
||||||
gas_fee_strategy: gas_fee_strategy,
|
gas_fee_strategy,
|
||||||
simulate: false,
|
simulate: false,
|
||||||
};
|
};
|
||||||
client.sell(sell_params).await?;
|
client.sell(sell_params).await?;
|
||||||
|
|
||||||
// PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell)
|
println!("跟单一次买+卖完成");
|
||||||
// creator_vault can be obtained from the trade event
|
Ok(())
|
||||||
|
|
||||||
// Exit program
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sol-trade-sdk = { path = "../.." }
|
sol-trade-sdk = { path = "../.." }
|
||||||
solana-streamer-sdk = "0.5.0"
|
sol-parser-sdk = { path = "../../../sol-parser-sdk" }
|
||||||
solana-sdk = "3.0.0"
|
solana-sdk = "3.0.0"
|
||||||
solana-commitment-config = { version = "3.0.0", features = ["serde"] }
|
solana-commitment-config = { version = "3.0.0", features = ["serde"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
//! PumpFun 狙击示例(仅使用 sol-parser-sdk 订阅 gRPC 事件)
|
||||||
|
//!
|
||||||
|
//! 监听创建者首次买入(Create 后同笔/首笔 Buy,is_created_buy == true),
|
||||||
|
//! 用事件参数(含 is_cashback_coin)构造 from_dev_trade 并执行一次买+卖。
|
||||||
|
|
||||||
|
use std::sync::{atomic::{AtomicBool, Ordering}, Arc};
|
||||||
|
|
||||||
|
use sol_parser_sdk::grpc::{
|
||||||
|
AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol,
|
||||||
|
TransactionFilter, YellowstoneGrpc,
|
||||||
|
};
|
||||||
|
use sol_parser_sdk::DexEvent;
|
||||||
use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address;
|
use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address;
|
||||||
use sol_trade_sdk::common::TradeConfig;
|
use sol_trade_sdk::common::TradeConfig;
|
||||||
use sol_trade_sdk::TradeTokenType;
|
use sol_trade_sdk::TradeTokenType;
|
||||||
@@ -10,108 +22,120 @@ use sol_trade_sdk::{
|
|||||||
use solana_commitment_config::CommitmentConfig;
|
use solana_commitment_config::CommitmentConfig;
|
||||||
use solana_sdk::signature::Keypair;
|
use solana_sdk::signature::Keypair;
|
||||||
use solana_sdk::signer::Signer;
|
use solana_sdk::signer::Signer;
|
||||||
use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::common::EventType;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
|
|
||||||
use solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent};
|
|
||||||
use solana_streamer_sdk::{match_event, streaming::ShredStreamGrpc};
|
|
||||||
use std::sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Atomic flag to ensure the sniper trade is executed only once
|
|
||||||
static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false);
|
static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
/// Main entry point - subscribes to PumpFun events and executes sniper trades on token creation
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("Subscribing to ShredStream events...");
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
let shred_stream = ShredStreamGrpc::new("use_your_shred_stream_url_here".to_string()).await?;
|
println!("PumpFun 狙击示例(sol-parser-sdk gRPC)...");
|
||||||
let callback = create_event_callback();
|
|
||||||
let protocols = vec![Protocol::PumpFun];
|
let config = ClientConfig {
|
||||||
let event_type_filter = EventTypeFilter {
|
enable_metrics: false,
|
||||||
include: vec![EventType::PumpFunBuy, EventType::PumpFunSell, EventType::PumpFunCreateToken],
|
connection_timeout_ms: 10000,
|
||||||
|
request_timeout_ms: 30000,
|
||||||
|
enable_tls: true,
|
||||||
|
order_mode: OrderMode::Unordered,
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
println!("Starting to listen for events, press Ctrl+C to stop...");
|
|
||||||
shred_stream.shredstream_subscribe(protocols, None, Some(event_type_filter), callback).await?;
|
let grpc_endpoint = std::env::var("GRPC_ENDPOINT")
|
||||||
|
.unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string());
|
||||||
|
let grpc = YellowstoneGrpc::new_with_config(
|
||||||
|
grpc_endpoint,
|
||||||
|
std::env::var("GRPC_AUTH_TOKEN").ok(),
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let protocols = vec![Protocol::PumpFun];
|
||||||
|
let transaction_filter = TransactionFilter::for_protocols(&protocols);
|
||||||
|
let account_filter = AccountFilter::for_protocols(&protocols);
|
||||||
|
let event_filter = EventTypeFilter::include_only(vec![
|
||||||
|
EventType::PumpFunCreate,
|
||||||
|
EventType::PumpFunBuy,
|
||||||
|
EventType::PumpFunBuyExactSolIn,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let queue = grpc
|
||||||
|
.subscribe_dex_events(vec![transaction_filter], vec![account_filter], Some(event_filter))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("订阅已启动,等待创建者首次买入(is_created_buy)后执行狙击(仅一次)...\n");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(event) = queue.pop() {
|
||||||
|
let run = match &event {
|
||||||
|
DexEvent::PumpFunBuy(e) | DexEvent::PumpFunBuyExactSolIn(e) => {
|
||||||
|
if e.is_created_buy && !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
|
||||||
|
Some(e.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(e) = run {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(err) = pumpfun_sniper_trade(e).await {
|
||||||
|
eprintln!("狙击执行错误: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tokio::signal::ctrl_c().await?;
|
tokio::signal::ctrl_c().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an event callback function that handles different types of events
|
|
||||||
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|
|
||||||
|event: Box<dyn UnifiedEvent>| {
|
|
||||||
match_event!(event, {
|
|
||||||
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
|
|
||||||
// Only process developer token creation events
|
|
||||||
if !e.is_dev_create_token_trade {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Ensure we only execute the trade once using atomic compare-and-swap
|
|
||||||
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
|
|
||||||
let event_clone = e.clone();
|
|
||||||
// Spawn a new task to handle the trading operation
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Err(err) = pumpfun_sniper_trade_with_shreds(event_clone).await {
|
|
||||||
eprintln!("Error in copy trade: {:?}", err);
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create SolanaTrade client
|
|
||||||
/// Initializes a new SolanaTrade client with configuration
|
|
||||||
async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
|
async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
|
||||||
println!("🚀 Initializing SolanaTrade client...");
|
|
||||||
let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
|
let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
|
||||||
let rpc_url = "https://api.mainnet-beta.solana.com".to_string();
|
let rpc_url = std::env::var("RPC_URL").unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
|
||||||
let commitment = CommitmentConfig::confirmed();
|
let commitment = CommitmentConfig::confirmed();
|
||||||
let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
|
let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
|
||||||
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
|
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
|
||||||
let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await;
|
Ok(SolanaTrade::new(Arc::new(payer), trade_config).await)
|
||||||
println!("✅ SolanaTrade client initialized successfully!");
|
|
||||||
Ok(solana_trade)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute PumpFun sniper trading strategy based on received token creation event
|
async fn pumpfun_sniper_trade(
|
||||||
/// This function buys tokens immediately after creation and then sells all tokens
|
e: sol_parser_sdk::core::events::PumpFunTradeEvent,
|
||||||
async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> {
|
) -> AnyResult<()> {
|
||||||
println!("Testing PumpFun trading...");
|
|
||||||
|
|
||||||
let client = create_solana_trade_client().await?;
|
let client = create_solana_trade_client().await?;
|
||||||
let mint_pubkey = trade_info.mint;
|
let mint_pubkey = e.mint;
|
||||||
let slippage_basis_points = Some(300);
|
let slippage_basis_points = Some(300u64);
|
||||||
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
|
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
|
||||||
|
|
||||||
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
||||||
gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001);
|
gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001);
|
||||||
|
|
||||||
// Buy tokens
|
// 创建者首次买入:用 from_dev_trade,max_sol_cost 用事件中的 sol_amount(可酌情加滑点)
|
||||||
println!("Buying tokens from PumpFun...");
|
let buy_sol_amount = 100_000u64;
|
||||||
let buy_sol_amount = 100_000;
|
let max_sol_cost = e.sol_amount.saturating_add(e.sol_amount / 10); // 约 +10% 作为上限
|
||||||
|
|
||||||
let buy_params = sol_trade_sdk::TradeBuyParams {
|
let buy_params = sol_trade_sdk::TradeBuyParams {
|
||||||
dex_type: DexType::PumpFun,
|
dex_type: DexType::PumpFun,
|
||||||
input_token_type: TradeTokenType::SOL,
|
input_token_type: TradeTokenType::SOL,
|
||||||
mint: mint_pubkey,
|
mint: mint_pubkey,
|
||||||
input_token_amount: buy_sol_amount,
|
input_token_amount: buy_sol_amount,
|
||||||
slippage_basis_points: slippage_basis_points,
|
slippage_basis_points,
|
||||||
recent_blockhash: Some(recent_blockhash),
|
recent_blockhash: Some(recent_blockhash),
|
||||||
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_dev_trade(
|
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_dev_trade(
|
||||||
trade_info.mint,
|
e.mint,
|
||||||
trade_info.token_amount,
|
e.token_amount,
|
||||||
trade_info.max_sol_cost,
|
max_sol_cost,
|
||||||
trade_info.creator,
|
e.creator,
|
||||||
trade_info.bonding_curve,
|
e.bonding_curve,
|
||||||
trade_info.associated_bonding_curve,
|
e.associated_bonding_curve,
|
||||||
trade_info.creator_vault,
|
e.creator_vault,
|
||||||
None,
|
None,
|
||||||
trade_info.fee_recipient,
|
e.fee_recipient,
|
||||||
trade_info.token_program,
|
e.token_program,
|
||||||
|
e.is_cashback_coin,
|
||||||
)),
|
)),
|
||||||
address_lookup_table_account: None,
|
address_lookup_table_account: None,
|
||||||
wait_transaction_confirmed: true,
|
wait_transaction_confirmed: true,
|
||||||
@@ -126,26 +150,35 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
|
|||||||
};
|
};
|
||||||
client.buy(buy_params).await?;
|
client.buy(buy_params).await?;
|
||||||
|
|
||||||
// Sell tokens
|
|
||||||
println!("Selling tokens from PumpFun...");
|
|
||||||
|
|
||||||
let rpc = client.infrastructure.rpc.clone();
|
let rpc = client.infrastructure.rpc.clone();
|
||||||
let payer = client.payer.pubkey();
|
let payer = client.payer.pubkey();
|
||||||
let account = get_associated_token_address(&payer, &mint_pubkey);
|
let account = get_associated_token_address(&payer, &mint_pubkey);
|
||||||
let balance = rpc.get_token_account_balance(&account).await?;
|
let balance = rpc.get_token_account_balance(&account).await?;
|
||||||
println!("Balance: {:?}", balance);
|
|
||||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||||
|
|
||||||
println!("Selling {} tokens", amount_token);
|
|
||||||
let sell_params = sol_trade_sdk::TradeSellParams {
|
let sell_params = sol_trade_sdk::TradeSellParams {
|
||||||
dex_type: DexType::PumpFun,
|
dex_type: DexType::PumpFun,
|
||||||
output_token_type: TradeTokenType::SOL,
|
output_token_type: TradeTokenType::SOL,
|
||||||
mint: mint_pubkey,
|
mint: mint_pubkey,
|
||||||
input_token_amount: amount_token,
|
input_token_amount: amount_token,
|
||||||
slippage_basis_points: slippage_basis_points,
|
slippage_basis_points,
|
||||||
recent_blockhash: Some(recent_blockhash),
|
recent_blockhash: Some(recent_blockhash),
|
||||||
with_tip: false,
|
with_tip: false,
|
||||||
extension_params: DexParamEnum::PumpFun(PumpFunParams::immediate_sell(trade_info.creator_vault, trade_info.token_program, true)),
|
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
|
||||||
|
e.bonding_curve,
|
||||||
|
e.associated_bonding_curve,
|
||||||
|
e.mint,
|
||||||
|
e.creator,
|
||||||
|
e.creator_vault,
|
||||||
|
e.virtual_token_reserves,
|
||||||
|
e.virtual_sol_reserves,
|
||||||
|
e.real_token_reserves,
|
||||||
|
e.real_sol_reserves,
|
||||||
|
Some(true),
|
||||||
|
e.fee_recipient,
|
||||||
|
e.token_program,
|
||||||
|
e.is_cashback_coin,
|
||||||
|
)),
|
||||||
address_lookup_table_account: None,
|
address_lookup_table_account: None,
|
||||||
wait_transaction_confirmed: true,
|
wait_transaction_confirmed: true,
|
||||||
create_output_token_ata: true,
|
create_output_token_ata: true,
|
||||||
@@ -153,14 +186,11 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
|
|||||||
close_mint_token_ata: false,
|
close_mint_token_ata: false,
|
||||||
durable_nonce: None,
|
durable_nonce: None,
|
||||||
fixed_output_token_amount: None,
|
fixed_output_token_amount: None,
|
||||||
gas_fee_strategy: gas_fee_strategy,
|
gas_fee_strategy,
|
||||||
simulate: false,
|
simulate: false,
|
||||||
};
|
};
|
||||||
client.sell(sell_params).await?;
|
client.sell(sell_params).await?;
|
||||||
|
|
||||||
// PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell)
|
println!("狙击一次买+卖完成");
|
||||||
// creator_vault can be obtained from the trade event
|
Ok(())
|
||||||
|
|
||||||
// Exit program after completing the trade
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ pub struct BondingCurveAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BondingCurveAccount {
|
impl BondingCurveAccount {
|
||||||
|
/// When building from event/parser data (e.g. sol-parser-sdk), pass the token's cashback flag
|
||||||
|
/// so that sell instructions include the correct remaining accounts. From RPC use `from_mint_by_rpc` instead.
|
||||||
pub fn from_dev_trade(
|
pub fn from_dev_trade(
|
||||||
bonding_curve: Pubkey,
|
bonding_curve: Pubkey,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
@@ -72,6 +74,7 @@ impl BondingCurveAccount {
|
|||||||
dev_sol_amount: u64,
|
dev_sol_amount: u64,
|
||||||
creator: Pubkey,
|
creator: Pubkey,
|
||||||
is_mayhem_mode: bool,
|
is_mayhem_mode: bool,
|
||||||
|
is_cashback_coin: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let account = if bonding_curve != Pubkey::default() {
|
let account = if bonding_curve != Pubkey::default() {
|
||||||
bonding_curve
|
bonding_curve
|
||||||
@@ -89,10 +92,12 @@ impl BondingCurveAccount {
|
|||||||
complete: false,
|
complete: false,
|
||||||
creator: creator,
|
creator: creator,
|
||||||
is_mayhem_mode: is_mayhem_mode,
|
is_mayhem_mode: is_mayhem_mode,
|
||||||
is_cashback_coin: false,
|
is_cashback_coin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When building from event/parser data (e.g. sol-parser-sdk), pass the token's cashback flag
|
||||||
|
/// so that sell instructions include the correct remaining accounts. From RPC use `from_mint_by_rpc` instead.
|
||||||
pub fn from_trade(
|
pub fn from_trade(
|
||||||
bonding_curve: Pubkey,
|
bonding_curve: Pubkey,
|
||||||
mint: Pubkey,
|
mint: Pubkey,
|
||||||
@@ -102,6 +107,7 @@ impl BondingCurveAccount {
|
|||||||
real_token_reserves: u64,
|
real_token_reserves: u64,
|
||||||
real_sol_reserves: u64,
|
real_sol_reserves: u64,
|
||||||
is_mayhem_mode: bool,
|
is_mayhem_mode: bool,
|
||||||
|
is_cashback_coin: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let account = if bonding_curve != Pubkey::default() {
|
let account = if bonding_curve != Pubkey::default() {
|
||||||
bonding_curve
|
bonding_curve
|
||||||
@@ -119,7 +125,7 @@ impl BondingCurveAccount {
|
|||||||
complete: false,
|
complete: false,
|
||||||
creator: creator,
|
creator: creator,
|
||||||
is_mayhem_mode: is_mayhem_mode,
|
is_mayhem_mode: is_mayhem_mode,
|
||||||
is_cashback_coin: false,
|
is_cashback_coin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ impl PumpFunParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||||
|
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||||
pub fn from_dev_trade(
|
pub fn from_dev_trade(
|
||||||
mint: Pubkey,
|
mint: Pubkey,
|
||||||
token_amount: u64,
|
token_amount: u64,
|
||||||
@@ -118,6 +120,7 @@ impl PumpFunParams {
|
|||||||
close_token_account_when_sell: Option<bool>,
|
close_token_account_when_sell: Option<bool>,
|
||||||
fee_recipient: Pubkey,
|
fee_recipient: Pubkey,
|
||||||
token_program: Pubkey,
|
token_program: Pubkey,
|
||||||
|
is_cashback_coin: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
|
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
|
||||||
let bonding_curve_account = BondingCurveAccount::from_dev_trade(
|
let bonding_curve_account = BondingCurveAccount::from_dev_trade(
|
||||||
@@ -127,6 +130,7 @@ impl PumpFunParams {
|
|||||||
max_sol_cost,
|
max_sol_cost,
|
||||||
creator,
|
creator,
|
||||||
is_mayhem_mode,
|
is_mayhem_mode,
|
||||||
|
is_cashback_coin,
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
bonding_curve: Arc::new(bonding_curve_account),
|
bonding_curve: Arc::new(bonding_curve_account),
|
||||||
@@ -137,6 +141,8 @@ impl PumpFunParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||||
|
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||||
pub fn from_trade(
|
pub fn from_trade(
|
||||||
bonding_curve: Pubkey,
|
bonding_curve: Pubkey,
|
||||||
associated_bonding_curve: Pubkey,
|
associated_bonding_curve: Pubkey,
|
||||||
@@ -150,6 +156,7 @@ impl PumpFunParams {
|
|||||||
close_token_account_when_sell: Option<bool>,
|
close_token_account_when_sell: Option<bool>,
|
||||||
fee_recipient: Pubkey,
|
fee_recipient: Pubkey,
|
||||||
token_program: Pubkey,
|
token_program: Pubkey,
|
||||||
|
is_cashback_coin: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
|
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
|
||||||
let bonding_curve = BondingCurveAccount::from_trade(
|
let bonding_curve = BondingCurveAccount::from_trade(
|
||||||
@@ -161,6 +168,7 @@ impl PumpFunParams {
|
|||||||
real_token_reserves,
|
real_token_reserves,
|
||||||
real_sol_reserves,
|
real_sol_reserves,
|
||||||
is_mayhem_mode,
|
is_mayhem_mode,
|
||||||
|
is_cashback_coin,
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
bonding_curve: Arc::new(bonding_curve),
|
bonding_curve: Arc::new(bonding_curve),
|
||||||
|
|||||||
Reference in New Issue
Block a user