add yellowstone grpc
This commit is contained in:
Executable
+123
@@ -0,0 +1,123 @@
|
||||
use anyhow::anyhow;
|
||||
use base64::engine::general_purpose;
|
||||
use base64::Engine;
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use regex::Regex;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
use super::myerror::AppError;
|
||||
|
||||
pub const PROGRAM_DATA: &str = "Program data: ";
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
|
||||
pub struct TradeEvent {
|
||||
pub mint: Pubkey,
|
||||
pub sol_amount: u64,
|
||||
pub token_amount: u64,
|
||||
pub is_buy: bool,
|
||||
pub user: Pubkey,
|
||||
pub timestamp: u64,
|
||||
pub virtual_sol_reserves: u64,
|
||||
pub virtual_token_reserves: u64,
|
||||
pub real_sol_reserves: u64,
|
||||
pub real_token_reserves: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
|
||||
pub struct CompleteEvent {
|
||||
pub user: Pubkey,
|
||||
pub mint: Pubkey,
|
||||
pub bonding_curve: Pubkey,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
|
||||
pub struct SwapBaseInLog {
|
||||
pub log_type: u8,
|
||||
// input
|
||||
pub amount_in: u64,
|
||||
pub minimum_out: u64,
|
||||
pub direction: u64,
|
||||
// user info
|
||||
pub user_source: u64,
|
||||
// pool info
|
||||
pub pool_coin: u64,
|
||||
pub pool_pc: u64,
|
||||
// calc result
|
||||
pub out_amount: u64,
|
||||
}
|
||||
|
||||
pub trait EventTrait: Sized + std::fmt::Debug {
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError>;
|
||||
}
|
||||
|
||||
impl EventTrait for TradeEvent {
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
|
||||
TradeEvent::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventTrait for CompleteEvent {
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
|
||||
CompleteEvent::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventTrait for SwapBaseInLog {
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
|
||||
SwapBaseInLog::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PumpEvent {}
|
||||
|
||||
impl PumpEvent {
|
||||
pub fn parse_logs<T: EventTrait + Clone>(logs: &Vec<String>) -> Option<T> {
|
||||
let mut event: Option<T> = None;
|
||||
if !logs.is_empty() {
|
||||
let logs_iter = logs.iter().peekable();
|
||||
|
||||
for l in logs_iter.rev() {
|
||||
if let Some(log) = l.strip_prefix(PROGRAM_DATA) {
|
||||
let borsh_bytes = general_purpose::STANDARD.decode(log).unwrap();
|
||||
let slice: &[u8] = &borsh_bytes[8..];
|
||||
|
||||
if let Ok(e) = T::from_bytes(slice) {
|
||||
event = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RaydiumEvent {}
|
||||
|
||||
impl RaydiumEvent {
|
||||
pub fn parse_logs<T: EventTrait + Clone>(logs: &Vec<String>) -> Option<T> {
|
||||
let mut event: Option<T> = None;
|
||||
|
||||
if !logs.is_empty() {
|
||||
let logs_iter = logs.iter().peekable();
|
||||
|
||||
for l in logs_iter.rev() {
|
||||
let re = Regex::new(r"ray_log: (?P<base64>[A-Za-z0-9+/=]+)").unwrap();
|
||||
|
||||
if let Some(caps) = re.captures(l) {
|
||||
if let Some(base64) = caps.name("base64") {
|
||||
let bytes = general_purpose::STANDARD.decode(base64.as_str()).unwrap();
|
||||
|
||||
if let Ok(e) = T::from_bytes(&bytes) {
|
||||
event = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event
|
||||
}
|
||||
}
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DexInstruction {
|
||||
CreateToken(CreateTokenInfo),
|
||||
UserTrade(TradeInfo),
|
||||
BotTrade(TradeInfo),
|
||||
Other,
|
||||
}
|
||||
|
||||
// 添加新的数据结构
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct CreateTokenInfo {
|
||||
pub signature: String,
|
||||
pub name: String,
|
||||
pub symbol: String,
|
||||
pub uri: String,
|
||||
pub mint: String,
|
||||
pub bonding_curve: String,
|
||||
pub user: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct TradeInfo {
|
||||
pub signature: String,
|
||||
pub mint: String,
|
||||
pub bonding_curve: String,
|
||||
pub sol_amount: u64,
|
||||
pub token_amount: u64,
|
||||
pub is_buy: bool,
|
||||
pub user: String,
|
||||
pub timestamp: i64,
|
||||
pub virtual_sol_reserves: u64,
|
||||
pub virtual_token_reserves: u64,
|
||||
pub real_sol_reserves: u64,
|
||||
pub real_token_reserves: u64,
|
||||
}
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::instruction::logs_data::{CreateTokenInfo, TradeInfo};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum DexEvent {
|
||||
NewToken(CreateTokenInfo),
|
||||
NewUserTrade(TradeInfo),
|
||||
NewBotTrade(TradeInfo),
|
||||
Error(String),
|
||||
}
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
use crate::instruction::logs_data::DexInstruction;
|
||||
use crate::instruction::logs_parser::{parse_create_token_data, parse_trade_data};
|
||||
use crate::error::ClientResult;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
pub struct LogFilter;
|
||||
|
||||
impl LogFilter {
|
||||
const PROGRAM_ID: &'static str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
|
||||
|
||||
/// Parse transaction logs and return instruction type and data
|
||||
pub fn parse_instruction(logs: &[String], bot_wallet: Option<Pubkey>) -> ClientResult<Vec<DexInstruction>> {
|
||||
let mut current_instruction = None;
|
||||
let mut program_data = String::new();
|
||||
let mut invoke_depth = 0;
|
||||
let mut last_data_len = 0;
|
||||
let mut instructions = Vec::new();
|
||||
for log in logs {
|
||||
// Check program invocation
|
||||
if log.contains(&format!("Program {} invoke", Self::PROGRAM_ID)) {
|
||||
invoke_depth += 1;
|
||||
if invoke_depth == 1 { // Only reset state at top level call
|
||||
current_instruction = None;
|
||||
program_data.clear();
|
||||
last_data_len = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if not in our program
|
||||
if invoke_depth == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Identify instruction type (only at top level)
|
||||
if invoke_depth == 1 && log.contains("Program log: Instruction:") {
|
||||
if log.contains("Create") {
|
||||
current_instruction = Some("create");
|
||||
} else if log.contains("Buy") || log.contains("Sell") {
|
||||
current_instruction = Some("trade");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect Program data
|
||||
if log.starts_with("Program data: ") {
|
||||
let data = log.trim_start_matches("Program data: ");
|
||||
if data.len() > last_data_len {
|
||||
program_data = data.to_string();
|
||||
last_data_len = data.len();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if program ends
|
||||
if log.contains(&format!("Program {} success", Self::PROGRAM_ID)) {
|
||||
invoke_depth -= 1;
|
||||
if invoke_depth == 0 { // Only process data when top level program ends
|
||||
if let Some(instruction_type) = current_instruction {
|
||||
if !program_data.is_empty() {
|
||||
match instruction_type {
|
||||
"create" => {
|
||||
if let Ok(token_info) = parse_create_token_data(&program_data) {
|
||||
instructions.push(DexInstruction::CreateToken(token_info));
|
||||
}
|
||||
},
|
||||
"trade" => {
|
||||
if let Ok(trade_info) = parse_trade_data(&program_data) {
|
||||
if let Some(bot_wallet_pubkey) = bot_wallet {
|
||||
if trade_info.user == bot_wallet_pubkey.to_string() {
|
||||
instructions.push(DexInstruction::BotTrade(trade_info));
|
||||
} else {
|
||||
instructions.push(DexInstruction::UserTrade(trade_info));
|
||||
}
|
||||
} else {
|
||||
instructions.push(DexInstruction::UserTrade(trade_info));
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
Executable
+173
@@ -0,0 +1,173 @@
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||
|
||||
use crate::error::{ClientError, ClientResult};
|
||||
use crate::instruction::{
|
||||
logs_data::{DexInstruction, CreateTokenInfo, TradeInfo},
|
||||
logs_filters::LogFilter
|
||||
};
|
||||
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
pub async fn process_logs<F>(
|
||||
signature: &str,
|
||||
logs: Vec<String>,
|
||||
callback: F,
|
||||
payer: Option<Pubkey>,
|
||||
) -> ClientResult<()>
|
||||
where
|
||||
F: Fn(&str, DexInstruction) + Send + Sync,
|
||||
{
|
||||
let instructions = LogFilter::parse_instruction(&logs, payer)?;
|
||||
for instruction in instructions {
|
||||
callback(signature, instruction);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Add parsing function
|
||||
pub fn parse_create_token_data(data: &str) -> ClientResult<CreateTokenInfo> {
|
||||
// First do base64 decoding
|
||||
let decoded = BASE64.decode(data)
|
||||
.map_err(|e| ClientError::Other(format!("Failed to decode base64: {}", e)))?;
|
||||
|
||||
// Skip prefix bytes (if any)
|
||||
let mut cursor = if decoded.len() > 8 { 8 } else { 0 };
|
||||
|
||||
// Read name length and name
|
||||
if cursor + 4 > decoded.len() {
|
||||
return Err(ClientError::Other("Data too short for name length".to_string()));
|
||||
}
|
||||
let name_len = read_u32(&decoded[cursor..]) as usize;
|
||||
cursor += 4;
|
||||
|
||||
if cursor + name_len > decoded.len() {
|
||||
return Err(ClientError::Other(format!("Data too short for name: need {} bytes", name_len)));
|
||||
}
|
||||
let name = String::from_utf8(decoded[cursor..cursor + name_len].to_vec())
|
||||
.map_err(|e| ClientError::Other(format!("Invalid UTF-8 in name: {}", e)))?;
|
||||
cursor += name_len;
|
||||
|
||||
// Read symbol length and symbol
|
||||
if cursor + 4 > decoded.len() {
|
||||
return Err(ClientError::Other("Data too short for symbol length".to_string()));
|
||||
}
|
||||
let symbol_len = read_u32(&decoded[cursor..]) as usize;
|
||||
cursor += 4;
|
||||
|
||||
if cursor + symbol_len > decoded.len() {
|
||||
return Err(ClientError::Other(format!("Data too short for symbol: need {} bytes", symbol_len)));
|
||||
}
|
||||
let symbol = String::from_utf8(decoded[cursor..cursor + symbol_len].to_vec())
|
||||
.map_err(|e| ClientError::Other(format!("Invalid UTF-8 in symbol: {}", e)))?;
|
||||
cursor += symbol_len;
|
||||
|
||||
// Read URI length and URI
|
||||
if cursor + 4 > decoded.len() {
|
||||
return Err(ClientError::Other("Data too short for URI length".to_string()));
|
||||
}
|
||||
let uri_len = read_u32(&decoded[cursor..]) as usize;
|
||||
cursor += 4;
|
||||
|
||||
if cursor + uri_len > decoded.len() {
|
||||
return Err(ClientError::Other(format!("Data too short for URI: need {} bytes", uri_len)));
|
||||
}
|
||||
let uri = String::from_utf8(decoded[cursor..cursor + uri_len].to_vec())
|
||||
.map_err(|e| ClientError::Other(format!("Invalid UTF-8 in uri: {}", e)))?;
|
||||
cursor += uri_len;
|
||||
|
||||
// Make sure there is enough data to read public keys
|
||||
if cursor + 32 * 3 > decoded.len() {
|
||||
return Err(ClientError::Other("Data too short for public keys".to_string()));
|
||||
}
|
||||
|
||||
// Parse Mint Public Key
|
||||
let mint = bs58::encode(&decoded[cursor..cursor+32]).into_string();
|
||||
cursor += 32;
|
||||
|
||||
// Parse Bonding Curve Public Key
|
||||
let bonding_curve = bs58::encode(&decoded[cursor..cursor+32]).into_string();
|
||||
cursor += 32;
|
||||
|
||||
// Parse User Public Key
|
||||
let user = bs58::encode(&decoded[cursor..cursor+32]).into_string();
|
||||
|
||||
Ok(CreateTokenInfo {
|
||||
signature: String::new(),
|
||||
name,
|
||||
symbol,
|
||||
uri,
|
||||
mint,
|
||||
bonding_curve,
|
||||
user,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_u32(data: &[u8]) -> u32 {
|
||||
let mut bytes = [0u8; 4];
|
||||
bytes.copy_from_slice(&data[..4]);
|
||||
u32::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn parse_trade_data(data: &str) -> ClientResult<TradeInfo> {
|
||||
let engine = base64::engine::general_purpose::STANDARD;
|
||||
let decoded = engine.decode(data).map_err(|e|
|
||||
ClientError::Parse(
|
||||
"Failed to decode base64".to_string(),
|
||||
e.to_string()
|
||||
)
|
||||
)?;
|
||||
|
||||
let mut cursor = 8; // Skip prefix
|
||||
|
||||
// 1. Mint (32 bytes)
|
||||
let mint = bs58::encode(&decoded[cursor..cursor + 32]).into_string();
|
||||
cursor += 32;
|
||||
|
||||
// 2. Sol Amount (8 bytes)
|
||||
let sol_amount = u64::from_le_bytes(decoded[cursor..cursor + 8].try_into().unwrap());
|
||||
cursor += 8;
|
||||
|
||||
// 3. Token Amount (8 bytes)
|
||||
let token_amount = u64::from_le_bytes(decoded[cursor..cursor + 8].try_into().unwrap());
|
||||
cursor += 8;
|
||||
|
||||
// 4. Is Buy (1 byte)
|
||||
let is_buy = decoded[cursor] != 0;
|
||||
cursor += 1;
|
||||
|
||||
// 5. User (32 bytes)
|
||||
let user = bs58::encode(&decoded[cursor..cursor + 32]).into_string();
|
||||
cursor += 32;
|
||||
|
||||
// 6. Timestamp (8 bytes)
|
||||
let timestamp = i64::from_le_bytes(decoded[cursor..cursor + 8].try_into().unwrap());
|
||||
cursor += 8;
|
||||
|
||||
// 7. Virtual Sol Reserves (8 bytes)
|
||||
let virtual_sol_reserves = u64::from_le_bytes(decoded[cursor..cursor + 8].try_into().unwrap());
|
||||
cursor += 8;
|
||||
|
||||
// 8. Virtual Token Reserves (8 bytes)
|
||||
let virtual_token_reserves = u64::from_le_bytes(decoded[cursor..cursor + 8].try_into().unwrap());
|
||||
cursor += 8;
|
||||
|
||||
let real_sol_reserves = u64::from_le_bytes(decoded[cursor..cursor + 8].try_into().unwrap());
|
||||
cursor += 8;
|
||||
|
||||
let real_token_reserves = u64::from_le_bytes(decoded[cursor..cursor + 8].try_into().unwrap());
|
||||
|
||||
Ok(TradeInfo {
|
||||
signature: String::new(),
|
||||
mint,
|
||||
bonding_curve: String::new(),
|
||||
sol_amount,
|
||||
token_amount,
|
||||
is_buy,
|
||||
user,
|
||||
timestamp,
|
||||
virtual_sol_reserves,
|
||||
virtual_token_reserves,
|
||||
real_sol_reserves,
|
||||
real_token_reserves,
|
||||
})
|
||||
}
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
use solana_client::{
|
||||
nonblocking::pubsub_client::PubsubClient,
|
||||
rpc_config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter}
|
||||
};
|
||||
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::task::JoinHandle;
|
||||
use futures::StreamExt;
|
||||
use crate::{constants, instruction::{
|
||||
logs_data::DexInstruction, logs_events::DexEvent, logs_filters::LogFilter
|
||||
}};
|
||||
|
||||
/// Subscription handle containing task and unsubscribe logic
|
||||
pub struct SubscriptionHandle {
|
||||
pub task: JoinHandle<()>,
|
||||
pub unsub_fn: Box<dyn Fn() + Send>,
|
||||
}
|
||||
|
||||
impl SubscriptionHandle {
|
||||
pub async fn shutdown(self) {
|
||||
(self.unsub_fn)();
|
||||
self.task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_pubsub_client(ws_url: &str) -> PubsubClient {
|
||||
PubsubClient::new(ws_url).await.unwrap()
|
||||
}
|
||||
|
||||
/// 启动订阅
|
||||
pub async fn tokens_subscription<F>(
|
||||
ws_url: &str,
|
||||
commitment: CommitmentConfig,
|
||||
callback: F,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
) -> Result<SubscriptionHandle, Box<dyn std::error::Error>>
|
||||
where
|
||||
F: Fn(DexEvent) + Send + Sync + 'static,
|
||||
{
|
||||
let program_address = constants::accounts::PUMPFUN.to_string();
|
||||
let logs_filter = RpcTransactionLogsFilter::Mentions(vec![program_address]);
|
||||
|
||||
let logs_config = RpcTransactionLogsConfig {
|
||||
commitment: Some(commitment),
|
||||
};
|
||||
|
||||
// Create PubsubClient
|
||||
let sub_client = Arc::new(PubsubClient::new(ws_url).await.unwrap());
|
||||
|
||||
let sub_client_clone = Arc::clone(&sub_client);
|
||||
|
||||
// Create channel for unsubscribe
|
||||
let (unsub_tx, _) = mpsc::channel(1);
|
||||
|
||||
// Start subscription task
|
||||
let task = tokio::spawn(async move {
|
||||
let (mut stream, _) = sub_client_clone.logs_subscribe(logs_filter, logs_config).await.unwrap();
|
||||
|
||||
loop {
|
||||
let msg = stream.next().await;
|
||||
match msg {
|
||||
Some(msg) => {
|
||||
if let Some(_err) = msg.value.err {
|
||||
continue;
|
||||
}
|
||||
|
||||
let instructions = LogFilter::parse_instruction(&msg.value.logs, bot_wallet).unwrap();
|
||||
for instruction in instructions {
|
||||
match instruction {
|
||||
DexInstruction::CreateToken(token_info) => {
|
||||
callback(DexEvent::NewToken(token_info));
|
||||
}
|
||||
DexInstruction::UserTrade(trade_info) => {
|
||||
callback(DexEvent::NewUserTrade(trade_info));
|
||||
}
|
||||
DexInstruction::BotTrade(trade_info) => {
|
||||
callback(DexEvent::NewBotTrade(trade_info));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("Token subscription stream ended");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Return subscription handle and unsubscribe logic
|
||||
Ok(SubscriptionHandle {
|
||||
task,
|
||||
unsub_fn: Box::new(move || {
|
||||
let _ = unsub_tx.try_send(());
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn stop_subscription(handle: SubscriptionHandle) {
|
||||
handle.shutdown().await;
|
||||
}
|
||||
Executable
+207
@@ -0,0 +1,207 @@
|
||||
//! Instructions for interacting with the Pump.fun program.
|
||||
//!
|
||||
//! This module contains instruction builders for creating Solana instructions to interact with the
|
||||
//! Pump.fun program. Each function takes the required accounts and instruction data and returns a
|
||||
//! properly formatted Solana instruction.
|
||||
//!
|
||||
//! # Instructions
|
||||
//!
|
||||
//! - `create`: Instruction to create a new token with an associated bonding curve.
|
||||
//! - `buy`: Instruction to buy tokens from a bonding curve by providing SOL.
|
||||
//! - `sell`: Instruction to sell tokens back to the bonding curve in exchange for SOL.
|
||||
|
||||
pub mod logs_data;
|
||||
pub mod logs_parser;
|
||||
pub mod logs_filters;
|
||||
pub mod logs_events;
|
||||
pub mod logs_subscribe;
|
||||
|
||||
pub use logs_data::*;
|
||||
pub use logs_parser::*;
|
||||
pub use logs_filters::*;
|
||||
pub use logs_events::*;
|
||||
pub use logs_subscribe::*;
|
||||
|
||||
use crate::{constants, PumpFun};
|
||||
use spl_associated_token_account::get_associated_token_address;
|
||||
|
||||
use solana_sdk::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
pubkey::Pubkey,
|
||||
signature::Keypair,
|
||||
signer::Signer,
|
||||
};
|
||||
|
||||
pub struct Create {
|
||||
pub _name: String,
|
||||
pub _symbol: String,
|
||||
pub _uri: String,
|
||||
}
|
||||
|
||||
impl Create {
|
||||
pub fn data(&self) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(8 + 8 + 8);
|
||||
data.extend_from_slice(&[24, 30, 200, 40, 5, 28, 7, 119]); // discriminator
|
||||
data.extend_from_slice(&self._name.as_bytes());
|
||||
data.extend_from_slice(&self._symbol.as_bytes());
|
||||
data.extend_from_slice(&self._uri.as_bytes());
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Buy {
|
||||
pub _amount: u64,
|
||||
pub _max_sol_cost: u64,
|
||||
}
|
||||
|
||||
impl Buy {
|
||||
pub fn data(&self) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(8 + 8 + 8);
|
||||
data.extend_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // discriminator
|
||||
data.extend_from_slice(&self._amount.to_le_bytes());
|
||||
data.extend_from_slice(&self._max_sol_cost.to_le_bytes());
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sell {
|
||||
pub _amount: u64,
|
||||
pub _min_sol_output: u64,
|
||||
}
|
||||
|
||||
impl Sell {
|
||||
pub fn data(&self) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(8 + 8 + 8);
|
||||
data.extend_from_slice(&[51, 230, 133, 164, 1, 127, 131, 173]); // discriminator
|
||||
data.extend_from_slice(&self._amount.to_le_bytes());
|
||||
data.extend_from_slice(&self._min_sol_output.to_le_bytes());
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Creates an instruction to create a new token with bonding curve
|
||||
///
|
||||
/// Creates a new SPL token with an associated bonding curve that determines its price.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `payer` - Keypair that will pay for account creation and transaction fees
|
||||
/// * `mint` - Keypair for the new token mint account that will be created
|
||||
/// * `args` - Create instruction data containing token name, symbol and metadata URI
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Solana instruction that when executed will create the token and its accounts
|
||||
pub fn create(payer: &Keypair, mint: &Keypair, args: Create) -> Instruction {
|
||||
let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(&mint.pubkey()).unwrap();
|
||||
Instruction::new_with_bytes(
|
||||
constants::accounts::PUMPFUN,
|
||||
&args.data(),
|
||||
vec![
|
||||
AccountMeta::new(mint.pubkey(), true),
|
||||
AccountMeta::new(PumpFun::get_mint_authority_pda(), false),
|
||||
AccountMeta::new(bonding_curve, false),
|
||||
AccountMeta::new(
|
||||
get_associated_token_address(&bonding_curve, &mint.pubkey()),
|
||||
false,
|
||||
),
|
||||
AccountMeta::new_readonly(PumpFun::get_global_pda(), false),
|
||||
AccountMeta::new_readonly(constants::accounts::MPL_TOKEN_METADATA, false),
|
||||
AccountMeta::new(PumpFun::get_metadata_pda(&mint.pubkey()), false),
|
||||
AccountMeta::new(payer.pubkey(), true),
|
||||
AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::ASSOCIATED_TOKEN_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::RENT, false),
|
||||
AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false),
|
||||
AccountMeta::new_readonly(constants::accounts::PUMPFUN, false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates an instruction to buy tokens from a bonding curve
|
||||
///
|
||||
/// Buys tokens by providing SOL. The amount of tokens received is calculated based on
|
||||
/// the bonding curve formula. A portion of the SOL is taken as a fee and sent to the
|
||||
/// fee recipient account.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `payer` - Keypair that will provide the SOL to buy tokens
|
||||
/// * `mint` - Public key of the token mint to buy
|
||||
/// * `fee_recipient` - Public key of the account that will receive the transaction fee
|
||||
/// * `args` - Buy instruction data containing the SOL amount and maximum acceptable token price
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Solana instruction that when executed will buy tokens from the bonding curve
|
||||
pub fn buy(
|
||||
payer: &Keypair,
|
||||
mint: &Pubkey,
|
||||
fee_recipient: &Pubkey,
|
||||
args: Buy,
|
||||
) -> Instruction {
|
||||
let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap();
|
||||
Instruction::new_with_bytes(
|
||||
constants::accounts::PUMPFUN,
|
||||
&args.data(),
|
||||
vec![
|
||||
AccountMeta::new_readonly(PumpFun::get_global_pda(), false),
|
||||
AccountMeta::new(*fee_recipient, false),
|
||||
AccountMeta::new_readonly(*mint, false),
|
||||
AccountMeta::new(bonding_curve, false),
|
||||
AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false),
|
||||
AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
|
||||
AccountMeta::new(payer.pubkey(), true),
|
||||
AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::RENT, false),
|
||||
AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false),
|
||||
AccountMeta::new_readonly(constants::accounts::PUMPFUN, false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates an instruction to sell tokens back to a bonding curve
|
||||
///
|
||||
/// Sells tokens back to the bonding curve in exchange for SOL. The amount of SOL received
|
||||
/// is calculated based on the bonding curve formula. A portion of the SOL is taken as
|
||||
/// a fee and sent to the fee recipient account.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `payer` - Keypair that owns the tokens to sell
|
||||
/// * `mint` - Public key of the token mint to sell
|
||||
/// * `fee_recipient` - Public key of the account that will receive the transaction fee
|
||||
/// * `args` - Sell instruction data containing token amount and minimum acceptable SOL output
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Solana instruction that when executed will sell tokens to the bonding curve
|
||||
pub fn sell(
|
||||
payer: &Keypair,
|
||||
mint: &Pubkey,
|
||||
fee_recipient: &Pubkey,
|
||||
args: Sell,
|
||||
) -> Instruction {
|
||||
let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap();
|
||||
Instruction::new_with_bytes(
|
||||
constants::accounts::PUMPFUN,
|
||||
&args.data(),
|
||||
vec![
|
||||
AccountMeta::new_readonly(PumpFun::get_global_pda(), false),
|
||||
AccountMeta::new(*fee_recipient, false),
|
||||
AccountMeta::new_readonly(*mint, false),
|
||||
AccountMeta::new(bonding_curve, false),
|
||||
AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false),
|
||||
AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
|
||||
AccountMeta::new(payer.pubkey(), true),
|
||||
AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::ASSOCIATED_TOKEN_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false),
|
||||
AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false),
|
||||
AccountMeta::new_readonly(constants::accounts::PUMPFUN, false),
|
||||
],
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user