0.7.24-pre.2

This commit is contained in:
2026-05-03 07:03:23 +02:00
parent d10a2270d8
commit d44171ca6f
10 changed files with 906 additions and 126 deletions

View File

@@ -5,6 +5,9 @@
/// Pump.fun program id.
pub const KB_PUMP_FUN_PROGRAM_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
const KB_PUMP_FUN_BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
const KB_PUMP_FUN_SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
/// Decoded Pump.fun `create_v2` token event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbPumpFunCreateV2TokenDecoded {
@@ -28,11 +31,46 @@ pub struct KbPumpFunCreateV2TokenDecoded {
pub payload_json: serde_json::Value,
}
/// Decoded Pump.fun bonding-curve trade event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbPumpFunTradeDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
pub instruction_id: i64,
/// Transaction signature.
pub signature: std::string::String,
/// Program id.
pub program_id: std::string::String,
/// Trade side.
pub trade_side: crate::KbSwapTradeSide,
/// Token mint.
pub mint: std::option::Option<std::string::String>,
/// Bonding curve account.
pub bonding_curve: std::option::Option<std::string::String>,
/// Associated bonding curve token account.
pub associated_bonding_curve: std::option::Option<std::string::String>,
/// User token account.
pub associated_user: std::option::Option<std::string::String>,
/// User wallet account.
pub user: std::option::Option<std::string::String>,
/// Decoded instruction amount, when available.
pub amount_raw: std::option::Option<std::string::String>,
/// Decoded SOL limit/threshold argument, when available.
pub sol_limit_raw: std::option::Option<std::string::String>,
/// Decoded payload.
pub payload_json: serde_json::Value,
}
/// Decoded Pump.fun event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbPumpFunDecodedEvent {
/// `create_v2` token creation.
CreateV2Token(KbPumpFunCreateV2TokenDecoded),
/// Buy trade.
BuyTrade(KbPumpFunTradeDecoded),
/// Sell trade.
SellTrade(KbPumpFunTradeDecoded),
}
/// Pump.fun decoder.
@@ -61,6 +99,9 @@ impl KbPumpFunDecoder {
)));
}
};
if transaction.err_json.is_some() {
return Ok(std::vec::Vec::new());
}
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
let transaction_json = match transaction_json_result {
@@ -88,9 +129,6 @@ impl KbPumpFunDecoder {
if program_id.as_str() != crate::KB_PUMP_FUN_PROGRAM_ID {
continue;
}
if !has_create_v2_log {
continue;
}
let instruction_id_option = instruction.id;
let instruction_id = match instruction_id_option {
Some(instruction_id) => instruction_id,
@@ -101,6 +139,83 @@ impl KbPumpFunDecoder {
Ok(accounts) => accounts,
Err(error) => return Err(error),
};
let instruction_data =
kb_decode_optional_instruction_data(instruction.data_json.as_ref());
let is_buy = kb_instruction_data_starts_with(
instruction_data.as_deref(),
&KB_PUMP_FUN_BUY_DISCRIMINATOR,
);
let is_sell = kb_instruction_data_starts_with(
instruction_data.as_deref(),
&KB_PUMP_FUN_SELL_DISCRIMINATOR,
);
if is_buy || is_sell {
if accounts.len() < 7 {
continue;
}
let amount_raw = kb_extract_u64_argument(instruction_data.as_deref(), 8);
let sol_limit_raw = kb_extract_u64_argument(instruction_data.as_deref(), 16);
let mint = kb_extract_account(&accounts, 2);
let bonding_curve = kb_extract_account(&accounts, 3);
let associated_bonding_curve = kb_extract_account(&accounts, 4);
let associated_user = kb_extract_account(&accounts, 5);
let user = kb_extract_account(&accounts, 6);
let fee_recipient = kb_extract_account(&accounts, 1);
let token_program = kb_extract_account(&accounts, 8);
let creator_vault = kb_extract_account(&accounts, 9);
let payload_json = serde_json::json!({
"decoder": "pump_fun",
"signature": transaction.signature,
"instructionId": instruction_id,
"instructionIndex": instruction.instruction_index,
"accounts": accounts,
"logMessages": log_messages,
"eventKind": if is_buy { "buy" } else { "sell" },
"poolAccount": bonding_curve,
"tokenAMint": mint,
"tokenBMint": crate::WSOL_MINT_ID,
"bondingCurve": bonding_curve,
"associatedBondingCurve": associated_bonding_curve,
"associatedUser": associated_user,
"user": user,
"feeRecipient": fee_recipient,
"tokenProgram": token_program,
"creatorVault": creator_vault,
"poolBaseTokenAccount": associated_bonding_curve,
"poolQuoteNativeAccount": bonding_curve,
"amountRaw": amount_raw,
"solLimitRaw": sol_limit_raw,
"tradeSide": if is_buy { "BuyBase" } else { "SellBase" }
});
let event = crate::KbPumpFunTradeDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.clone(),
trade_side: if is_buy {
crate::KbSwapTradeSide::BuyBase
} else {
crate::KbSwapTradeSide::SellBase
},
mint,
bonding_curve,
associated_bonding_curve,
associated_user,
user,
amount_raw,
sol_limit_raw,
payload_json,
};
if is_buy {
decoded_events.push(crate::KbPumpFunDecodedEvent::BuyTrade(event));
} else {
decoded_events.push(crate::KbPumpFunDecodedEvent::SellTrade(event));
}
continue;
}
if !has_create_v2_log {
continue;
}
if accounts.len() < 6 {
continue;
}
@@ -139,6 +254,59 @@ impl KbPumpFunDecoder {
}
}
fn kb_decode_optional_instruction_data(
data_json: std::option::Option<&std::string::String>,
) -> std::option::Option<std::vec::Vec<u8>> {
let data_json = match data_json {
Some(data_json) => data_json,
None => return None,
};
let parsed_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
let encoded = match parsed_result {
Ok(parsed) => match parsed.as_str() {
Some(text) => text.to_string(),
None => data_json.clone(),
},
Err(_) => data_json.clone(),
};
let decode_result = bs58::decode(encoded.as_str()).into_vec();
match decode_result {
Ok(decoded) => Some(decoded),
Err(_) => None,
}
}
fn kb_instruction_data_starts_with(
instruction_data: std::option::Option<&[u8]>,
discriminator: &[u8; 8],
) -> bool {
let instruction_data = match instruction_data {
Some(instruction_data) => instruction_data,
None => return false,
};
if instruction_data.len() < discriminator.len() {
return false;
}
&instruction_data[0..discriminator.len()] == discriminator
}
fn kb_extract_u64_argument(
instruction_data: std::option::Option<&[u8]>,
offset: usize,
) -> std::option::Option<std::string::String> {
let instruction_data = match instruction_data {
Some(instruction_data) => instruction_data,
None => return None,
};
let end = offset + 8;
if instruction_data.len() < end {
return None;
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&instruction_data[offset..end]);
Some(u64::from_le_bytes(bytes).to_string())
}
fn kb_extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
@@ -167,10 +335,7 @@ fn kb_extract_log_messages(
messages
}
fn kb_log_messages_contain_keyword(
log_messages: &[std::string::String],
keyword: &str,
) -> bool {
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_log_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_log_text(log_message.as_str());
@@ -306,6 +471,12 @@ mod tests {
);
assert_eq!(event.creator, Some("Creator111".to_string()));
}
crate::KbPumpFunDecodedEvent::BuyTrade(_) => {
panic!("unexpected pump_fun buy trade event");
}
crate::KbPumpFunDecodedEvent::SellTrade(_) => {
panic!("unexpected pump_fun sell trade event");
}
}
}