0.7.54
This commit is contained in:
@@ -62,6 +62,7 @@ pub use phoenix_v1::PhoenixV1AuditDecoded;
|
||||
pub use phoenix_v1::PhoenixV1DecodedEvent;
|
||||
pub use phoenix_v1::PhoenixV1Decoder;
|
||||
pub use pump_fun::PumpFunCreateV2TokenDecoded;
|
||||
pub use pump_fun::PumpFunInstructionAuditDecoded;
|
||||
pub use pump_fun::PumpFunDecodedEvent;
|
||||
pub use pump_fun::PumpFunDecoder;
|
||||
pub use pump_fun::PumpFunTradeDecoded;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1627,9 +1627,85 @@ impl DexDecodeService {
|
||||
)
|
||||
.await;
|
||||
},
|
||||
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||
let pool_account = Self::pump_fun_payload_string(
|
||||
&event.payload_json,
|
||||
&["poolAccount", "bondingCurve", "bonding_curve", "pool"],
|
||||
);
|
||||
let token_a_mint = Self::pump_fun_payload_string(
|
||||
&event.payload_json,
|
||||
&["tokenAMint", "token_a_mint", "tokenMint", "token_mint", "mint"],
|
||||
);
|
||||
let token_b_mint = match Self::pump_fun_payload_string(
|
||||
&event.payload_json,
|
||||
&["tokenBMint", "token_b_mint", "quoteMint", "quote_mint"],
|
||||
) {
|
||||
Some(token_b_mint) => Some(token_b_mint),
|
||||
None => {
|
||||
if token_a_mint.is_some() {
|
||||
Some(crate::WSOL_MINT_ID.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
let lp_mint = Self::pump_fun_payload_string(
|
||||
&event.payload_json,
|
||||
&[
|
||||
"lpMint",
|
||||
"lp_mint",
|
||||
"associatedBondingCurve",
|
||||
"associated_bonding_curve",
|
||||
"poolBaseTokenAccount",
|
||||
],
|
||||
);
|
||||
return self
|
||||
.materialize_named_dex_event(
|
||||
transaction,
|
||||
event.transaction_id,
|
||||
event.instruction_id,
|
||||
"pump_fun",
|
||||
event.program_id.clone(),
|
||||
event.event_kind.as_str(),
|
||||
pool_account,
|
||||
None,
|
||||
token_a_mint,
|
||||
token_b_mint,
|
||||
lp_mint,
|
||||
event.payload_json.clone(),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn pump_fun_payload_string(
|
||||
payload: &serde_json::Value,
|
||||
keys: &[&str],
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if let Some(object) = payload.as_object() {
|
||||
for key in keys {
|
||||
let value = object.get(*key);
|
||||
if let Some(value) = value {
|
||||
if let Some(text) = value.as_str() {
|
||||
if !text.trim().is_empty() {
|
||||
return Some(text.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let decoded_arguments = object.get("decodedArguments");
|
||||
if let Some(decoded_arguments) = decoded_arguments {
|
||||
let nested = Self::pump_fun_payload_string(decoded_arguments, keys);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
async fn persist_pump_fun_trade_event(
|
||||
&self,
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
@@ -2266,8 +2342,23 @@ impl DexDecodeService {
|
||||
Ok(decoded_events) => decoded_events,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let decoded_events = pump_fun_enrich_trade_events_with_instruction_context(decoded_events);
|
||||
let mut persisted = std::vec::Vec::new();
|
||||
for decoded_event in &decoded_events {
|
||||
if !pump_fun_decoded_event_is_trade_event(decoded_event) {
|
||||
continue;
|
||||
}
|
||||
let persist_result = self.persist_pump_fun_event(transaction, decoded_event).await;
|
||||
let persisted_event = match persist_result {
|
||||
Ok(persisted_event) => persisted_event,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
persisted.push(persisted_event);
|
||||
}
|
||||
for decoded_event in &decoded_events {
|
||||
if pump_fun_decoded_event_is_trade_event(decoded_event) {
|
||||
continue;
|
||||
}
|
||||
let persist_result = self.persist_pump_fun_event(transaction, decoded_event).await;
|
||||
let persisted_event = match persist_result {
|
||||
Ok(persisted_event) => persisted_event,
|
||||
@@ -4313,6 +4404,310 @@ fn dex_decode_extract_first_amount_string(
|
||||
return dex_decode_extract_first_number_as_string(value, candidate_keys);
|
||||
}
|
||||
|
||||
|
||||
fn pump_fun_enrich_trade_events_with_instruction_context(
|
||||
decoded_events: std::vec::Vec<crate::PumpFunDecodedEvent>,
|
||||
) -> std::vec::Vec<crate::PumpFunDecodedEvent> {
|
||||
let mut enriched_events = std::vec::Vec::new();
|
||||
for decoded_event in &decoded_events {
|
||||
let enriched_event = match decoded_event {
|
||||
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||
if event.event_kind.as_str() == "pump_fun.trade_event" {
|
||||
let mut enriched_event = event.clone();
|
||||
pump_fun_merge_matching_instruction_context_into_trade_event(
|
||||
&decoded_events,
|
||||
&mut enriched_event,
|
||||
);
|
||||
pump_fun_mark_trade_event_duplicate_when_direct_instruction_exists(
|
||||
&decoded_events,
|
||||
&mut enriched_event,
|
||||
);
|
||||
crate::PumpFunDecodedEvent::InstructionAudit(enriched_event)
|
||||
} else {
|
||||
decoded_event.clone()
|
||||
}
|
||||
},
|
||||
_ => decoded_event.clone(),
|
||||
};
|
||||
enriched_events.push(enriched_event);
|
||||
}
|
||||
return enriched_events;
|
||||
}
|
||||
|
||||
fn pump_fun_merge_matching_instruction_context_into_trade_event(
|
||||
decoded_events: &[crate::PumpFunDecodedEvent],
|
||||
trade_event: &mut crate::PumpFunInstructionAuditDecoded,
|
||||
) {
|
||||
let trade_payload = trade_event.payload_json.clone();
|
||||
let trade_instruction_id = dex_decode_extract_first_i64(
|
||||
&trade_payload,
|
||||
&["instructionId", "instruction_id"],
|
||||
);
|
||||
let trade_mint = dex_decode_extract_first_string(
|
||||
&trade_payload,
|
||||
&["mint", "tokenMint", "tokenAMint"],
|
||||
);
|
||||
let trade_actor = dex_decode_extract_first_string(
|
||||
&trade_payload,
|
||||
&["user", "actorWallet", "userWallet"],
|
||||
);
|
||||
for sibling in decoded_events {
|
||||
let sibling_event = match sibling {
|
||||
crate::PumpFunDecodedEvent::InstructionAudit(sibling_event) => sibling_event,
|
||||
_ => continue,
|
||||
};
|
||||
if sibling_event.event_kind.as_str() == "pump_fun.trade_event" {
|
||||
continue;
|
||||
}
|
||||
if !pump_fun_instruction_context_can_back_trade_event(sibling_event.event_kind.as_str()) {
|
||||
continue;
|
||||
}
|
||||
let sibling_instruction_id = Some(sibling_event.instruction_id);
|
||||
if trade_instruction_id.is_some()
|
||||
&& sibling_instruction_id.is_some()
|
||||
&& trade_instruction_id != sibling_instruction_id
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let sibling_mint = dex_decode_extract_first_string(
|
||||
&sibling_event.payload_json,
|
||||
&["mint", "tokenMint", "tokenAMint"],
|
||||
);
|
||||
if !dex_decode_optional_strings_match(trade_mint.as_deref(), sibling_mint.as_deref()) {
|
||||
continue;
|
||||
}
|
||||
let sibling_actor = dex_decode_extract_first_string(
|
||||
&sibling_event.payload_json,
|
||||
&["user", "actorWallet", "userWallet"],
|
||||
);
|
||||
if !dex_decode_optional_strings_match(trade_actor.as_deref(), sibling_actor.as_deref()) {
|
||||
continue;
|
||||
}
|
||||
pump_fun_merge_instruction_context_payload(
|
||||
&sibling_event.payload_json,
|
||||
&mut trade_event.payload_json,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fun_instruction_context_can_back_trade_event(event_kind: &str) -> bool {
|
||||
match event_kind {
|
||||
"pump_fun.buy_exact_quote_in_v2" => return true,
|
||||
"pump_fun.buy_v2" => return true,
|
||||
"pump_fun.sell_v2" => return true,
|
||||
"pump_fun.buy_exact_sol_in" => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fun_mark_trade_event_duplicate_when_direct_instruction_exists(
|
||||
decoded_events: &[crate::PumpFunDecodedEvent],
|
||||
trade_event: &mut crate::PumpFunInstructionAuditDecoded,
|
||||
) {
|
||||
let trade_payload = trade_event.payload_json.clone();
|
||||
let trade_instruction_id = dex_decode_extract_first_i64(
|
||||
&trade_payload,
|
||||
&["instructionId", "instruction_id"],
|
||||
);
|
||||
let trade_mint = dex_decode_extract_first_string(
|
||||
&trade_payload,
|
||||
&["mint", "tokenMint", "tokenAMint"],
|
||||
);
|
||||
let trade_actor = dex_decode_extract_first_string(
|
||||
&trade_payload,
|
||||
&["user", "actorWallet", "userWallet"],
|
||||
);
|
||||
for sibling in decoded_events {
|
||||
let direct_match = match sibling {
|
||||
crate::PumpFunDecodedEvent::BuyTrade(event) => {
|
||||
pump_fun_direct_trade_matches_anchor_trade_event(
|
||||
event.instruction_id,
|
||||
event.mint.as_deref(),
|
||||
event.user.as_deref(),
|
||||
trade_instruction_id,
|
||||
trade_mint.as_deref(),
|
||||
trade_actor.as_deref(),
|
||||
)
|
||||
},
|
||||
crate::PumpFunDecodedEvent::SellTrade(event) => {
|
||||
pump_fun_direct_trade_matches_anchor_trade_event(
|
||||
event.instruction_id,
|
||||
event.mint.as_deref(),
|
||||
event.user.as_deref(),
|
||||
trade_instruction_id,
|
||||
trade_mint.as_deref(),
|
||||
trade_actor.as_deref(),
|
||||
)
|
||||
},
|
||||
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||
if event.event_kind.as_str() != "pump_fun.buy_exact_sol_in" {
|
||||
false
|
||||
} else {
|
||||
let instruction_mint = dex_decode_extract_first_string(
|
||||
&event.payload_json,
|
||||
&["mint", "tokenMint", "tokenAMint"],
|
||||
);
|
||||
let instruction_actor = dex_decode_extract_first_string(
|
||||
&event.payload_json,
|
||||
&["user", "actorWallet", "userWallet"],
|
||||
);
|
||||
pump_fun_direct_trade_matches_anchor_trade_event(
|
||||
event.instruction_id,
|
||||
instruction_mint.as_deref(),
|
||||
instruction_actor.as_deref(),
|
||||
trade_instruction_id,
|
||||
trade_mint.as_deref(),
|
||||
trade_actor.as_deref(),
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
if !direct_match {
|
||||
continue;
|
||||
}
|
||||
let object = match trade_event.payload_json.as_object_mut() {
|
||||
Some(object) => object,
|
||||
None => return,
|
||||
};
|
||||
object.insert(
|
||||
"skipTradeReason".to_string(),
|
||||
serde_json::Value::String(
|
||||
"pump_fun_trade_event_covered_by_direct_instruction_trade".to_string(),
|
||||
),
|
||||
);
|
||||
object.insert(
|
||||
"skipCandleReason".to_string(),
|
||||
serde_json::Value::String(
|
||||
"pump_fun_trade_event_covered_by_direct_instruction_trade".to_string(),
|
||||
),
|
||||
);
|
||||
object.insert(
|
||||
"anchorTradeEventCoveredByDirectInstructionTrade".to_string(),
|
||||
serde_json::Value::Bool(true),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fun_direct_trade_matches_anchor_trade_event(
|
||||
direct_instruction_id: i64,
|
||||
direct_mint: std::option::Option<&str>,
|
||||
direct_actor: std::option::Option<&str>,
|
||||
trade_instruction_id: std::option::Option<i64>,
|
||||
trade_mint: std::option::Option<&str>,
|
||||
trade_actor: std::option::Option<&str>,
|
||||
) -> bool {
|
||||
if let Some(trade_instruction_id) = trade_instruction_id {
|
||||
if direct_instruction_id != trade_instruction_id {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if !dex_decode_optional_strings_match(trade_mint, direct_mint) {
|
||||
return false;
|
||||
}
|
||||
if !dex_decode_optional_strings_match(trade_actor, direct_actor) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
fn pump_fun_merge_instruction_context_payload(
|
||||
instruction_payload: &serde_json::Value,
|
||||
trade_payload: &mut serde_json::Value,
|
||||
) {
|
||||
let trade_object = match trade_payload.as_object_mut() {
|
||||
Some(trade_object) => trade_object,
|
||||
None => return,
|
||||
};
|
||||
let instruction_object = match instruction_payload.as_object() {
|
||||
Some(instruction_object) => instruction_object,
|
||||
None => return,
|
||||
};
|
||||
let copy_keys = [
|
||||
"poolAccount",
|
||||
"bondingCurve",
|
||||
"associatedBondingCurve",
|
||||
"poolBaseTokenAccount",
|
||||
"poolQuoteTokenAccount",
|
||||
"associatedQuoteBondingCurve",
|
||||
"lpMint",
|
||||
"tokenAMint",
|
||||
"tokenBMint",
|
||||
"quoteMint",
|
||||
"feeRecipient",
|
||||
"creatorVault",
|
||||
"associatedCreatorVault",
|
||||
];
|
||||
for key in copy_keys {
|
||||
if trade_object.contains_key(key) {
|
||||
continue;
|
||||
}
|
||||
let value = match instruction_object.get(key) {
|
||||
Some(value) => value.clone(),
|
||||
None => continue,
|
||||
};
|
||||
trade_object.insert(key.to_string(), value);
|
||||
}
|
||||
trade_object.insert(
|
||||
"amountSource".to_string(),
|
||||
serde_json::Value::String("pump_fun_anchor_trade_event".to_string()),
|
||||
);
|
||||
trade_object.insert(
|
||||
"anchorTradeEventInstructionContextSource".to_string(),
|
||||
serde_json::Value::String("matching_instruction_audit".to_string()),
|
||||
);
|
||||
trade_object.remove("skipTradeReason");
|
||||
trade_object.remove("skipCandleReason");
|
||||
trade_object.remove("skipCatalogReason");
|
||||
}
|
||||
|
||||
fn dex_decode_extract_first_i64(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<i64> {
|
||||
if let Some(object) = value.as_object() {
|
||||
for candidate_key in candidate_keys {
|
||||
let candidate_value = match object.get(*candidate_key) {
|
||||
Some(candidate_value) => candidate_value,
|
||||
None => continue,
|
||||
};
|
||||
if let Some(number) = candidate_value.as_i64() {
|
||||
return Some(number);
|
||||
}
|
||||
if let Some(text) = candidate_value.as_str() {
|
||||
let parsed = text.parse::<i64>();
|
||||
match parsed {
|
||||
Ok(parsed) => return Some(parsed),
|
||||
Err(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn dex_decode_optional_strings_match(
|
||||
left: std::option::Option<&str>,
|
||||
right: std::option::Option<&str>,
|
||||
) -> bool {
|
||||
match (left, right) {
|
||||
(Some(left), Some(right)) => return left == right,
|
||||
_ => return true,
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fun_decoded_event_is_trade_event(decoded_event: &crate::PumpFunDecodedEvent) -> bool {
|
||||
match decoded_event {
|
||||
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||
return event.event_kind.as_str() == "pump_fun.trade_event";
|
||||
},
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn dex_decode_extract_first_string(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
|
||||
@@ -119,6 +119,21 @@ pub(crate) fn dex_detection_route(
|
||||
("pump_fun", "pump_fun.sell") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||
},
|
||||
("pump_fun", "pump_fun.buy_exact_sol_in") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||
},
|
||||
("pump_fun", "pump_fun.buy_exact_quote_in_v2") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||
},
|
||||
("pump_fun", "pump_fun.buy_v2") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||
},
|
||||
("pump_fun", "pump_fun.sell_v2") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||
},
|
||||
("pump_fun", "pump_fun.trade_event") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||
},
|
||||
("pump_swap", "pump_swap.buy") => {
|
||||
if crate::dex_detection_route::is_incomplete_pump_swap_decoded_event(decoded_event) {
|
||||
return Some(
|
||||
|
||||
@@ -323,6 +323,15 @@ pub fn is_dex_trade_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind == "raydium_launchpad.trade_event" {
|
||||
return true;
|
||||
}
|
||||
if event_kind == "pump_fun.trade_event" {
|
||||
return true;
|
||||
}
|
||||
if event_kind.ends_with(".buy_v2") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.ends_with(".sell_v2") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.ends_with(".buy") {
|
||||
return true;
|
||||
}
|
||||
@@ -520,6 +529,15 @@ pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains("partner_claim_fee") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains("distribute_creator_fees") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains("minimum_distributable_fee") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains("reserved_fee_recipients_event") {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -828,6 +846,15 @@ pub fn is_dex_admin_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".extend_account") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".add_quote_mint") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".remove_quote_mint") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains("reserved_fee_recipients") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains("update_") {
|
||||
return true;
|
||||
}
|
||||
@@ -1165,6 +1192,9 @@ mod tests {
|
||||
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.swap_v2"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.exact_output"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.buy"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.buy_v2"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.sell_v2"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.trade_event"), "trade");
|
||||
assert!(super::is_dex_trade_event_kind("raydium_cpmm.swap_base_input"));
|
||||
assert!(super::is_dex_candle_candidate_event_kind("raydium_cpmm.swap_base_input"));
|
||||
}
|
||||
|
||||
@@ -318,6 +318,9 @@ fn infer_expected_db_target_for_entry(
|
||||
if decoder_code == "pump_swap" {
|
||||
return infer_pump_swap_expected_db_target(entry_name, entry_kind);
|
||||
}
|
||||
if decoder_code == "pump_fun" {
|
||||
return infer_pump_fun_expected_db_target(entry_name, entry_kind);
|
||||
}
|
||||
if decoder_code == "raydium_cpmm"
|
||||
&& (entry_name == "swap_event" || entry_name == "anchor_idl_instruction")
|
||||
{
|
||||
@@ -524,6 +527,104 @@ fn infer_expected_db_target(
|
||||
return Some(target.to_string());
|
||||
}
|
||||
|
||||
fn infer_pump_fun_expected_db_target(
|
||||
entry_name: &str,
|
||||
entry_kind: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if entry_kind == crate::ENTRY_KIND_PROGRAM {
|
||||
return None;
|
||||
}
|
||||
if entry_name == "buy"
|
||||
|| entry_name == "sell"
|
||||
|| entry_name == "buy_v2"
|
||||
|| entry_name == "sell_v2"
|
||||
|| entry_name == "buy_exact_sol_in"
|
||||
|| entry_name == "buy_exact_quote_in_v2"
|
||||
{
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string());
|
||||
}
|
||||
if entry_name == "trade_event" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string());
|
||||
}
|
||||
if entry_name == "create"
|
||||
|| entry_name == "create_event"
|
||||
|| entry_name == "create_v2"
|
||||
|| entry_name == "create_v2_token"
|
||||
|| entry_name == "complete_event"
|
||||
{
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string());
|
||||
}
|
||||
if entry_name == "initialize" {
|
||||
return Some(
|
||||
crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string(),
|
||||
);
|
||||
}
|
||||
if entry_name == "migrate"
|
||||
|| entry_name == "migrate_v2"
|
||||
|| entry_name == "migrate_bonding_curve_creator"
|
||||
|| entry_name == "migrate_bonding_curve_creator_event"
|
||||
|| entry_name == "complete_pump_amm_migration_event"
|
||||
{
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string());
|
||||
}
|
||||
if entry_name == "collect_creator_fee"
|
||||
|| entry_name == "collect_creator_fee_v2"
|
||||
|| entry_name == "collect_creator_fee_event"
|
||||
|| entry_name == "distribute_creator_fees"
|
||||
|| entry_name == "distribute_creator_fees_v2"
|
||||
|| entry_name == "distribute_creator_fees_event"
|
||||
|| entry_name == "get_minimum_distributable_fee"
|
||||
|| entry_name == "minimum_distributable_fee_event"
|
||||
{
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS.to_string());
|
||||
}
|
||||
if entry_name == "claim_cashback"
|
||||
|| entry_name == "claim_cashback_v2"
|
||||
|| entry_name == "claim_cashback_event"
|
||||
|| entry_name == "claim_token_incentives"
|
||||
|| entry_name == "claim_token_incentives_event"
|
||||
|| entry_name == "admin_update_token_incentives"
|
||||
|| entry_name == "admin_update_token_incentives_event"
|
||||
|| entry_name == "init_user_volume_accumulator"
|
||||
|| entry_name == "init_user_volume_accumulator_event"
|
||||
|| entry_name == "sync_user_volume_accumulator"
|
||||
|| entry_name == "sync_user_volume_accumulator_event"
|
||||
|| entry_name == "close_user_volume_accumulator"
|
||||
|| entry_name == "close_user_volume_accumulator_event"
|
||||
{
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS.to_string());
|
||||
}
|
||||
if entry_name == "admin_set_creator"
|
||||
|| entry_name == "admin_set_creator_event"
|
||||
|| entry_name == "admin_set_idl_authority"
|
||||
|| entry_name == "admin_set_idl_authority_event"
|
||||
|| entry_name == "add_quote_mint"
|
||||
|| entry_name == "remove_quote_mint"
|
||||
|| entry_name == "extend_account"
|
||||
|| entry_name == "extend_account_event"
|
||||
|| entry_name == "set_creator"
|
||||
|| entry_name == "set_creator_event"
|
||||
|| entry_name == "set_mayhem_virtual_params"
|
||||
|| entry_name == "update_mayhem_virtual_params_event"
|
||||
|| entry_name == "set_metaplex_creator"
|
||||
|| entry_name == "set_metaplex_creator_event"
|
||||
|| entry_name == "set_params"
|
||||
|| entry_name == "set_params_event"
|
||||
|| entry_name == "set_reserved_fee_recipients"
|
||||
|| entry_name == "reserved_fee_recipients_event"
|
||||
|| entry_name == "set_virtual_quote_reserves"
|
||||
|| entry_name == "toggle_cashback_enabled"
|
||||
|| entry_name == "toggle_create_v2"
|
||||
|| entry_name == "toggle_mayhem_mode"
|
||||
|| entry_name == "update_buyback_config"
|
||||
|| entry_name == "update_global_authority"
|
||||
|| entry_name == "update_global_authority_event"
|
||||
{
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS.to_string());
|
||||
}
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
|
||||
fn infer_pump_swap_expected_db_target(
|
||||
entry_name: &str,
|
||||
entry_kind: &str,
|
||||
@@ -654,6 +755,161 @@ fn infer_pump_swap_event_family(
|
||||
return infer_event_family(entry_name, entry_kind);
|
||||
}
|
||||
|
||||
fn infer_pump_fun_event_family(
|
||||
entry_name: &str,
|
||||
entry_kind: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if entry_kind == crate::ENTRY_KIND_PROGRAM {
|
||||
return None;
|
||||
}
|
||||
match entry_name {
|
||||
"buy"
|
||||
| "sell"
|
||||
| "buy_v2"
|
||||
| "sell_v2"
|
||||
| "buy_exact_quote_in_v2"
|
||||
| "buy_exact_sol_in"
|
||||
| "trade_event" => return Some("swap".to_string()),
|
||||
"create" | "create_event" | "create_v2" | "create_v2_token" | "complete_event" => {
|
||||
return Some("launch".to_string());
|
||||
},
|
||||
"migrate"
|
||||
| "migrate_v2"
|
||||
| "migrate_bonding_curve_creator"
|
||||
| "migrate_bonding_curve_creator_event"
|
||||
| "complete_pump_amm_migration_event" => return Some("migration".to_string()),
|
||||
"claim_cashback"
|
||||
| "claim_cashback_v2"
|
||||
| "claim_cashback_event"
|
||||
| "claim_token_incentives"
|
||||
| "claim_token_incentives_event"
|
||||
| "admin_update_token_incentives"
|
||||
| "admin_update_token_incentives_event"
|
||||
| "init_user_volume_accumulator"
|
||||
| "init_user_volume_accumulator_event"
|
||||
| "sync_user_volume_accumulator"
|
||||
| "sync_user_volume_accumulator_event"
|
||||
| "close_user_volume_accumulator"
|
||||
| "close_user_volume_accumulator_event" => return Some("reward".to_string()),
|
||||
"collect_creator_fee"
|
||||
| "collect_creator_fee_v2"
|
||||
| "collect_creator_fee_event"
|
||||
| "distribute_creator_fees"
|
||||
| "distribute_creator_fees_v2"
|
||||
| "distribute_creator_fees_event"
|
||||
| "get_minimum_distributable_fee"
|
||||
| "minimum_distributable_fee_event" => return Some("fee".to_string()),
|
||||
"add_quote_mint"
|
||||
| "remove_quote_mint"
|
||||
| "admin_set_creator"
|
||||
| "admin_set_creator_event"
|
||||
| "admin_set_idl_authority"
|
||||
| "admin_set_idl_authority_event"
|
||||
| "extend_account"
|
||||
| "extend_account_event"
|
||||
| "set_creator"
|
||||
| "set_creator_event"
|
||||
| "set_mayhem_virtual_params"
|
||||
| "update_mayhem_virtual_params_event"
|
||||
| "set_metaplex_creator"
|
||||
| "set_metaplex_creator_event"
|
||||
| "set_params"
|
||||
| "set_params_event"
|
||||
| "set_reserved_fee_recipients"
|
||||
| "reserved_fee_recipients_event"
|
||||
| "set_virtual_quote_reserves"
|
||||
| "toggle_cashback_enabled"
|
||||
| "toggle_create_v2"
|
||||
| "toggle_mayhem_mode"
|
||||
| "update_buyback_config"
|
||||
| "update_global_authority"
|
||||
| "update_global_authority_event" => return Some("admin_config".to_string()),
|
||||
"initialize" => return Some("pool_create".to_string()),
|
||||
_ => return infer_event_family(entry_name, entry_kind),
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fun_local_event_kind(entry_name: &str) -> std::option::Option<std::string::String> {
|
||||
if entry_name.ends_with("_event") {
|
||||
return Some(format!("pump_fun.{}", entry_name));
|
||||
}
|
||||
match entry_name {
|
||||
"buy" => return Some("pump_fun.buy".to_string()),
|
||||
"sell" => return Some("pump_fun.sell".to_string()),
|
||||
"create_v2" => return Some("pump_fun.create_v2_token".to_string()),
|
||||
"add_quote_mint" => return Some("pump_fun.add_quote_mint".to_string()),
|
||||
"admin_set_creator" => return Some("pump_fun.admin_set_creator".to_string()),
|
||||
"admin_set_idl_authority" => {
|
||||
return Some("pump_fun.admin_set_idl_authority".to_string());
|
||||
},
|
||||
"admin_update_token_incentives" => {
|
||||
return Some("pump_fun.admin_update_token_incentives".to_string());
|
||||
},
|
||||
"buy_exact_quote_in_v2" => {
|
||||
return Some("pump_fun.buy_exact_quote_in_v2".to_string());
|
||||
},
|
||||
"buy_exact_sol_in" => return Some("pump_fun.buy_exact_sol_in".to_string()),
|
||||
"buy_v2" => return Some("pump_fun.buy_v2".to_string()),
|
||||
"claim_cashback" => return Some("pump_fun.claim_cashback".to_string()),
|
||||
"claim_cashback_v2" => return Some("pump_fun.claim_cashback_v2".to_string()),
|
||||
"claim_token_incentives" => {
|
||||
return Some("pump_fun.claim_token_incentives".to_string());
|
||||
},
|
||||
"close_user_volume_accumulator" => {
|
||||
return Some("pump_fun.close_user_volume_accumulator".to_string());
|
||||
},
|
||||
"collect_creator_fee" => return Some("pump_fun.collect_creator_fee".to_string()),
|
||||
"collect_creator_fee_v2" => return Some("pump_fun.collect_creator_fee_v2".to_string()),
|
||||
"create" => return Some("pump_fun.create".to_string()),
|
||||
"distribute_creator_fees" => {
|
||||
return Some("pump_fun.distribute_creator_fees".to_string());
|
||||
},
|
||||
"distribute_creator_fees_v2" => {
|
||||
return Some("pump_fun.distribute_creator_fees_v2".to_string());
|
||||
},
|
||||
"extend_account" => return Some("pump_fun.extend_account".to_string()),
|
||||
"get_minimum_distributable_fee" => {
|
||||
return Some("pump_fun.get_minimum_distributable_fee".to_string());
|
||||
},
|
||||
"init_user_volume_accumulator" => {
|
||||
return Some("pump_fun.init_user_volume_accumulator".to_string());
|
||||
},
|
||||
"initialize" => return Some("pump_fun.initialize".to_string()),
|
||||
"migrate" => return Some("pump_fun.migrate".to_string()),
|
||||
"migrate_bonding_curve_creator" => {
|
||||
return Some("pump_fun.migrate_bonding_curve_creator".to_string());
|
||||
},
|
||||
"migrate_v2" => return Some("pump_fun.migrate_v2".to_string()),
|
||||
"remove_quote_mint" => return Some("pump_fun.remove_quote_mint".to_string()),
|
||||
"sell_v2" => return Some("pump_fun.sell_v2".to_string()),
|
||||
"set_creator" => return Some("pump_fun.set_creator".to_string()),
|
||||
"set_mayhem_virtual_params" => {
|
||||
return Some("pump_fun.set_mayhem_virtual_params".to_string());
|
||||
},
|
||||
"set_metaplex_creator" => return Some("pump_fun.set_metaplex_creator".to_string()),
|
||||
"set_params" => return Some("pump_fun.set_params".to_string()),
|
||||
"set_reserved_fee_recipients" => {
|
||||
return Some("pump_fun.set_reserved_fee_recipients".to_string());
|
||||
},
|
||||
"set_virtual_quote_reserves" => {
|
||||
return Some("pump_fun.set_virtual_quote_reserves".to_string());
|
||||
},
|
||||
"sync_user_volume_accumulator" => {
|
||||
return Some("pump_fun.sync_user_volume_accumulator".to_string());
|
||||
},
|
||||
"toggle_cashback_enabled" => {
|
||||
return Some("pump_fun.toggle_cashback_enabled".to_string());
|
||||
},
|
||||
"toggle_create_v2" => return Some("pump_fun.toggle_create_v2".to_string()),
|
||||
"toggle_mayhem_mode" => return Some("pump_fun.toggle_mayhem_mode".to_string()),
|
||||
"update_buyback_config" => return Some("pump_fun.update_buyback_config".to_string()),
|
||||
"update_global_authority" => {
|
||||
return Some("pump_fun.update_global_authority".to_string());
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_swap_local_event_kind(entry_name: &str) -> std::option::Option<std::string::String> {
|
||||
if entry_name.ends_with("_event") {
|
||||
return Some(format!("pump_swap.{}", entry_name));
|
||||
@@ -716,6 +972,9 @@ fn infer_event_family_for_entry(
|
||||
entry_name: &str,
|
||||
entry_kind: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if decoder_code == "pump_fun" {
|
||||
return infer_pump_fun_event_family(entry_name, entry_kind);
|
||||
}
|
||||
if decoder_code == "pump_swap" {
|
||||
return infer_pump_swap_event_family(entry_name, entry_kind);
|
||||
}
|
||||
@@ -1110,6 +1369,9 @@ pub(crate) fn known_local_event_kind(
|
||||
decoder_code: &str,
|
||||
entry_name: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if decoder_code == "pump_fun" {
|
||||
return pump_fun_local_event_kind(entry_name);
|
||||
}
|
||||
if decoder_code == "pump_swap" {
|
||||
return pump_swap_local_event_kind(entry_name);
|
||||
}
|
||||
@@ -1488,6 +1750,85 @@ mod tests {
|
||||
Some("raydium_clmm.pool_created_event".to_string())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn pump_fun_coverage_maps_local_idl_and_audit_entries() {
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("pump_fun", "buy"),
|
||||
Some("pump_fun.buy".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("pump_fun", "create_v2"),
|
||||
Some("pump_fun.create_v2_token".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("pump_fun", "buy_v2"),
|
||||
Some("pump_fun.buy_v2".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("pump_fun", "collect_creator_fee_v2"),
|
||||
Some("pump_fun.collect_creator_fee_v2".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("pump_fun", "trade_event"),
|
||||
Some("pump_fun.trade_event".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("pump_fun", "claim_cashback_event"),
|
||||
Some("pump_fun.claim_cashback_event".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family_for_entry("pump_fun", "create_event", crate::ENTRY_KIND_EVENT),
|
||||
Some("launch".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family_for_entry(
|
||||
"pump_fun",
|
||||
"set_metaplex_creator_event",
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
),
|
||||
Some("admin_config".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family_for_entry(
|
||||
"pump_fun",
|
||||
"claim_token_incentives_event",
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
),
|
||||
Some("reward".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family_for_entry("pump_fun", "buy_v2", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("swap".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_expected_db_target_for_entry(
|
||||
"pump_fun",
|
||||
"buy",
|
||||
Some("swap"),
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_expected_db_target_for_entry(
|
||||
"pump_fun",
|
||||
"buy_v2",
|
||||
Some("swap"),
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_expected_db_target_for_entry(
|
||||
"pump_fun",
|
||||
"create_v2",
|
||||
Some("launch"),
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launchpad_swap_instructions_materialize_as_launch_events_without_duplicate_trades() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -319,6 +319,53 @@ fn resolve_instruction_name(
|
||||
};
|
||||
return Some(format!("raydium_launchpad.{}", layout.instruction_name));
|
||||
}
|
||||
if program_id == crate::PUMP_FUN_PROGRAM_ID || decoder_code == Some("pump_fun") {
|
||||
let name = match discriminator_hex {
|
||||
"e445a52e51cb9a1d" => "anchor_self_cpi_log",
|
||||
"6f79153828185ed1" => "add_quote_mint",
|
||||
"4519ab8e39ef0d04" => "admin_set_creator",
|
||||
"08d960e79068c005" => "admin_set_idl_authority",
|
||||
"d10b7357d5177ccc" => "admin_update_token_incentives",
|
||||
"66063d1201daebea" => "buy",
|
||||
"c2ab1c46684d5b2f" => "buy_exact_quote_in_v2",
|
||||
"38fc74089edfcd5f" => "buy_exact_sol_in",
|
||||
"b817ee6167c5d33d" => "buy_v2",
|
||||
"253a237ebe35e4c5" => "claim_cashback",
|
||||
"7af3cc415e741d37" => "claim_cashback_v2",
|
||||
"1004471ccc01281b" => "claim_token_incentives",
|
||||
"f945a4da9667548a" => "close_user_volume_accumulator",
|
||||
"1416567bc61cdb84" => "collect_creator_fee",
|
||||
"cf118af204221338" => "collect_creator_fee_v2",
|
||||
"181ec828051c0777" => "create",
|
||||
"d6904cec5f8b31b4" => "create_v2",
|
||||
"a572670079cef751" => "distribute_creator_fees",
|
||||
"ffcb134ff444089f" => "distribute_creator_fees_v2",
|
||||
"ea66c2cb96483ee5" => "extend_account",
|
||||
"75e17fca865f4423" => "get_minimum_distributable_fee",
|
||||
"5e06ca73ff60e8b7" => "init_user_volume_accumulator",
|
||||
"afaf6d1f0d989bed" => "initialize",
|
||||
"9beae792ec9ea21e" => "migrate",
|
||||
"577c34bf3426d6e8" => "migrate_bonding_curve_creator",
|
||||
"bbcb121fceedfe29" => "migrate_v2",
|
||||
"b141df2658d19e9b" => "remove_quote_mint",
|
||||
"33e685a4017f83ad" => "sell",
|
||||
"5df6823ce7e940b2" => "sell_v2",
|
||||
"fe94ff70cf8eaaa5" => "set_creator",
|
||||
"3da9bcbf99952a61" => "set_mayhem_virtual_params",
|
||||
"8a60aed93055c5f6" => "set_metaplex_creator",
|
||||
"1beab2349302bb8d" => "set_params",
|
||||
"6faca2e87259d58e" => "set_reserved_fee_recipients",
|
||||
"6587bf6809581460" => "set_virtual_quote_reserves",
|
||||
"561fc057a3574fee" => "sync_user_volume_accumulator",
|
||||
"7367e0ffbd5956c3" => "toggle_cashback_enabled",
|
||||
"1cffe6f0ac6bcbab" => "toggle_create_v2",
|
||||
"01096fd0641fffa3" => "toggle_mayhem_mode",
|
||||
"fbe0ab92a01a71e9" => "update_buyback_config",
|
||||
"e3b54ac4d01561d5" => "update_global_authority",
|
||||
_ => return None,
|
||||
};
|
||||
return Some(name.to_string());
|
||||
}
|
||||
if program_id == crate::PUMP_SWAP_PROGRAM_ID || decoder_code == Some("pump_swap") {
|
||||
let name = match discriminator_hex {
|
||||
"e445a52e51cb9a1d" => "anchor_self_cpi_log",
|
||||
|
||||
@@ -1177,6 +1177,8 @@ pub use dex::PumpFunCreateV2TokenDecoded;
|
||||
pub use dex::PumpFunDecodedEvent;
|
||||
/// Pump.fun decoder.
|
||||
pub use dex::PumpFunDecoder;
|
||||
/// Decoded Pump.fun audit-only instruction event.
|
||||
pub use dex::PumpFunInstructionAuditDecoded;
|
||||
/// Decoded Pump.fun bonding-curve trade event.
|
||||
pub use dex::PumpFunTradeDecoded;
|
||||
/// Decoded PumpSwap event.
|
||||
|
||||
@@ -112,6 +112,15 @@ impl NonTradeEventMaterializationService {
|
||||
if is_anchor_event_audit_only(&payload) {
|
||||
continue;
|
||||
}
|
||||
if should_skip_pump_fun_duplicate_non_trade_event(decoded_event, &decoded_events) {
|
||||
tracing::debug!(
|
||||
event_kind = %decoded_event.event_kind,
|
||||
decoded_event_id = ?decoded_event.id,
|
||||
signature = %transaction.signature,
|
||||
"skipping duplicate pump_fun non-trade materialization"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str()) {
|
||||
let cleanup_result =
|
||||
self.delete_stale_pool_admin_event_for_lifecycle(decoded_event).await;
|
||||
@@ -140,7 +149,9 @@ impl NonTradeEventMaterializationService {
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str()) {
|
||||
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str())
|
||||
&& !is_launchpad_launch_event_materializable(decoded_event.event_kind.as_str())
|
||||
{
|
||||
let materialized = self
|
||||
.materialize_pool_lifecycle_event(&transaction, transaction_id, decoded_event)
|
||||
.await;
|
||||
@@ -672,6 +683,10 @@ impl NonTradeEventMaterializationService {
|
||||
"poolState",
|
||||
"pool_state",
|
||||
"poolAccount",
|
||||
"bondingCurve",
|
||||
"bonding_curve",
|
||||
"sharingConfig",
|
||||
"sharing_config",
|
||||
],
|
||||
);
|
||||
let related_mint = extract_first_string(
|
||||
@@ -737,9 +752,8 @@ impl NonTradeEventMaterializationService {
|
||||
Some(decoded_event_id) => decoded_event_id,
|
||||
None => return Ok(false),
|
||||
};
|
||||
let context = self
|
||||
.resolve_liquidity_context(transaction, transaction_id, decoded_event)
|
||||
.await;
|
||||
let context =
|
||||
self.resolve_liquidity_context(transaction, transaction_id, decoded_event).await;
|
||||
let context = match context {
|
||||
Ok(context) => context,
|
||||
Err(error) => return Err(error),
|
||||
@@ -1018,9 +1032,8 @@ impl NonTradeEventMaterializationService {
|
||||
Some(decoded_event_id) => decoded_event_id,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let payload_result = serde_json::from_str::<serde_json::Value>(
|
||||
decoded_event.payload_json.as_str(),
|
||||
);
|
||||
let payload_result =
|
||||
serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
|
||||
let mut object = match payload_result {
|
||||
Ok(serde_json::Value::Object(object)) => object,
|
||||
Ok(other) => {
|
||||
@@ -1179,9 +1192,8 @@ impl NonTradeEventMaterializationService {
|
||||
Ok(decoded_events) => decoded_events,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let target_payload_result = serde_json::from_str::<serde_json::Value>(
|
||||
decoded_event.payload_json.as_str(),
|
||||
);
|
||||
let target_payload_result =
|
||||
serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
|
||||
let target_payload = match target_payload_result {
|
||||
Ok(target_payload) => target_payload,
|
||||
Err(_) => serde_json::Value::Object(serde_json::Map::new()),
|
||||
@@ -1193,9 +1205,8 @@ impl NonTradeEventMaterializationService {
|
||||
if !candidate.event_kind.starts_with("raydium_clmm.") {
|
||||
continue;
|
||||
}
|
||||
let candidate_payload_result = serde_json::from_str::<serde_json::Value>(
|
||||
candidate.payload_json.as_str(),
|
||||
);
|
||||
let candidate_payload_result =
|
||||
serde_json::from_str::<serde_json::Value>(candidate.payload_json.as_str());
|
||||
let candidate_payload = match candidate_payload_result {
|
||||
Ok(candidate_payload) => candidate_payload,
|
||||
Err(_) => serde_json::Value::Object(serde_json::Map::new()),
|
||||
@@ -1425,9 +1436,8 @@ struct MaterializationAccountKeyInfo {
|
||||
fn token_mints_by_account_from_transaction(
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
) -> std::collections::HashMap<std::string::String, std::string::String> {
|
||||
let transaction_json = serde_json::from_str::<serde_json::Value>(
|
||||
transaction.transaction_json.as_str(),
|
||||
);
|
||||
let transaction_json =
|
||||
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
|
||||
let transaction_json = match transaction_json {
|
||||
Ok(transaction_json) => transaction_json,
|
||||
Err(_) => return std::collections::HashMap::new(),
|
||||
@@ -1475,10 +1485,7 @@ fn materialization_account_keys(
|
||||
value.get("pubkey").and_then(serde_json::Value::as_str).map(str::to_string)
|
||||
};
|
||||
if let Some(address) = address {
|
||||
account_keys.push(MaterializationAccountKeyInfo {
|
||||
index: index as i64,
|
||||
address,
|
||||
});
|
||||
account_keys.push(MaterializationAccountKeyInfo { index: index as i64, address });
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
@@ -1507,10 +1514,7 @@ fn append_materialization_loaded_addresses(
|
||||
None => continue,
|
||||
};
|
||||
let index = account_keys.len() as i64;
|
||||
account_keys.push(MaterializationAccountKeyInfo {
|
||||
index,
|
||||
address: address.to_string(),
|
||||
});
|
||||
account_keys.push(MaterializationAccountKeyInfo { index, address: address.to_string() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1567,21 +1571,15 @@ fn infer_raydium_clmm_pair_mints_from_payload_accounts(
|
||||
Some(accounts) => accounts,
|
||||
None => return None,
|
||||
};
|
||||
let instruction_name = payload
|
||||
.get("instructionName")
|
||||
.and_then(serde_json::Value::as_str);
|
||||
let instruction_name = payload.get("instructionName").and_then(serde_json::Value::as_str);
|
||||
let instruction_name = match instruction_name {
|
||||
Some(instruction_name) => instruction_name,
|
||||
None => "",
|
||||
};
|
||||
let candidate_pairs = raydium_clmm_token_account_candidate_pairs(instruction_name);
|
||||
for pair in candidate_pairs {
|
||||
let inferred = infer_mints_from_account_pair(
|
||||
accounts,
|
||||
pair.0,
|
||||
pair.1,
|
||||
token_mints_by_account,
|
||||
);
|
||||
let inferred =
|
||||
infer_mints_from_account_pair(accounts, pair.0, pair.1, token_mints_by_account);
|
||||
if let Some(inferred) = inferred {
|
||||
return Some(inferred);
|
||||
}
|
||||
@@ -1610,7 +1608,17 @@ fn raydium_clmm_token_account_candidate_pairs(
|
||||
if instruction_name == "increase_liquidity_v2" {
|
||||
return vec![(13, 14), (9, 10), (7, 8)];
|
||||
}
|
||||
return vec![(12, 13), (13, 14), (9, 10), (7, 8), (5, 6), (10, 11), (14, 15), (18, 19), (20, 21)];
|
||||
return vec![
|
||||
(12, 13),
|
||||
(13, 14),
|
||||
(9, 10),
|
||||
(7, 8),
|
||||
(5, 6),
|
||||
(10, 11),
|
||||
(14, 15),
|
||||
(18, 19),
|
||||
(20, 21),
|
||||
];
|
||||
}
|
||||
|
||||
fn infer_mints_from_account_pair(
|
||||
@@ -1732,6 +1740,21 @@ fn extract_account_string(
|
||||
}
|
||||
|
||||
fn is_launchpad_launch_event_materializable(event_kind: &str) -> bool {
|
||||
if event_kind.contains("pump_fun.create_v2_token") {
|
||||
return true;
|
||||
}
|
||||
if event_kind == "pump_fun.create" || event_kind == "pump_fun.create_event" {
|
||||
return true;
|
||||
}
|
||||
if event_kind == "pump_fun.migrate"
|
||||
|| event_kind == "pump_fun.migrate_v2"
|
||||
|| event_kind == "pump_fun.migrate_bonding_curve_creator"
|
||||
|| event_kind == "pump_fun.migrate_bonding_curve_creator_event"
|
||||
|| event_kind == "pump_fun.complete_event"
|
||||
|| event_kind == "pump_fun.complete_pump_amm_migration_event"
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains("raydium_launchpad.buy_exact_in") {
|
||||
return true;
|
||||
}
|
||||
@@ -1793,6 +1816,17 @@ fn launchpad_launch_event_role(event_kind: &str) -> std::string::String {
|
||||
if event_kind.contains("migrate_to_cpswap") {
|
||||
return "migration_to_cpswap".to_string();
|
||||
}
|
||||
if event_kind.contains("pump_fun.migrate")
|
||||
|| event_kind.contains("pump_fun.complete_pump_amm_migration")
|
||||
{
|
||||
return "pump_fun_migration".to_string();
|
||||
}
|
||||
if event_kind.contains("pump_fun.complete_event") {
|
||||
return "pump_fun_completion".to_string();
|
||||
}
|
||||
if event_kind.contains("pump_fun.create") {
|
||||
return "pump_fun_launch".to_string();
|
||||
}
|
||||
return "launch".to_string();
|
||||
}
|
||||
|
||||
@@ -1938,7 +1972,108 @@ fn extract_first_bool(
|
||||
return None;
|
||||
}
|
||||
|
||||
fn should_skip_pump_fun_duplicate_non_trade_event(
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
decoded_events: &[crate::DexDecodedEventDto],
|
||||
) -> bool {
|
||||
if !decoded_event.event_kind.starts_with("pump_fun.") {
|
||||
return false;
|
||||
}
|
||||
let preferred_siblings =
|
||||
pump_fun_preferred_non_trade_siblings(decoded_event.event_kind.as_str());
|
||||
if preferred_siblings.is_empty() {
|
||||
return false;
|
||||
}
|
||||
for sibling in decoded_events {
|
||||
if sibling.id == decoded_event.id {
|
||||
continue;
|
||||
}
|
||||
for preferred in &preferred_siblings {
|
||||
if sibling.event_kind.as_str() == *preferred {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn pump_fun_preferred_non_trade_siblings(event_kind: &str) -> std::vec::Vec<&'static str> {
|
||||
match event_kind {
|
||||
"pump_fun.admin_set_creator" => return vec!["pump_fun.admin_set_creator_event"],
|
||||
"pump_fun.admin_set_idl_authority" => {
|
||||
return vec!["pump_fun.admin_set_idl_authority_event"];
|
||||
},
|
||||
"pump_fun.admin_update_token_incentives" => {
|
||||
return vec!["pump_fun.admin_update_token_incentives_event"];
|
||||
},
|
||||
"pump_fun.claim_cashback" | "pump_fun.claim_cashback_v2" => {
|
||||
return vec!["pump_fun.claim_cashback_event"];
|
||||
},
|
||||
"pump_fun.claim_token_incentives" => return vec!["pump_fun.claim_token_incentives_event"],
|
||||
"pump_fun.close_user_volume_accumulator" => {
|
||||
return vec!["pump_fun.close_user_volume_accumulator_event"];
|
||||
},
|
||||
"pump_fun.collect_creator_fee" | "pump_fun.collect_creator_fee_v2" => {
|
||||
return vec!["pump_fun.collect_creator_fee_event"];
|
||||
},
|
||||
"pump_fun.create" => return vec!["pump_fun.create_v2_token", "pump_fun.create_event"],
|
||||
"pump_fun.create_event" => return vec!["pump_fun.create_v2_token"],
|
||||
"pump_fun.distribute_creator_fees" | "pump_fun.distribute_creator_fees_v2" => {
|
||||
return vec!["pump_fun.distribute_creator_fees_event"];
|
||||
},
|
||||
"pump_fun.extend_account" => return vec!["pump_fun.extend_account_event"],
|
||||
"pump_fun.get_minimum_distributable_fee" => {
|
||||
return vec!["pump_fun.minimum_distributable_fee_event"];
|
||||
},
|
||||
"pump_fun.init_user_volume_accumulator" => {
|
||||
return vec!["pump_fun.init_user_volume_accumulator_event"];
|
||||
},
|
||||
"pump_fun.migrate_bonding_curve_creator" => {
|
||||
return vec!["pump_fun.migrate_bonding_curve_creator_event"];
|
||||
},
|
||||
"pump_fun.set_creator" => return vec!["pump_fun.set_creator_event"],
|
||||
"pump_fun.set_metaplex_creator" => return vec!["pump_fun.set_metaplex_creator_event"],
|
||||
"pump_fun.set_params" => return vec!["pump_fun.set_params_event"],
|
||||
"pump_fun.set_reserved_fee_recipients" => {
|
||||
return vec!["pump_fun.reserved_fee_recipients_event"];
|
||||
},
|
||||
"pump_fun.sync_user_volume_accumulator" => {
|
||||
return vec!["pump_fun.sync_user_volume_accumulator_event"];
|
||||
},
|
||||
"pump_fun.update_global_authority" => {
|
||||
return vec!["pump_fun.update_global_authority_event"];
|
||||
},
|
||||
"pump_fun.set_mayhem_virtual_params" => {
|
||||
return vec!["pump_fun.update_mayhem_virtual_params_event"];
|
||||
},
|
||||
_ => return std::vec::Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pump_fun_payload(payload: &serde_json::Value) -> bool {
|
||||
if let Some(object) = payload.as_object() {
|
||||
let protocol_name = object.get("protocolName").and_then(serde_json::Value::as_str);
|
||||
if protocol_name == Some("pump_fun") {
|
||||
return true;
|
||||
}
|
||||
let decoder = object.get("decoder").and_then(serde_json::Value::as_str);
|
||||
if decoder == Some("pump_fun") {
|
||||
return true;
|
||||
}
|
||||
let event_kind = object.get("eventKind").and_then(serde_json::Value::as_str);
|
||||
if let Some(event_kind) = event_kind {
|
||||
if event_kind.starts_with("pump_fun.") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn is_anchor_event_audit_only(payload: &serde_json::Value) -> bool {
|
||||
if is_pump_fun_payload(payload) {
|
||||
return false;
|
||||
}
|
||||
if let Some(object) = payload.as_object() {
|
||||
let flag = object.get("anchorEventAuditOnly");
|
||||
if let Some(flag) = flag {
|
||||
@@ -1946,6 +2081,12 @@ fn is_anchor_event_audit_only(payload: &serde_json::Value) -> bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let flag = object.get("instructionAuditOnly");
|
||||
if let Some(flag) = flag {
|
||||
if flag.as_bool() == Some(true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,18 @@ impl TradeAggregationService {
|
||||
if !crate::is_dex_trade_event_kind(decoded_event.event_kind.as_str()) {
|
||||
continue;
|
||||
}
|
||||
if crate::trade_aggregation::should_skip_pump_fun_duplicate_trade_event(
|
||||
decoded_event,
|
||||
&decoded_events,
|
||||
) {
|
||||
tracing::debug!(
|
||||
event_kind = %decoded_event.event_kind,
|
||||
decoded_event_id = ?decoded_event.id,
|
||||
transaction_signature = %transaction.signature,
|
||||
"skipping duplicate pump_fun trade_event because an instruction trade exists"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let event_context =
|
||||
crate::trade_aggregation_context::load_trade_aggregation_decoded_event_context(
|
||||
self.database.as_ref(),
|
||||
@@ -200,6 +212,68 @@ impl TradeAggregationService {
|
||||
}
|
||||
}
|
||||
|
||||
fn should_skip_pump_fun_duplicate_trade_event(
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
decoded_events: &[crate::DexDecodedEventDto],
|
||||
) -> bool {
|
||||
if decoded_event.event_kind.as_str() != "pump_fun.trade_event" {
|
||||
return false;
|
||||
}
|
||||
let trade_instruction_id = pump_fun_payload_instruction_id(decoded_event.payload_json.as_str());
|
||||
for sibling in decoded_events {
|
||||
if sibling.id == decoded_event.id {
|
||||
continue;
|
||||
}
|
||||
if !is_direct_materialized_pump_fun_instruction_trade_kind(sibling.event_kind.as_str()) {
|
||||
continue;
|
||||
}
|
||||
let sibling_instruction_id = pump_fun_payload_instruction_id(sibling.payload_json.as_str());
|
||||
if trade_instruction_id.is_some()
|
||||
&& sibling_instruction_id.is_some()
|
||||
&& trade_instruction_id != sibling_instruction_id
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn is_direct_materialized_pump_fun_instruction_trade_kind(event_kind: &str) -> bool {
|
||||
match event_kind {
|
||||
"pump_fun.buy" => return true,
|
||||
"pump_fun.sell" => return true,
|
||||
"pump_fun.buy_exact_sol_in" => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fun_payload_instruction_id(payload_json: &str) -> std::option::Option<i64> {
|
||||
let parsed_result = serde_json::from_str::<serde_json::Value>(payload_json);
|
||||
let parsed = match parsed_result {
|
||||
Ok(parsed) => parsed,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let object = match parsed.as_object() {
|
||||
Some(object) => object,
|
||||
None => return None,
|
||||
};
|
||||
let value = match object.get("instructionId") {
|
||||
Some(value) => value,
|
||||
None => return None,
|
||||
};
|
||||
if let Some(number) = value.as_i64() {
|
||||
return Some(number);
|
||||
}
|
||||
if let Some(text) = value.as_str() {
|
||||
let parsed_number = text.parse::<i64>();
|
||||
match parsed_number {
|
||||
Ok(parsed_number) => return Some(parsed_number),
|
||||
Err(_) => return None,
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
|
||||
let err_json = match transaction.err_json.as_ref() {
|
||||
|
||||
@@ -91,7 +91,8 @@ pub(crate) async fn resolve_trade_amounts(
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut price_quote_per_base,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
@@ -788,7 +789,7 @@ fn apply_raydium_launchpad_side_amount_mapping(
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_pump_fun_amount_fallback(
|
||||
async fn apply_pump_fun_amount_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
quote_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
@@ -813,9 +814,183 @@ fn apply_pump_fun_amount_fallback(
|
||||
if price_quote_per_base.is_none() {
|
||||
*price_quote_per_base = inferred.2;
|
||||
}
|
||||
if base_amount_raw.is_none() || quote_amount_raw.is_none() || price_quote_per_base.is_none() {
|
||||
let sibling_result = crate::trade_amount_resolution::apply_pump_fun_trade_event_sibling_amount_fallback(
|
||||
input,
|
||||
base_amount_raw,
|
||||
quote_amount_raw,
|
||||
price_quote_per_base,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = sibling_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn apply_pump_fun_trade_event_sibling_amount_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
quote_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
price_quote_per_base: &mut std::option::Option<f64>,
|
||||
) -> Result<(), crate::Error> {
|
||||
if !crate::trade_amount_resolution::pump_fun_instruction_trade_can_use_trade_event_fallback(
|
||||
input.decoded_event.event_kind.as_str(),
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
let sibling_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
|
||||
input.database,
|
||||
input.decoded_event.transaction_id,
|
||||
)
|
||||
.await;
|
||||
let sibling_events = match sibling_events_result {
|
||||
Ok(sibling_events) => sibling_events,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
for sibling_event in sibling_events {
|
||||
if sibling_event.id == input.decoded_event.id {
|
||||
continue;
|
||||
}
|
||||
if sibling_event.protocol_name.as_str() != "pump_fun" {
|
||||
continue;
|
||||
}
|
||||
if sibling_event.event_kind.as_str() != "pump_fun.trade_event" {
|
||||
continue;
|
||||
}
|
||||
let sibling_payload_result =
|
||||
serde_json::from_str::<serde_json::Value>(sibling_event.payload_json.as_str());
|
||||
let sibling_payload = match sibling_payload_result {
|
||||
Ok(sibling_payload) => sibling_payload,
|
||||
Err(error) => {
|
||||
tracing::debug!(
|
||||
decoded_event_id = ?sibling_event.id,
|
||||
error = %error,
|
||||
"cannot parse pump_fun trade_event sibling payload for amount fallback"
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
if !crate::trade_amount_resolution::pump_fun_trade_event_sibling_matches_instruction(
|
||||
input.decoded_event.event_kind.as_str(),
|
||||
input.payload,
|
||||
&sibling_payload,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let sibling_base_amount = crate::trade_amount_resolution::extract_amount_string(
|
||||
&sibling_payload,
|
||||
&["baseAmountRaw", "baseAmount", "token_amount", "tokenAmount"],
|
||||
);
|
||||
let sibling_quote_amount = crate::trade_amount_resolution::extract_amount_string(
|
||||
&sibling_payload,
|
||||
&["quoteAmountRaw", "quoteAmount", "sol_amount", "solAmount", "quote_amount"],
|
||||
);
|
||||
if base_amount_raw.is_none() {
|
||||
*base_amount_raw = sibling_base_amount;
|
||||
}
|
||||
if quote_amount_raw.is_none() {
|
||||
*quote_amount_raw = sibling_quote_amount;
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
*price_quote_per_base = crate::trade_metric_update::compute_price_quote_per_base_from_raw_amounts_with_decimals(
|
||||
base_amount_raw.as_deref(),
|
||||
quote_amount_raw.as_deref(),
|
||||
input.base_token_decimals,
|
||||
input.quote_token_decimals,
|
||||
);
|
||||
}
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
sibling_decoded_event_id = ?sibling_event.id,
|
||||
base_amount_raw = ?base_amount_raw,
|
||||
quote_amount_raw = ?quote_amount_raw,
|
||||
price_quote_per_base = ?price_quote_per_base,
|
||||
"pump_fun instruction amounts recovered from sibling trade_event"
|
||||
);
|
||||
if base_amount_raw.is_some() && quote_amount_raw.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn pump_fun_instruction_trade_can_use_trade_event_fallback(event_kind: &str) -> bool {
|
||||
match event_kind {
|
||||
"pump_fun.buy_exact_quote_in_v2" => return true,
|
||||
"pump_fun.buy_exact_sol_in" => return true,
|
||||
"pump_fun.buy_v2" => return true,
|
||||
"pump_fun.sell_v2" => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fun_trade_event_sibling_matches_instruction(
|
||||
instruction_event_kind: &str,
|
||||
instruction_payload: &serde_json::Value,
|
||||
trade_event_payload: &serde_json::Value,
|
||||
) -> bool {
|
||||
let expected_is_buy = match instruction_event_kind {
|
||||
"pump_fun.buy_exact_quote_in_v2" => Some(true),
|
||||
"pump_fun.buy_exact_sol_in" => Some(true),
|
||||
"pump_fun.buy_v2" => Some(true),
|
||||
"pump_fun.sell_v2" => Some(false),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(expected_is_buy) = expected_is_buy {
|
||||
let actual_is_buy = crate::trade_amount_resolution::extract_bool_by_candidate_keys(
|
||||
trade_event_payload,
|
||||
&["is_buy", "isBuy"],
|
||||
);
|
||||
match actual_is_buy {
|
||||
Some(actual_is_buy) if actual_is_buy == expected_is_buy => {},
|
||||
Some(_) => return false,
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
let instruction_mint = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
instruction_payload,
|
||||
&["mint", "tokenMint", "tokenAMint"],
|
||||
);
|
||||
let trade_event_mint = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
trade_event_payload,
|
||||
&["mint", "tokenMint", "tokenAMint"],
|
||||
);
|
||||
if !crate::trade_amount_resolution::optional_string_values_match(
|
||||
instruction_mint.as_deref(),
|
||||
trade_event_mint.as_deref(),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let instruction_user = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
instruction_payload,
|
||||
&["user", "actorWallet"],
|
||||
);
|
||||
let trade_event_user = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
trade_event_payload,
|
||||
&["user", "actorWallet"],
|
||||
);
|
||||
if !crate::trade_amount_resolution::optional_string_values_match(
|
||||
instruction_user.as_deref(),
|
||||
trade_event_user.as_deref(),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn optional_string_values_match(
|
||||
left: std::option::Option<&str>,
|
||||
right: std::option::Option<&str>,
|
||||
) -> bool {
|
||||
match (left, right) {
|
||||
(Some(left), Some(right)) => return left == right,
|
||||
_ => return true,
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_raydium_instruction_amount_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
@@ -1492,6 +1667,44 @@ fn extract_amount_string(
|
||||
);
|
||||
}
|
||||
|
||||
fn extract_bool_by_candidate_keys(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<bool> {
|
||||
if let Some(object) = value.as_object() {
|
||||
for candidate_key in candidate_keys {
|
||||
let direct_option = object.get(*candidate_key);
|
||||
if let Some(direct) = direct_option {
|
||||
if let Some(bool_value) = direct.as_bool() {
|
||||
return Some(bool_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested_result = crate::trade_amount_resolution::extract_bool_by_candidate_keys(
|
||||
nested_value,
|
||||
candidate_keys,
|
||||
);
|
||||
if nested_result.is_some() {
|
||||
return nested_result;
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
if let Some(array) = value.as_array() {
|
||||
for nested_value in array {
|
||||
let nested_result = crate::trade_amount_resolution::extract_bool_by_candidate_keys(
|
||||
nested_value,
|
||||
candidate_keys,
|
||||
);
|
||||
if nested_result.is_some() {
|
||||
return nested_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn extract_string_by_candidate_keys(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
|
||||
@@ -11042,6 +11042,127 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
|
||||
8,
|
||||
"decoders/pumpfun-decoder/src/instructions/admin_update_token_incentives.rs",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"add_quote_mint",
|
||||
"6f79153828185ed1",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"buy_exact_quote_in_v2",
|
||||
"c2ab1c46684d5b2f",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"buy_v2",
|
||||
"b817ee6167c5d33d",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"claim_cashback_v2",
|
||||
"7af3cc415e741d37",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"collect_creator_fee_v2",
|
||||
"cf118af204221338",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"distribute_creator_fees_v2",
|
||||
"ffcb134ff444089f",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"migrate_v2",
|
||||
"bbcb121fceedfe29",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"remove_quote_mint",
|
||||
"b141df2658d19e9b",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"sell_v2",
|
||||
"5df6823ce7e940b2",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"set_virtual_quote_reserves",
|
||||
"6587bf6809581460",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
manual_solscan_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
"pump",
|
||||
"launch",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"update_buyback_config",
|
||||
"fbe0ab92a01a71e9",
|
||||
8,
|
||||
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||
),
|
||||
upstream_git_discriminator_entry(
|
||||
"pump_fun",
|
||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||
|
||||
Reference in New Issue
Block a user