0.7.57
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1126,7 +1126,7 @@ impl DexDecodeService {
|
||||
event.instruction_id,
|
||||
"meteora_dlmm",
|
||||
event.program_id.clone(),
|
||||
"meteora_dlmm.swap",
|
||||
event.event_kind.as_str(),
|
||||
event.pool_account.clone(),
|
||||
None,
|
||||
event.token_a_mint.clone(),
|
||||
@@ -4273,6 +4273,18 @@ fn candidate_meteora_audit_pool_account(
|
||||
return accounts.get(index).cloned();
|
||||
}
|
||||
|
||||
fn is_meteora_dlmm_instruction_swap_event_kind(event_kind: &str) -> bool {
|
||||
return matches!(
|
||||
event_kind,
|
||||
"meteora_dlmm.swap"
|
||||
| "meteora_dlmm.swap2"
|
||||
| "meteora_dlmm.swap_exact_out"
|
||||
| "meteora_dlmm.swap_exact_out2"
|
||||
| "meteora_dlmm.swap_with_price_impact"
|
||||
| "meteora_dlmm.swap_with_price_impact2"
|
||||
);
|
||||
}
|
||||
|
||||
fn is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
|
||||
protocol_name: &str,
|
||||
instruction: &crate::ChainInstructionDto,
|
||||
@@ -4294,7 +4306,7 @@ fn is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
|
||||
}
|
||||
for decoded_event in decoded_events {
|
||||
if decoded_event.protocol_name == "meteora_dlmm"
|
||||
&& decoded_event.event_kind == "meteora_dlmm.swap"
|
||||
&& is_meteora_dlmm_instruction_swap_event_kind(decoded_event.event_kind.as_str())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -170,7 +170,12 @@ pub(crate) fn dex_detection_route(
|
||||
("meteora_dlmm", "meteora_dlmm.create_pool") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDlmmPool);
|
||||
},
|
||||
("meteora_dlmm", "meteora_dlmm.swap") => {
|
||||
("meteora_dlmm", "meteora_dlmm.swap")
|
||||
| ("meteora_dlmm", "meteora_dlmm.swap2")
|
||||
| ("meteora_dlmm", "meteora_dlmm.swap_exact_out")
|
||||
| ("meteora_dlmm", "meteora_dlmm.swap_exact_out2")
|
||||
| ("meteora_dlmm", "meteora_dlmm.swap_with_price_impact")
|
||||
| ("meteora_dlmm", "meteora_dlmm.swap_with_price_impact2") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDlmmPool);
|
||||
},
|
||||
("meteora_damm_v1", "meteora_damm_v1.create_pool") => {
|
||||
|
||||
@@ -323,6 +323,9 @@ pub fn is_dex_informational_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true when the event kind represents a swap-like event.
|
||||
pub fn is_dex_trade_event_kind(event_kind: &str) -> bool {
|
||||
if is_meteora_dlmm_anchor_swap_log_event_kind(event_kind) {
|
||||
return false;
|
||||
}
|
||||
if event_kind == "raydium_launchpad.trade_event" {
|
||||
return true;
|
||||
}
|
||||
@@ -376,8 +379,126 @@ pub fn is_dex_candle_candidate_event_kind(event_kind: &str) -> bool {
|
||||
return is_dex_trade_event_kind(event_kind);
|
||||
}
|
||||
|
||||
fn is_meteora_dlmm_anchor_swap_log_event_kind(event_kind: &str) -> bool {
|
||||
return matches!(event_kind, "meteora_dlmm.swap_event" | "meteora_dlmm.swap2_evt");
|
||||
}
|
||||
|
||||
fn is_meteora_dlmm_position_lifecycle_event_kind(event_kind: &str) -> bool {
|
||||
if !event_kind.starts_with("meteora_dlmm.") {
|
||||
return false;
|
||||
}
|
||||
return matches!(
|
||||
event_kind,
|
||||
"meteora_dlmm.initialize_position"
|
||||
| "meteora_dlmm.initialize_position2"
|
||||
| "meteora_dlmm.initialize_position_by_operator"
|
||||
| "meteora_dlmm.initialize_position_pda"
|
||||
| "meteora_dlmm.position_create_event"
|
||||
| "meteora_dlmm.close_position"
|
||||
| "meteora_dlmm.close_position2"
|
||||
| "meteora_dlmm.close_position_if_empty"
|
||||
| "meteora_dlmm.position_close_event"
|
||||
| "meteora_dlmm.decrease_position_length"
|
||||
| "meteora_dlmm.decrease_position_length_event"
|
||||
| "meteora_dlmm.increase_position_length"
|
||||
| "meteora_dlmm.increase_position_length2"
|
||||
| "meteora_dlmm.increase_position_length_event"
|
||||
| "meteora_dlmm.update_position_operator"
|
||||
| "meteora_dlmm.update_position_operator_event"
|
||||
| "meteora_dlmm.update_position_lock_release_point_event"
|
||||
| "meteora_dlmm.set_position_permissionless_operation_bits_evt"
|
||||
);
|
||||
}
|
||||
|
||||
fn is_meteora_dlmm_pool_lifecycle_event_kind(event_kind: &str) -> bool {
|
||||
if !event_kind.starts_with("meteora_dlmm.") {
|
||||
return false;
|
||||
}
|
||||
if is_meteora_dlmm_position_lifecycle_event_kind(event_kind) {
|
||||
return true;
|
||||
}
|
||||
return matches!(
|
||||
event_kind,
|
||||
"meteora_dlmm.create_pool"
|
||||
| "meteora_dlmm.swap_event"
|
||||
| "meteora_dlmm.swap2_evt"
|
||||
| "meteora_dlmm.lb_pair_create_event"
|
||||
| "meteora_dlmm.initialize_lb_pair"
|
||||
| "meteora_dlmm.initialize_lb_pair2"
|
||||
| "meteora_dlmm.initialize_permission_lb_pair"
|
||||
| "meteora_dlmm.initialize_customizable_permissionless_lb_pair"
|
||||
| "meteora_dlmm.initialize_customizable_permissionless_lb_pair2"
|
||||
| "meteora_dlmm.initialize_bin_array"
|
||||
| "meteora_dlmm.initialize_bin_array_bitmap_extension"
|
||||
| "meteora_dlmm.close_bin_array"
|
||||
| "meteora_dlmm.go_to_a_bin"
|
||||
| "meteora_dlmm.go_to_a_bin_event"
|
||||
| "meteora_dlmm.increase_oracle_length"
|
||||
| "meteora_dlmm.increase_observation_event"
|
||||
| "meteora_dlmm.migrate_bin_array"
|
||||
| "meteora_dlmm.migrate_position"
|
||||
);
|
||||
}
|
||||
|
||||
fn is_meteora_dlmm_admin_event_kind(event_kind: &str) -> bool {
|
||||
if !event_kind.starts_with("meteora_dlmm.") {
|
||||
return false;
|
||||
}
|
||||
if is_meteora_dlmm_pool_lifecycle_event_kind(event_kind) {
|
||||
return false;
|
||||
}
|
||||
return matches!(
|
||||
event_kind,
|
||||
"meteora_dlmm.instruction_audit"
|
||||
| "meteora_dlmm.close_claim_fee_operator_account"
|
||||
| "meteora_dlmm.close_claim_protocol_fee_operator"
|
||||
| "meteora_dlmm.close_operator_account"
|
||||
| "meteora_dlmm.close_preset_parameter"
|
||||
| "meteora_dlmm.close_preset_parameter2"
|
||||
| "meteora_dlmm.close_token_badge"
|
||||
| "meteora_dlmm.create_claim_protocol_fee_operator"
|
||||
| "meteora_dlmm.create_operator_account"
|
||||
| "meteora_dlmm.dynamic_fee_parameter_update_event"
|
||||
| "meteora_dlmm.fee_parameter_update_event"
|
||||
| "meteora_dlmm.initialize_preset_parameter"
|
||||
| "meteora_dlmm.initialize_preset_parameter2"
|
||||
| "meteora_dlmm.initialize_preset_parameter_v2"
|
||||
| "meteora_dlmm.initialize_token_badge"
|
||||
| "meteora_dlmm.set_activation_point"
|
||||
| "meteora_dlmm.set_pair_status"
|
||||
| "meteora_dlmm.set_pair_status_permissionless"
|
||||
| "meteora_dlmm.set_permissionless_operation_bits"
|
||||
| "meteora_dlmm.set_pre_activation_duration"
|
||||
| "meteora_dlmm.set_pre_activation_swap_address"
|
||||
| "meteora_dlmm.update_base_fee_parameters"
|
||||
| "meteora_dlmm.update_dynamic_fee_parameters"
|
||||
| "meteora_dlmm.update_fees_and_reward2"
|
||||
| "meteora_dlmm.update_fees_and_rewards"
|
||||
| "meteora_dlmm.update_reward_duration"
|
||||
| "meteora_dlmm.update_reward_duration_event"
|
||||
| "meteora_dlmm.update_reward_funder"
|
||||
| "meteora_dlmm.update_reward_funder_event"
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns true for liquidity lifecycle changes that must not become candles.
|
||||
pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dlmm.")
|
||||
&& (event_kind.contains("withdraw_protocol_fee")
|
||||
|| event_kind.contains("withdraw_ineligible_reward"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if is_meteora_dlmm_position_lifecycle_event_kind(event_kind) {
|
||||
return false;
|
||||
}
|
||||
if event_kind.starts_with("meteora_dlmm.")
|
||||
&& (event_kind == "meteora_dlmm.rebalance_liquidity"
|
||||
|| event_kind == "meteora_dlmm.rebalancing_event"
|
||||
|| event_kind == "meteora_dlmm.remove_all_liquidity")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if event_kind.starts_with("meteora_dbc.")
|
||||
&& (event_kind.contains("fee")
|
||||
|| event_kind.contains("surplus")
|
||||
@@ -461,6 +582,15 @@ pub fn is_dex_liquidity_add_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for liquidity remove-like DEX events.
|
||||
pub fn is_dex_liquidity_remove_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dlmm.")
|
||||
&& (event_kind.contains("withdraw_protocol_fee")
|
||||
|| event_kind.contains("withdraw_ineligible_reward"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if event_kind == "meteora_dlmm.remove_all_liquidity" {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".withdraw_pnl") {
|
||||
return false;
|
||||
}
|
||||
@@ -509,6 +639,17 @@ pub fn is_dex_position_close_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for fee collection events.
|
||||
pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dlmm.") && event_kind.contains("claim_fee_operator") {
|
||||
return false;
|
||||
}
|
||||
if event_kind.starts_with("meteora_dlmm.")
|
||||
&& (event_kind.contains("claim_fee")
|
||||
|| event_kind.contains("composition_fee")
|
||||
|| event_kind.contains("withdraw_protocol_fee")
|
||||
|| event_kind.contains("zap_protocol_fee"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if event_kind.starts_with("meteora_dbc.")
|
||||
&& (event_kind.contains("fee")
|
||||
|| event_kind.contains("surplus")
|
||||
@@ -580,6 +721,13 @@ pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for reward or incentive events.
|
||||
pub fn is_dex_reward_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dlmm.")
|
||||
&& (event_kind.contains("update_fees_and_reward")
|
||||
|| event_kind.contains("update_reward_duration")
|
||||
|| event_kind.contains("update_reward_funder"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if event_kind.starts_with("pump_fees.")
|
||||
&& (event_kind.contains("social_fee_pda_claimed")
|
||||
|| event_kind.contains("claim_social_fee_pda"))
|
||||
@@ -633,6 +781,15 @@ pub fn is_dex_orderbook_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".open_limit_order") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".place_limit_order") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".cancel_limit_order") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains("limit_order_evt") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".increase_limit_order") {
|
||||
return true;
|
||||
}
|
||||
@@ -650,6 +807,9 @@ pub fn is_dex_orderbook_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for pool, pair, launch, mint, burn or migration lifecycle events.
|
||||
pub fn is_dex_pool_lifecycle_event_kind(event_kind: &str) -> bool {
|
||||
if is_meteora_dlmm_pool_lifecycle_event_kind(event_kind) {
|
||||
return true;
|
||||
}
|
||||
if event_kind.starts_with("pump_fees.")
|
||||
&& (event_kind.contains("create_fee_sharing_config")
|
||||
|| event_kind.contains("create_social_fee_pda")
|
||||
@@ -772,6 +932,25 @@ pub fn is_dex_migration_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for pool creation or initialization events.
|
||||
pub fn is_dex_pool_creation_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dlmm.") {
|
||||
if event_kind.contains(".initialize_position") {
|
||||
return false;
|
||||
}
|
||||
if event_kind.contains(".initialize_bin_array") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".initialize_customizable_permissionless_lb_pair")
|
||||
|| event_kind.contains(".initialize_permission_lb_pair")
|
||||
|| event_kind.contains(".initialize_lb_pair")
|
||||
|| event_kind.contains(".lb_pair_create")
|
||||
|| event_kind.contains(".create_pool")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".initialize") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if event_kind == "meteora_dbc.evt_initialize_pool_event"
|
||||
|| event_kind.contains(".initialize_virtual_pool")
|
||||
{
|
||||
@@ -859,6 +1038,12 @@ pub fn is_dex_token_account_close_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for admin, configuration or permission changes.
|
||||
pub fn is_dex_admin_event_kind(event_kind: &str) -> bool {
|
||||
if is_meteora_dlmm_pool_lifecycle_event_kind(event_kind) {
|
||||
return false;
|
||||
}
|
||||
if is_meteora_dlmm_admin_event_kind(event_kind) {
|
||||
return true;
|
||||
}
|
||||
if event_kind.starts_with("meteora_dbc.")
|
||||
&& (event_kind.contains("config")
|
||||
|| event_kind.contains("operator")
|
||||
@@ -1391,6 +1576,42 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_meteora_dlmm_fee_reward_admin_without_multi_target_overlap() {
|
||||
assert!(super::is_dex_fee_event_kind("meteora_dlmm.withdraw_protocol_fee"));
|
||||
assert!(!super::is_dex_liquidity_event_kind("meteora_dlmm.withdraw_protocol_fee"));
|
||||
assert!(!super::is_dex_liquidity_remove_event_kind("meteora_dlmm.withdraw_protocol_fee"));
|
||||
assert!(!super::is_dex_fee_event_kind("meteora_dlmm.close_claim_fee_operator_account"));
|
||||
assert!(super::is_dex_admin_event_kind("meteora_dlmm.close_claim_fee_operator_account"));
|
||||
assert!(super::is_dex_reward_event_kind("meteora_dlmm.initialize_reward"));
|
||||
assert!(!super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.initialize_reward"));
|
||||
assert!(!super::is_dex_reward_event_kind("meteora_dlmm.update_fees_and_rewards"));
|
||||
assert!(super::is_dex_admin_event_kind("meteora_dlmm.update_fees_and_rewards"));
|
||||
assert!(!super::is_dex_reward_event_kind("meteora_dlmm.update_reward_duration_event"));
|
||||
assert!(super::is_dex_admin_event_kind("meteora_dlmm.update_reward_duration_event"));
|
||||
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.position_create_event"));
|
||||
assert!(!super::is_dex_liquidity_event_kind("meteora_dlmm.position_create_event"));
|
||||
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.increase_position_length"));
|
||||
assert!(!super::is_dex_liquidity_event_kind("meteora_dlmm.increase_position_length"));
|
||||
assert!(!super::is_dex_admin_event_kind("meteora_dlmm.update_position_operator"));
|
||||
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.close_bin_array"));
|
||||
assert!(super::is_dex_admin_event_kind("meteora_dlmm.create_operator_account"));
|
||||
assert!(super::is_dex_admin_event_kind(
|
||||
"meteora_dlmm.initialize_preset_parameter_v2"
|
||||
));
|
||||
assert!(super::is_dex_admin_event_kind("meteora_dlmm.instruction_audit"));
|
||||
assert!(!super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.instruction_audit"));
|
||||
assert!(!super::is_dex_fee_event_kind("meteora_dlmm.instruction_audit"));
|
||||
assert!(!super::is_dex_reward_event_kind("meteora_dlmm.instruction_audit"));
|
||||
assert!(super::is_dex_liquidity_event_kind("meteora_dlmm.rebalancing_event"));
|
||||
assert!(super::is_dex_liquidity_event_kind("meteora_dlmm.remove_all_liquidity"));
|
||||
assert!(!super::is_dex_trade_event_kind("meteora_dlmm.swap_event"));
|
||||
assert!(!super::is_dex_candle_candidate_event_kind("meteora_dlmm.swap_event"));
|
||||
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.swap_event"));
|
||||
assert!(!super::is_dex_trade_event_kind("meteora_dlmm.swap2_evt"));
|
||||
assert!(!super::is_dex_candle_candidate_event_kind("meteora_dlmm.swap2_evt"));
|
||||
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.swap2_evt"));
|
||||
}
|
||||
#[test]
|
||||
fn enriched_payload_keeps_existing_fields() {
|
||||
let payload_json = serde_json::json!({
|
||||
@@ -1514,6 +1735,10 @@ mod tests {
|
||||
super::classify_dex_event_category_code("meteora_dlmm.remove_liquidity"),
|
||||
"liquidity"
|
||||
);
|
||||
assert_eq!(
|
||||
super::classify_dex_event_category_code("meteora_dlmm.initialize_position"),
|
||||
"pool_lifecycle"
|
||||
);
|
||||
assert_eq!(
|
||||
super::classify_dex_event_lifecycle_kind_code("meteora_dlmm.initialize_position"),
|
||||
"position_open"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -200,6 +200,90 @@ fn resolve_instruction_name(
|
||||
Some(discriminator_hex) => discriminator_hex,
|
||||
None => return None,
|
||||
};
|
||||
if program_id == crate::METEORA_DLMM_PROGRAM_ID || decoder_code == Some("meteora_dlmm") {
|
||||
let name = match discriminator_hex {
|
||||
"e445a52e51cb9a1d" => "anchor_self_cpi_log",
|
||||
"b59d59438fb63448" => "add_liquidity",
|
||||
"e4a24e1c46db7473" => "add_liquidity2",
|
||||
"0703967f94283dc8" => "add_liquidity_by_strategy",
|
||||
"03dd95da6f8d76d5" => "add_liquidity_by_strategy2",
|
||||
"2905eeaf64e106cd" => "add_liquidity_by_strategy_one_side",
|
||||
"1c8cee63e7a21595" => "add_liquidity_by_weight",
|
||||
"d13b3f5b6fc899e4" => "add_liquidity_by_weight2",
|
||||
"5e9b6797465fdca5" => "add_liquidity_one_side",
|
||||
"a1c26754ab47fa9a" => "add_liquidity_one_side_precise",
|
||||
"2133a3c975627de7" => "add_liquidity_one_side_precise2",
|
||||
"849c841f4328e861" => "cancel_limit_order",
|
||||
"a9204f8988e84689" => "claim_fee",
|
||||
"70bf65ab1c907fbb" => "claim_fee2",
|
||||
"955fb5f25e5a9ea2" => "claim_reward",
|
||||
"be037f77b2579db7" => "claim_reward2",
|
||||
"44ae5850b5cc13e0" => "close_bin_array",
|
||||
"b8d5581fb3658224" => "close_claim_fee_operator_account",
|
||||
"397c249b7ef95dab" => "close_limit_order_if_empty",
|
||||
"ab09d54a7817031d" => "close_operator_account",
|
||||
"7b86510031446262" => "close_position",
|
||||
"ae5a2373ba2893e2" => "close_position2",
|
||||
"3b7cd4765b986e9d" => "close_position_if_empty",
|
||||
"04949164861ab53d" => "close_preset_parameter",
|
||||
"27195f6b7411731c" => "close_preset_parameter2",
|
||||
"6c92566eb3fe0a68" => "close_token_badge",
|
||||
"dd40f695f099e5a3" => "create_operator_account",
|
||||
"c2db882019606925" => "decrease_position_length",
|
||||
"b46945505f32496c" => "for_idl_type_generation_do_not_call",
|
||||
"bc32f9a55d97263f" => "fund_reward",
|
||||
"9248aee028fd54ae" => "go_to_a_bin",
|
||||
"be3d7d57674f9ead" => "increase_oracle_length",
|
||||
"505375d3420d2195" => "increase_position_length",
|
||||
"ffd2cc477389e171" => "increase_position_length2",
|
||||
"235613b94ed44bd3" => "initialize_bin_array",
|
||||
"2f9de2b40cf02147" => "initialize_bin_array_bitmap_extension",
|
||||
"2e2729876fb7c840" => "initialize_customizable_permissionless_lb_pair",
|
||||
"f349817e3313f16b" => "initialize_customizable_permissionless_lb_pair2",
|
||||
"2d9aedd2dd0fa65c" => "initialize_lb_pair",
|
||||
"493b2478ed536cc6" => "initialize_lb_pair2",
|
||||
"6c66d555fb033515" => "initialize_permission_lb_pair",
|
||||
"dbc0ea47bebf6650" => "initialize_position",
|
||||
"8f13f291d50f6873" => "initialize_position2",
|
||||
"fbbdbef475fe2394" => "initialize_position_by_operator",
|
||||
"2e527d92558de499" => "initialize_position_pda",
|
||||
"42bc47d3626d0eba" => "initialize_preset_parameter",
|
||||
"75c73e67068e1fcb" => "initialize_preset_parameter_v2",
|
||||
"5f87c0c4f281e644" => "initialize_reward",
|
||||
"fd4dcd5f1be059df" => "initialize_token_badge",
|
||||
"6cb021ba92e501c5" => "place_limit_order",
|
||||
"5c04b0c177b95309" => "rebalance_liquidity",
|
||||
"0a333d2370691855" => "remove_all_liquidity",
|
||||
"5055d14818ceb16c" => "remove_liquidity",
|
||||
"e6d7527ff165e392" => "remove_liquidity2",
|
||||
"1a526698f04a691a" => "remove_liquidity_by_range",
|
||||
"cc02c391359191cd" => "remove_liquidity_by_range2",
|
||||
"5bf90fa51a81fe7d" => "set_activation_point",
|
||||
"43f8e7899a95d9ae" => "set_pair_status",
|
||||
"4e3b98d346b72ed0" => "set_pair_status_permissionless",
|
||||
"543acb8ba351beba" => "set_permissionless_operation_bits",
|
||||
"a53dc9f4829f1664" => "set_pre_activation_duration",
|
||||
"398b2f7bd850df0a" => "set_pre_activation_swap_address",
|
||||
"f8c69e91e17587c8" => "swap",
|
||||
"414b3f4ceb5b5b88" => "swap2",
|
||||
"fa49652126cf4bb8" => "swap_exact_out",
|
||||
"2bd7f784893cf351" => "swap_exact_out2",
|
||||
"38ade6d0ade49ccd" => "swap_with_price_impact",
|
||||
"4a62c0d6b1334b33" => "swap_with_price_impact2",
|
||||
"4ba8dfa110c3032f" => "update_base_fee_parameters",
|
||||
"5ca12ef6ffbd1616" => "update_dynamic_fee_parameters",
|
||||
"208eb89a6741b858" => "update_fees_and_reward2",
|
||||
"9ae6fa0decd14bdf" => "update_fees_and_rewards",
|
||||
"cab8678fb4bf74d9" => "update_position_operator",
|
||||
"8aaec4a9d5ebfe6b" => "update_reward_duration",
|
||||
"d31c3020d7a02317" => "update_reward_funder",
|
||||
"94ce2ac3f7316708" => "withdraw_ineligible_reward",
|
||||
"9ec99ebd215da267" => "withdraw_protocol_fee",
|
||||
"d59bbb2238b65bf0" => "zap_protocol_fee",
|
||||
_ => "instruction_audit",
|
||||
};
|
||||
return Some(format!("meteora_dlmm.{}", name));
|
||||
}
|
||||
if program_id == crate::METEORA_DBC_PROGRAM_ID || decoder_code == Some("meteora_dbc") {
|
||||
let name = match discriminator_hex {
|
||||
"e445a52e51cb9a1d" => "meteora_dbc.anchor_self_cpi_log",
|
||||
@@ -578,6 +662,43 @@ fn option_i64_key(value: std::option::Option<i64>) -> std::string::String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn resolves_meteora_dlmm_instruction_observation_names() {
|
||||
let anchor_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DLMM_PROGRAM_ID,
|
||||
Some("meteora_dlmm"),
|
||||
Some("e445a52e51cb9a1d"),
|
||||
);
|
||||
assert_eq!(anchor_name.as_deref(), Some("meteora_dlmm.anchor_self_cpi_log"));
|
||||
let swap_exact_out2_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DLMM_PROGRAM_ID,
|
||||
Some("meteora_dlmm"),
|
||||
Some("2bd7f784893cf351"),
|
||||
);
|
||||
assert_eq!(swap_exact_out2_name.as_deref(), Some("meteora_dlmm.swap_exact_out2"));
|
||||
let close_position_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DLMM_PROGRAM_ID,
|
||||
Some("meteora_dlmm"),
|
||||
Some("7b86510031446262"),
|
||||
);
|
||||
assert_eq!(close_position_name.as_deref(), Some("meteora_dlmm.close_position"));
|
||||
let preset_v2_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DLMM_PROGRAM_ID,
|
||||
Some("meteora_dlmm"),
|
||||
Some("75c73e67068e1fcb"),
|
||||
);
|
||||
assert_eq!(
|
||||
preset_v2_name.as_deref(),
|
||||
Some("meteora_dlmm.initialize_preset_parameter_v2")
|
||||
);
|
||||
let unknown_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DLMM_PROGRAM_ID,
|
||||
Some("meteora_dlmm"),
|
||||
Some("ffffffffffffffff"),
|
||||
);
|
||||
assert_eq!(unknown_name.as_deref(), Some("meteora_dlmm.instruction_audit"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_meteora_dbc_instruction_observation_names() {
|
||||
let anchor_name = super::resolve_instruction_name(
|
||||
|
||||
@@ -71,10 +71,14 @@ fn should_skip_non_trade_event_due_to_explicit_reason(
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if crate::is_dex_orderbook_event_kind(decoded_event.event_kind.as_str())
|
||||
&& payload_has_non_empty_text(payload, "skipOrderbookReason")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
fn should_attempt_meteora_dbc_explicit_skip_materialization(
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
payload: &serde_json::Value,
|
||||
@@ -121,7 +125,6 @@ fn is_meteora_dbc_instruction_admin_materialization_candidate(event_kind: &str)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum FeeAmountRecoveryPolicy {
|
||||
Disabled,
|
||||
@@ -151,11 +154,43 @@ fn fee_amount_recovery_policy_for_event_kind(event_kind: &str) -> FeeAmountRecov
|
||||
| "raydium_launchpad.claim_platform_fee_from_vault"
|
||||
| "raydium_launchpad.collect_fee"
|
||||
| "raydium_stable_swap.withdraw_pnl"
|
||||
| "raydium_stable_swap.withdraw_srm" => FeeAmountRecoveryPolicy::InnerSplTransfer,
|
||||
| "raydium_stable_swap.withdraw_srm"
|
||||
| "meteora_dlmm.claim_fee"
|
||||
| "meteora_dlmm.claim_fee2"
|
||||
| "meteora_dlmm.claim_fee_event"
|
||||
| "meteora_dlmm.claim_fee2_event"
|
||||
| "meteora_dlmm.composition_fee_event"
|
||||
| "meteora_dlmm.withdraw_protocol_fee"
|
||||
| "meteora_dlmm.zap_protocol_fee" => FeeAmountRecoveryPolicy::InnerSplTransfer,
|
||||
_ => FeeAmountRecoveryPolicy::Disabled,
|
||||
};
|
||||
}
|
||||
|
||||
fn reward_amount_recovery_policy_for_event_kind(event_kind: &str) -> FeeAmountRecoveryPolicy {
|
||||
return match event_kind {
|
||||
"meteora_dlmm.claim_reward"
|
||||
| "meteora_dlmm.claim_reward2"
|
||||
| "meteora_dlmm.claim_reward_event"
|
||||
| "meteora_dlmm.claim_reward2_event"
|
||||
| "meteora_dlmm.fund_reward"
|
||||
| "meteora_dlmm.fund_reward_event"
|
||||
| "meteora_dlmm.initialize_reward"
|
||||
| "meteora_dlmm.initialize_reward_event"
|
||||
| "meteora_dlmm.withdraw_ineligible_reward"
|
||||
| "meteora_dlmm.withdraw_ineligible_reward_event" => {
|
||||
FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
},
|
||||
_ => FeeAmountRecoveryPolicy::Disabled,
|
||||
};
|
||||
}
|
||||
|
||||
fn optional_text_is_missing(value: &std::option::Option<std::string::String>) -> bool {
|
||||
match value.as_ref() {
|
||||
Some(value) => return value.trim().is_empty(),
|
||||
None => return true,
|
||||
}
|
||||
}
|
||||
|
||||
fn payload_has_non_empty_text(payload: &serde_json::Value, key: &str) -> bool {
|
||||
let value = match payload.get(key) {
|
||||
Some(value) => value,
|
||||
@@ -240,7 +275,10 @@ impl NonTradeEventMaterializationService {
|
||||
continue;
|
||||
}
|
||||
if should_skip_non_trade_event_due_to_explicit_reason(decoded_event, &payload)
|
||||
&& !should_attempt_meteora_dbc_explicit_skip_materialization(decoded_event, &payload)
|
||||
&& !should_attempt_meteora_dbc_explicit_skip_materialization(
|
||||
decoded_event,
|
||||
&payload,
|
||||
)
|
||||
{
|
||||
tracing::debug!(
|
||||
event_kind = %decoded_event.event_kind,
|
||||
@@ -534,10 +572,44 @@ impl NonTradeEventMaterializationService {
|
||||
],
|
||||
);
|
||||
let mut payload_json = decoded_event.payload_json.clone();
|
||||
let mut fee_amount_legs: std::vec::Vec<MeteoraDbcActualAmount> = std::vec::Vec::new();
|
||||
if is_meteora_dbc_instruction_fee_materialization_candidate(decoded_event.event_kind.as_str()) {
|
||||
let mut fee_amount_legs: std::vec::Vec<MeteoraDbcActualAmount> =
|
||||
fee_amount_legs_from_payload(payload);
|
||||
if !fee_amount_legs.is_empty() {
|
||||
match fee_amount_legs.as_slice() {
|
||||
[first_leg] => {
|
||||
fee_token_mint = Some(first_leg.token_mint.clone());
|
||||
fee_amount_raw = Some(first_leg.amount_raw.clone());
|
||||
},
|
||||
_ => {
|
||||
fee_token_mint = None;
|
||||
fee_amount_raw = None;
|
||||
},
|
||||
}
|
||||
let updated_payload =
|
||||
decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
|
||||
payload_json = updated_payload.to_string();
|
||||
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
|
||||
self.database.as_ref(),
|
||||
decoded_event_id,
|
||||
payload_json.as_str(),
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = update_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if fee_amount_legs.is_empty()
|
||||
&& is_meteora_dbc_instruction_fee_materialization_candidate(
|
||||
decoded_event.event_kind.as_str(),
|
||||
)
|
||||
{
|
||||
let inferred_result = self
|
||||
.resolve_meteora_dbc_actual_fee_amounts(transaction, transaction_id, decoded_event, payload)
|
||||
.resolve_meteora_dbc_actual_fee_amounts(
|
||||
transaction,
|
||||
transaction_id,
|
||||
decoded_event,
|
||||
payload,
|
||||
)
|
||||
.await;
|
||||
let inferred = match inferred_result {
|
||||
Ok(inferred) => inferred,
|
||||
@@ -555,7 +627,8 @@ impl NonTradeEventMaterializationService {
|
||||
fee_amount_raw = None;
|
||||
},
|
||||
}
|
||||
let updated_payload = decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
|
||||
let updated_payload =
|
||||
decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
|
||||
payload_json = updated_payload.to_string();
|
||||
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
|
||||
self.database.as_ref(),
|
||||
@@ -597,11 +670,14 @@ impl NonTradeEventMaterializationService {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
let recovery_policy = fee_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str());
|
||||
let recovery_policy =
|
||||
fee_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str());
|
||||
if fee_amount_legs.is_empty()
|
||||
&& fee_amount_raw.is_none()
|
||||
&& recovery_policy != FeeAmountRecoveryPolicy::Disabled
|
||||
&& !is_meteora_dbc_instruction_fee_materialization_candidate(decoded_event.event_kind.as_str())
|
||||
&& !is_meteora_dbc_instruction_fee_materialization_candidate(
|
||||
decoded_event.event_kind.as_str(),
|
||||
)
|
||||
{
|
||||
let inferred_result = self
|
||||
.resolve_allowlisted_actual_fee_amounts_from_spl_transfers(
|
||||
@@ -626,7 +702,8 @@ impl NonTradeEventMaterializationService {
|
||||
fee_amount_raw = None;
|
||||
},
|
||||
}
|
||||
let updated_payload = decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
|
||||
let updated_payload =
|
||||
decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
|
||||
payload_json = updated_payload.to_string();
|
||||
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
|
||||
self.database.as_ref(),
|
||||
@@ -650,10 +727,11 @@ impl NonTradeEventMaterializationService {
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if let Some(explanation) = explanation {
|
||||
let updated_payload = decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
|
||||
payload,
|
||||
explanation.as_str(),
|
||||
);
|
||||
let updated_payload =
|
||||
decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
|
||||
payload,
|
||||
explanation.as_str(),
|
||||
);
|
||||
payload_json = updated_payload.to_string();
|
||||
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
|
||||
self.database.as_ref(),
|
||||
@@ -781,10 +859,13 @@ impl NonTradeEventMaterializationService {
|
||||
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
|
||||
None => None,
|
||||
};
|
||||
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result =
|
||||
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
|
||||
.await;
|
||||
let instruction_index =
|
||||
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
|
||||
self.database.as_ref(),
|
||||
transaction_id,
|
||||
)
|
||||
.await;
|
||||
let instructions = match instructions_result {
|
||||
Ok(instructions) => instructions,
|
||||
Err(error) => return Err(error),
|
||||
@@ -855,10 +936,13 @@ impl NonTradeEventMaterializationService {
|
||||
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
|
||||
None => None,
|
||||
};
|
||||
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result =
|
||||
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
|
||||
.await;
|
||||
let instruction_index =
|
||||
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
|
||||
self.database.as_ref(),
|
||||
transaction_id,
|
||||
)
|
||||
.await;
|
||||
let instructions = match instructions_result {
|
||||
Ok(instructions) => instructions,
|
||||
Err(error) => return Err(error),
|
||||
@@ -929,10 +1013,13 @@ impl NonTradeEventMaterializationService {
|
||||
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
|
||||
None => None,
|
||||
};
|
||||
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result =
|
||||
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
|
||||
.await;
|
||||
let instruction_index =
|
||||
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
|
||||
self.database.as_ref(),
|
||||
transaction_id,
|
||||
)
|
||||
.await;
|
||||
let instructions = match instructions_result {
|
||||
Ok(instructions) => instructions,
|
||||
Err(error) => return Err(error),
|
||||
@@ -988,9 +1075,7 @@ impl NonTradeEventMaterializationService {
|
||||
non_zero_mints.insert(mint);
|
||||
}
|
||||
if non_zero_mints.len() > 1 {
|
||||
return Ok(Some(
|
||||
"multi_token_fee_amounts_materialized_as_fee_amount_legs".to_string(),
|
||||
));
|
||||
return Ok(Some("multi_token_fee_amounts_materialized_as_fee_amount_legs".to_string()));
|
||||
}
|
||||
if transfer_count > 0 && non_zero_transfer_count == 0 {
|
||||
return Ok(Some("fee_instruction_has_only_zero_amount_transfers".to_string()));
|
||||
@@ -1001,6 +1086,86 @@ impl NonTradeEventMaterializationService {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
async fn resolve_allowlisted_actual_reward_amount_from_spl_transfers(
|
||||
&self,
|
||||
transaction_id: i64,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
payload: &serde_json::Value,
|
||||
) -> Result<std::option::Option<MeteoraDbcActualAmount>, crate::Error> {
|
||||
let parent_instruction_id = match decoded_event.id {
|
||||
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
|
||||
None => None,
|
||||
};
|
||||
let instruction_index =
|
||||
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
|
||||
self.database.as_ref(),
|
||||
transaction_id,
|
||||
)
|
||||
.await;
|
||||
let instructions = match instructions_result {
|
||||
Ok(instructions) => instructions,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let mut amount_legs = std::vec::Vec::new();
|
||||
for instruction in &instructions {
|
||||
if !instruction_is_child_of_decoded_event(
|
||||
instruction,
|
||||
parent_instruction_id,
|
||||
instruction_index,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let parsed_type = match instruction.parsed_type.as_deref() {
|
||||
Some(parsed_type) => parsed_type,
|
||||
None => continue,
|
||||
};
|
||||
if parsed_type != "transferChecked" && parsed_type != "transfer" {
|
||||
continue;
|
||||
}
|
||||
let parsed_json = match instruction.parsed_json.as_deref() {
|
||||
Some(parsed_json) => parsed_json,
|
||||
None => continue,
|
||||
};
|
||||
let parsed_value = serde_json::from_str::<serde_json::Value>(parsed_json);
|
||||
let parsed_value = match parsed_value {
|
||||
Ok(parsed_value) => parsed_value,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let amount_raw = extract_spl_transfer_amount_raw(&parsed_value);
|
||||
let amount_raw = match amount_raw {
|
||||
Some(amount_raw) => amount_raw,
|
||||
None => continue,
|
||||
};
|
||||
let amount = amount_raw.parse::<u128>();
|
||||
let amount = match amount {
|
||||
Ok(amount) => amount,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if amount == 0 {
|
||||
continue;
|
||||
}
|
||||
let mint = extract_first_string(&parsed_value, &["mint"]);
|
||||
let mint = match mint {
|
||||
Some(mint) => mint,
|
||||
None => continue,
|
||||
};
|
||||
let source_account = extract_first_string(&parsed_value, &["source"]);
|
||||
let destination_account = extract_first_string(&parsed_value, &["destination"]);
|
||||
amount_legs.push(MeteoraDbcActualAmount {
|
||||
token_mint: mint,
|
||||
amount_raw,
|
||||
source: "allowlisted_reward_inner_spl_transfer".to_string(),
|
||||
source_account,
|
||||
destination_account,
|
||||
});
|
||||
}
|
||||
return Ok(single_mint_amount_from_actual_amounts(
|
||||
amount_legs.as_slice(),
|
||||
"allowlisted_reward_inner_spl_transfer",
|
||||
));
|
||||
}
|
||||
|
||||
async fn resolve_meteora_dbc_unmaterialized_fee_skip_reason(
|
||||
&self,
|
||||
transaction_id: i64,
|
||||
@@ -1011,10 +1176,13 @@ impl NonTradeEventMaterializationService {
|
||||
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
|
||||
None => None,
|
||||
};
|
||||
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result =
|
||||
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
|
||||
.await;
|
||||
let instruction_index =
|
||||
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
|
||||
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
|
||||
self.database.as_ref(),
|
||||
transaction_id,
|
||||
)
|
||||
.await;
|
||||
let instructions = match instructions_result {
|
||||
Ok(instructions) => instructions,
|
||||
Err(error) => return Err(error),
|
||||
@@ -1070,9 +1238,7 @@ impl NonTradeEventMaterializationService {
|
||||
non_zero_mints.insert(mint);
|
||||
}
|
||||
if non_zero_mints.len() > 1 {
|
||||
return Ok(Some(
|
||||
"multi_token_fee_amounts_require_multiple_fee_rows".to_string(),
|
||||
));
|
||||
return Ok(Some("multi_token_fee_amounts_require_multiple_fee_rows".to_string()));
|
||||
}
|
||||
if transfer_count > 0 && non_zero_transfer_count == 0 {
|
||||
return Ok(Some("fee_instruction_has_only_zero_amount_transfers".to_string()));
|
||||
@@ -1112,11 +1278,11 @@ impl NonTradeEventMaterializationService {
|
||||
"user",
|
||||
],
|
||||
);
|
||||
let reward_token_mint = extract_first_string(
|
||||
let mut reward_token_mint = extract_first_string(
|
||||
payload,
|
||||
&["rewardTokenMint", "reward_token_mint", "tokenMint", "token_mint", "mint"],
|
||||
);
|
||||
let reward_amount_raw = extract_first_amount_string(
|
||||
let mut reward_amount_raw = extract_first_amount_string(
|
||||
payload,
|
||||
&[
|
||||
"rewardAmountRaw",
|
||||
@@ -1128,6 +1294,35 @@ impl NonTradeEventMaterializationService {
|
||||
"amount",
|
||||
],
|
||||
);
|
||||
let mut reward_payload_json = decoded_event.payload_json.clone();
|
||||
if reward_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str())
|
||||
== FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
&& (optional_text_is_missing(&reward_token_mint)
|
||||
|| optional_text_is_missing(&reward_amount_raw))
|
||||
{
|
||||
let recovered_reward_result = self
|
||||
.resolve_allowlisted_actual_reward_amount_from_spl_transfers(
|
||||
transaction_id,
|
||||
decoded_event,
|
||||
payload,
|
||||
)
|
||||
.await;
|
||||
let recovered_reward = match recovered_reward_result {
|
||||
Ok(recovered_reward) => recovered_reward,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if let Some(recovered_reward) = recovered_reward {
|
||||
if optional_text_is_missing(&reward_token_mint) {
|
||||
reward_token_mint = Some(recovered_reward.token_mint.clone());
|
||||
}
|
||||
if optional_text_is_missing(&reward_amount_raw) {
|
||||
reward_amount_raw = Some(recovered_reward.amount_raw.clone());
|
||||
}
|
||||
reward_payload_json =
|
||||
decoded_payload_with_materialized_reward_amount(payload, &recovered_reward)
|
||||
.to_string();
|
||||
}
|
||||
}
|
||||
let dto = crate::RewardEventDto::new(
|
||||
transaction_id,
|
||||
Some(decoded_event_id),
|
||||
@@ -1143,7 +1338,7 @@ impl NonTradeEventMaterializationService {
|
||||
actor_wallet,
|
||||
reward_token_mint,
|
||||
reward_amount_raw,
|
||||
decoded_event.payload_json.clone(),
|
||||
reward_payload_json,
|
||||
);
|
||||
let upsert_result = crate::query_reward_events_upsert(self.database.as_ref(), &dto).await;
|
||||
match upsert_result {
|
||||
@@ -2100,6 +2295,119 @@ struct MeteoraDbcActualAmount {
|
||||
destination_account: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
fn single_mint_amount_from_actual_amounts(
|
||||
amount_legs: &[MeteoraDbcActualAmount],
|
||||
source: &str,
|
||||
) -> std::option::Option<MeteoraDbcActualAmount> {
|
||||
let mut token_mint: std::option::Option<std::string::String> = None;
|
||||
let mut amount_total: u128 = 0;
|
||||
let mut source_account: std::option::Option<std::string::String> = None;
|
||||
let mut destination_account: std::option::Option<std::string::String> = None;
|
||||
for leg in amount_legs {
|
||||
if leg.token_mint.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let amount = leg.amount_raw.parse::<u128>();
|
||||
let amount = match amount {
|
||||
Ok(amount) => amount,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if amount == 0 {
|
||||
continue;
|
||||
}
|
||||
match token_mint.as_ref() {
|
||||
Some(existing_mint) => {
|
||||
if existing_mint != &leg.token_mint {
|
||||
return None;
|
||||
}
|
||||
},
|
||||
None => token_mint = Some(leg.token_mint.clone()),
|
||||
}
|
||||
let checked_total = amount_total.checked_add(amount);
|
||||
amount_total = match checked_total {
|
||||
Some(checked_total) => checked_total,
|
||||
None => return None,
|
||||
};
|
||||
if source_account.is_none() {
|
||||
source_account = leg.source_account.clone();
|
||||
}
|
||||
if destination_account.is_none() {
|
||||
destination_account = leg.destination_account.clone();
|
||||
}
|
||||
}
|
||||
let token_mint = match token_mint {
|
||||
Some(token_mint) => token_mint,
|
||||
None => return None,
|
||||
};
|
||||
if amount_total == 0 {
|
||||
return None;
|
||||
}
|
||||
return Some(MeteoraDbcActualAmount {
|
||||
token_mint,
|
||||
amount_raw: amount_total.to_string(),
|
||||
source: source.to_string(),
|
||||
source_account,
|
||||
destination_account,
|
||||
});
|
||||
}
|
||||
|
||||
fn fee_amount_legs_from_payload(
|
||||
payload: &serde_json::Value,
|
||||
) -> std::vec::Vec<MeteoraDbcActualAmount> {
|
||||
let mut legs = std::vec::Vec::new();
|
||||
let candidate_arrays = ["feeAmountLegs", "materializedFeeAmountLegs"];
|
||||
for candidate_key in candidate_arrays {
|
||||
let candidate = match payload.get(candidate_key) {
|
||||
Some(candidate) => candidate,
|
||||
None => continue,
|
||||
};
|
||||
let array = match candidate.as_array() {
|
||||
Some(array) => array,
|
||||
None => continue,
|
||||
};
|
||||
for leg_value in array {
|
||||
let token_mint = extract_first_string(
|
||||
leg_value,
|
||||
&["tokenMint", "token_mint", "feeTokenMint", "fee_token_mint", "mint"],
|
||||
);
|
||||
let amount_raw = extract_first_amount_string(
|
||||
leg_value,
|
||||
&["amountRaw", "amount_raw", "feeAmountRaw", "fee_amount_raw", "amount"],
|
||||
);
|
||||
let token_mint = match token_mint {
|
||||
Some(token_mint) => token_mint,
|
||||
None => continue,
|
||||
};
|
||||
let amount_raw = match amount_raw {
|
||||
Some(amount_raw) => amount_raw,
|
||||
None => continue,
|
||||
};
|
||||
let amount_trimmed = amount_raw.trim();
|
||||
if amount_trimmed.is_empty() || amount_trimmed == "0" {
|
||||
continue;
|
||||
}
|
||||
let source =
|
||||
match extract_first_string(leg_value, &["amountSource", "amount_source", "source"])
|
||||
{
|
||||
Some(source) => source,
|
||||
None => "decoded_fee_amount_leg".to_string(),
|
||||
};
|
||||
let source_account =
|
||||
extract_first_string(leg_value, &["sourceAccount", "source_account"]);
|
||||
let destination_account =
|
||||
extract_first_string(leg_value, &["destinationAccount", "destination_account"]);
|
||||
legs.push(MeteoraDbcActualAmount {
|
||||
token_mint,
|
||||
amount_raw: amount_trimmed.to_string(),
|
||||
source,
|
||||
source_account,
|
||||
destination_account,
|
||||
});
|
||||
}
|
||||
}
|
||||
return legs;
|
||||
}
|
||||
|
||||
fn instruction_is_child_of_decoded_event(
|
||||
instruction: &crate::ChainInstructionDto,
|
||||
parent_instruction_id: std::option::Option<i64>,
|
||||
@@ -2147,6 +2455,57 @@ fn extract_spl_transfer_amount_raw(
|
||||
return None;
|
||||
}
|
||||
|
||||
fn decoded_payload_with_materialized_reward_amount(
|
||||
payload: &serde_json::Value,
|
||||
reward_amount: &MeteoraDbcActualAmount,
|
||||
) -> serde_json::Value {
|
||||
let mut object = match payload.clone() {
|
||||
serde_json::Value::Object(object) => object,
|
||||
other => {
|
||||
let mut object = serde_json::Map::new();
|
||||
object.insert("rawPayload".to_string(), other);
|
||||
object
|
||||
},
|
||||
};
|
||||
object.insert("skipRewardReason".to_string(), serde_json::Value::String(String::new()));
|
||||
object.insert("rewardMaterialized".to_string(), serde_json::Value::Bool(true));
|
||||
object.insert(
|
||||
"rewardMaterializationSource".to_string(),
|
||||
serde_json::Value::String(reward_amount.source.clone()),
|
||||
);
|
||||
object.insert(
|
||||
"rewardTokenMint".to_string(),
|
||||
serde_json::Value::String(reward_amount.token_mint.clone()),
|
||||
);
|
||||
object.insert(
|
||||
"rewardAmountRaw".to_string(),
|
||||
serde_json::Value::String(reward_amount.amount_raw.clone()),
|
||||
);
|
||||
match reward_amount.source_account.as_ref() {
|
||||
Some(source_account) => {
|
||||
object.insert(
|
||||
"rewardSourceAccount".to_string(),
|
||||
serde_json::Value::String(source_account.clone()),
|
||||
);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
match reward_amount.destination_account.as_ref() {
|
||||
Some(destination_account) => {
|
||||
object.insert(
|
||||
"rewardDestinationAccount".to_string(),
|
||||
serde_json::Value::String(destination_account.clone()),
|
||||
);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
object.insert(
|
||||
"eventActionability".to_string(),
|
||||
serde_json::Value::String("reward_materialized".to_string()),
|
||||
);
|
||||
return serde_json::Value::Object(object);
|
||||
}
|
||||
|
||||
fn decoded_payload_with_materialized_fee_legs(
|
||||
payload: &serde_json::Value,
|
||||
legs: &[MeteoraDbcActualAmount],
|
||||
@@ -2159,11 +2518,11 @@ fn decoded_payload_with_materialized_fee_legs(
|
||||
object
|
||||
},
|
||||
};
|
||||
object.insert("skipFeeReason".to_string(), serde_json::Value::String(std::string::String::new()));
|
||||
object.insert(
|
||||
"feeMaterialized".to_string(),
|
||||
serde_json::Value::Bool(true),
|
||||
"skipFeeReason".to_string(),
|
||||
serde_json::Value::String(std::string::String::new()),
|
||||
);
|
||||
object.insert("feeMaterialized".to_string(), serde_json::Value::Bool(true));
|
||||
object.insert(
|
||||
"feeMaterializationSource".to_string(),
|
||||
serde_json::Value::String("fee_event_amounts".to_string()),
|
||||
@@ -2217,18 +2576,9 @@ fn fee_amount_leg_payload_json(
|
||||
"legIndex".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(u64::from(leg_index))),
|
||||
);
|
||||
object.insert(
|
||||
"tokenMint".to_string(),
|
||||
serde_json::Value::String(leg.token_mint.clone()),
|
||||
);
|
||||
object.insert(
|
||||
"amountRaw".to_string(),
|
||||
serde_json::Value::String(leg.amount_raw.clone()),
|
||||
);
|
||||
object.insert(
|
||||
"amountSource".to_string(),
|
||||
serde_json::Value::String(leg.source.clone()),
|
||||
);
|
||||
object.insert("tokenMint".to_string(), serde_json::Value::String(leg.token_mint.clone()));
|
||||
object.insert("amountRaw".to_string(), serde_json::Value::String(leg.amount_raw.clone()));
|
||||
object.insert("amountSource".to_string(), serde_json::Value::String(leg.source.clone()));
|
||||
match leg.source_account.as_ref() {
|
||||
Some(source_account) => {
|
||||
object.insert(
|
||||
@@ -2250,7 +2600,6 @@ fn fee_amount_leg_payload_json(
|
||||
return serde_json::Value::Object(object).to_string();
|
||||
}
|
||||
|
||||
|
||||
fn fee_event_amount_leg_from_parent_amount(
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
transaction_id: i64,
|
||||
@@ -2272,7 +2621,11 @@ fn fee_event_amount_leg_from_parent_amount(
|
||||
if fee_amount_raw.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
let payload_json = fee_amount_parent_payload_json(decoded_event, fee_token_mint.as_str(), fee_amount_raw.as_str());
|
||||
let payload_json = fee_amount_parent_payload_json(
|
||||
decoded_event,
|
||||
fee_token_mint.as_str(),
|
||||
fee_amount_raw.as_str(),
|
||||
);
|
||||
return Some(crate::FeeEventAmountDto::new(
|
||||
0,
|
||||
transaction_id,
|
||||
@@ -2302,14 +2655,8 @@ fn fee_amount_parent_payload_json(
|
||||
"legIndex".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(0_u64)),
|
||||
);
|
||||
object.insert(
|
||||
"tokenMint".to_string(),
|
||||
serde_json::Value::String(token_mint.to_string()),
|
||||
);
|
||||
object.insert(
|
||||
"amountRaw".to_string(),
|
||||
serde_json::Value::String(amount_raw.to_string()),
|
||||
);
|
||||
object.insert("tokenMint".to_string(), serde_json::Value::String(token_mint.to_string()));
|
||||
object.insert("amountRaw".to_string(), serde_json::Value::String(amount_raw.to_string()));
|
||||
object.insert(
|
||||
"amountSource".to_string(),
|
||||
serde_json::Value::String("parent_fee_event_amount".to_string()),
|
||||
@@ -2329,14 +2676,8 @@ fn decoded_payload_with_unmaterialized_fee_reason(
|
||||
object
|
||||
},
|
||||
};
|
||||
object.insert(
|
||||
"skipFeeReason".to_string(),
|
||||
serde_json::Value::String(reason.to_string()),
|
||||
);
|
||||
object.insert(
|
||||
"feeMaterialized".to_string(),
|
||||
serde_json::Value::Bool(false),
|
||||
);
|
||||
object.insert("skipFeeReason".to_string(), serde_json::Value::String(reason.to_string()));
|
||||
object.insert("feeMaterialized".to_string(), serde_json::Value::Bool(false));
|
||||
object.insert(
|
||||
"feeMaterializationSource".to_string(),
|
||||
serde_json::Value::String("none".to_string()),
|
||||
@@ -2368,10 +2709,7 @@ fn decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
|
||||
"feeAmountRecoveryReason".to_string(),
|
||||
serde_json::Value::String(reason.to_string()),
|
||||
);
|
||||
object.insert(
|
||||
"feeAmountLegsMaterialized".to_string(),
|
||||
serde_json::Value::Bool(false),
|
||||
);
|
||||
object.insert("feeAmountLegsMaterialized".to_string(), serde_json::Value::Bool(false));
|
||||
object.insert(
|
||||
"feeAmountLegCount".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(0_u64)),
|
||||
@@ -2387,7 +2725,8 @@ fn resolve_meteora_dbc_actual_fee_amount_from_lamport_delta(
|
||||
if !decoded_event.event_kind.contains("pool_creation_fee") {
|
||||
return None;
|
||||
}
|
||||
let transaction_value = serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
|
||||
let transaction_value =
|
||||
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
|
||||
let transaction_value = match transaction_value {
|
||||
Ok(transaction_value) => transaction_value,
|
||||
Err(_) => return None,
|
||||
@@ -2408,7 +2747,8 @@ fn resolve_meteora_dbc_actual_fee_amount_from_lamport_delta(
|
||||
append_unique_candidate_account(&mut candidate_accounts, decoded_event.pool_account.as_deref());
|
||||
let payload_pool_account = extract_first_string(payload, &["poolAccount", "pool", "poolState"]);
|
||||
append_unique_candidate_account(&mut candidate_accounts, payload_pool_account.as_deref());
|
||||
let payload_related_account = extract_first_string(payload, &["relatedAccount", "receiver", "feeReceiver"]);
|
||||
let payload_related_account =
|
||||
extract_first_string(payload, &["relatedAccount", "receiver", "feeReceiver"]);
|
||||
append_unique_candidate_account(&mut candidate_accounts, payload_related_account.as_deref());
|
||||
if let Some(accounts) = payload.get("accounts").and_then(serde_json::Value::as_array) {
|
||||
for account in accounts {
|
||||
@@ -3325,6 +3665,155 @@ mod tests {
|
||||
super::fee_amount_recovery_policy_for_event_kind("meteora_dbc.claim_trading_fee"),
|
||||
super::FeeAmountRecoveryPolicy::Disabled
|
||||
);
|
||||
assert_eq!(
|
||||
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_fee"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_fee2"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.composition_fee_event"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.withdraw_protocol_fee"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.zap_protocol_fee"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::fee_amount_recovery_policy_for_event_kind(
|
||||
"meteora_dlmm.close_claim_fee_operator_account"
|
||||
),
|
||||
super::FeeAmountRecoveryPolicy::Disabled
|
||||
);
|
||||
assert_eq!(
|
||||
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.swap_event"),
|
||||
super::FeeAmountRecoveryPolicy::Disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reward_amount_recovery_policy_is_allowlisted_only() {
|
||||
assert_eq!(
|
||||
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward2"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward_event"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.fund_reward"),
|
||||
super::FeeAmountRecoveryPolicy::InnerSplTransfer
|
||||
);
|
||||
assert_eq!(
|
||||
super::reward_amount_recovery_policy_for_event_kind(
|
||||
"meteora_dlmm.update_fees_and_rewards"
|
||||
),
|
||||
super::FeeAmountRecoveryPolicy::Disabled
|
||||
);
|
||||
assert_eq!(
|
||||
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.swap_event"),
|
||||
super::FeeAmountRecoveryPolicy::Disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_mint_reward_amount_sums_matching_mints_only() {
|
||||
let legs = vec![
|
||||
super::MeteoraDbcActualAmount {
|
||||
token_mint: "RewardMint111".to_string(),
|
||||
amount_raw: "7".to_string(),
|
||||
source: "test".to_string(),
|
||||
source_account: Some("Source111".to_string()),
|
||||
destination_account: Some("Destination111".to_string()),
|
||||
},
|
||||
super::MeteoraDbcActualAmount {
|
||||
token_mint: "RewardMint111".to_string(),
|
||||
amount_raw: "5".to_string(),
|
||||
source: "test".to_string(),
|
||||
source_account: Some("Source222".to_string()),
|
||||
destination_account: Some("Destination222".to_string()),
|
||||
},
|
||||
];
|
||||
let resolved = super::single_mint_amount_from_actual_amounts(
|
||||
legs.as_slice(),
|
||||
"allowlisted_reward_inner_spl_transfer",
|
||||
);
|
||||
let resolved = match resolved {
|
||||
Some(resolved) => resolved,
|
||||
None => panic!("expected single mint reward amount"),
|
||||
};
|
||||
assert_eq!(resolved.token_mint, "RewardMint111");
|
||||
assert_eq!(resolved.amount_raw, "12");
|
||||
assert_eq!(resolved.source, "allowlisted_reward_inner_spl_transfer");
|
||||
|
||||
let mixed_mint_legs = vec![
|
||||
super::MeteoraDbcActualAmount {
|
||||
token_mint: "RewardMint111".to_string(),
|
||||
amount_raw: "7".to_string(),
|
||||
source: "test".to_string(),
|
||||
source_account: None,
|
||||
destination_account: None,
|
||||
},
|
||||
super::MeteoraDbcActualAmount {
|
||||
token_mint: "OtherMint111".to_string(),
|
||||
amount_raw: "5".to_string(),
|
||||
source: "test".to_string(),
|
||||
source_account: None,
|
||||
destination_account: None,
|
||||
},
|
||||
];
|
||||
assert!(
|
||||
super::single_mint_amount_from_actual_amounts(
|
||||
mixed_mint_legs.as_slice(),
|
||||
"allowlisted_reward_inner_spl_transfer",
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoded_payload_with_materialized_reward_amount_marks_source() {
|
||||
let payload = serde_json::json!({
|
||||
"eventKind": "meteora_dlmm.claim_reward"
|
||||
});
|
||||
let reward_amount = super::MeteoraDbcActualAmount {
|
||||
token_mint: "RewardMint111".to_string(),
|
||||
amount_raw: "42".to_string(),
|
||||
source: "allowlisted_reward_inner_spl_transfer".to_string(),
|
||||
source_account: Some("Source111".to_string()),
|
||||
destination_account: Some("Destination111".to_string()),
|
||||
};
|
||||
let materialized =
|
||||
super::decoded_payload_with_materialized_reward_amount(&payload, &reward_amount);
|
||||
assert_eq!(
|
||||
materialized.get("rewardTokenMint").and_then(serde_json::Value::as_str),
|
||||
Some("RewardMint111")
|
||||
);
|
||||
assert_eq!(
|
||||
materialized.get("rewardAmountRaw").and_then(serde_json::Value::as_str),
|
||||
Some("42")
|
||||
);
|
||||
assert_eq!(
|
||||
materialized
|
||||
.get("rewardMaterializationSource")
|
||||
.and_then(serde_json::Value::as_str),
|
||||
Some("allowlisted_reward_inner_spl_transfer")
|
||||
);
|
||||
assert_eq!(
|
||||
materialized.get("eventActionability").and_then(serde_json::Value::as_str),
|
||||
Some("reward_materialized")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user