0.7.49
This commit is contained in:
@@ -19,6 +19,8 @@ pub struct NonTradeEventMaterializationResult {
|
||||
pub reward_event_count: usize,
|
||||
/// Number of pool administration events inserted or refreshed.
|
||||
pub pool_admin_event_count: usize,
|
||||
/// Number of orderbook or limit-order events inserted or refreshed.
|
||||
pub orderbook_event_count: usize,
|
||||
}
|
||||
|
||||
/// Materializes useful non-trade decoded DEX events.
|
||||
@@ -61,7 +63,7 @@ impl NonTradeEventMaterializationService {
|
||||
)));
|
||||
},
|
||||
};
|
||||
if transaction.err_json.is_some() {
|
||||
if transaction_has_effective_error(&transaction) {
|
||||
tracing::debug!(
|
||||
signature = %transaction.signature,
|
||||
"skipping non-trade materialization for failed transaction"
|
||||
@@ -189,6 +191,24 @@ impl NonTradeEventMaterializationService {
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
if crate::is_dex_orderbook_event_kind(decoded_event.event_kind.as_str()) {
|
||||
let materialized = self
|
||||
.materialize_orderbook_event(
|
||||
&transaction,
|
||||
transaction_id,
|
||||
decoded_event,
|
||||
&payload,
|
||||
)
|
||||
.await;
|
||||
match materialized {
|
||||
Ok(was_materialized) => {
|
||||
if was_materialized {
|
||||
result.orderbook_event_count += 1;
|
||||
}
|
||||
},
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
for decoded_event in &decoded_events {
|
||||
if !decoded_event.event_kind.ends_with(".lp_change_event") {
|
||||
@@ -673,6 +693,86 @@ WHERE decoded_event_id = ?
|
||||
}
|
||||
}
|
||||
|
||||
async fn materialize_orderbook_event(
|
||||
&self,
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
transaction_id: i64,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
payload: &serde_json::Value,
|
||||
) -> Result<bool, crate::Error> {
|
||||
let decoded_event_id = match decoded_event.id {
|
||||
Some(decoded_event_id) => decoded_event_id,
|
||||
None => return Ok(false),
|
||||
};
|
||||
let context = self.resolve_decoded_event_context(decoded_event).await;
|
||||
let context = match context {
|
||||
Ok(context) => context,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let order_action = normalize_orderbook_action(decoded_event.event_kind.as_str());
|
||||
let actor_wallet = extract_first_string(
|
||||
payload,
|
||||
&["actorWallet", "actor_wallet", "owner", "authority", "payer", "user"],
|
||||
);
|
||||
let order_account = match extract_first_string(
|
||||
payload,
|
||||
&["orderAccount", "order_account", "limitOrder", "limit_order", "order"],
|
||||
) {
|
||||
Some(order_account) => Some(order_account),
|
||||
None => fallback_order_account(decoded_event.event_kind.as_str(), payload),
|
||||
};
|
||||
let amount_raw = extract_first_amount_string(
|
||||
payload,
|
||||
&[
|
||||
"amountRaw",
|
||||
"amount_raw",
|
||||
"amount",
|
||||
"decreasedAmountRaw",
|
||||
"decreased_amount_raw",
|
||||
"decreasedAmount",
|
||||
"increasedAmountRaw",
|
||||
"increased_amount_raw",
|
||||
"increasedAmount",
|
||||
],
|
||||
);
|
||||
let amount_min_raw = extract_first_amount_string(
|
||||
payload,
|
||||
&["amountMinRaw", "amount_min_raw", "amountMin", "amount_min"],
|
||||
);
|
||||
let tick_index = extract_first_i64(payload, &["tickIndex", "tick_index"]);
|
||||
let zero_for_one = extract_first_bool(payload, &["zeroForOne", "zero_for_one"]);
|
||||
let dto = crate::OrderbookEventDto::new(
|
||||
transaction_id,
|
||||
Some(decoded_event_id),
|
||||
context.dex_id,
|
||||
context.pool_id,
|
||||
context.pair_id,
|
||||
transaction.signature.clone(),
|
||||
transaction.slot,
|
||||
decoded_event.protocol_name.clone(),
|
||||
decoded_event.program_id.clone(),
|
||||
decoded_event.event_kind.clone(),
|
||||
order_action,
|
||||
decoded_event.pool_account.clone(),
|
||||
decoded_event.market_account.clone(),
|
||||
actor_wallet,
|
||||
order_account,
|
||||
decoded_event.token_a_mint.clone(),
|
||||
decoded_event.token_b_mint.clone(),
|
||||
amount_raw,
|
||||
amount_min_raw,
|
||||
tick_index,
|
||||
zero_for_one,
|
||||
decoded_event.payload_json.clone(),
|
||||
);
|
||||
let upsert_result =
|
||||
crate::query_orderbook_events_upsert(self.database.as_ref(), &dto).await;
|
||||
match upsert_result {
|
||||
Ok(_) => return Ok(true),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_liquidity_context_from_decoded_event(
|
||||
&self,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
@@ -789,6 +889,162 @@ WHERE decoded_event_id = ?
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_orderbook_action(event_kind: &str) -> std::string::String {
|
||||
if event_kind.contains(".open_limit_order") {
|
||||
return "open_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".increase_limit_order") {
|
||||
return "increase_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".decrease_limit_order") {
|
||||
return "decrease_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".close_limit_order") {
|
||||
return "close_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".settle_limit_order") {
|
||||
return "settle_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains("order_place") {
|
||||
return "order_place".to_string();
|
||||
}
|
||||
if event_kind.contains("order_cancel") {
|
||||
return "order_cancel".to_string();
|
||||
}
|
||||
if event_kind.contains("settle_funds") {
|
||||
return "settle_funds".to_string();
|
||||
}
|
||||
return event_kind.to_string();
|
||||
}
|
||||
|
||||
fn fallback_order_account(
|
||||
event_kind: &str,
|
||||
payload: &serde_json::Value,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if event_kind.contains(".close_limit_order") {
|
||||
return extract_account_at(payload, 2);
|
||||
}
|
||||
if event_kind.contains(".open_limit_order")
|
||||
|| event_kind.contains(".increase_limit_order")
|
||||
|| event_kind.contains(".decrease_limit_order")
|
||||
{
|
||||
return extract_account_at(payload, 3);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn extract_account_at(
|
||||
value: &serde_json::Value,
|
||||
index: usize,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if let Some(object) = value.as_object() {
|
||||
let accounts = object.get("accounts");
|
||||
if let Some(accounts) = accounts {
|
||||
if let Some(array) = accounts.as_array() {
|
||||
let candidate = array.get(index);
|
||||
if let Some(candidate) = candidate {
|
||||
if let Some(text) = candidate.as_str() {
|
||||
let trimmed = text.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested = extract_account_at(nested_value, index);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn 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 = object.get(*candidate_key);
|
||||
if let Some(candidate_value) = candidate_value {
|
||||
if let Some(number) = candidate_value.as_i64() {
|
||||
return Some(number);
|
||||
}
|
||||
if let Some(number) = candidate_value.as_u64() {
|
||||
let converted = i64::try_from(number);
|
||||
if let Ok(converted) = converted {
|
||||
return Some(converted);
|
||||
}
|
||||
}
|
||||
if let Some(text) = candidate_value.as_str() {
|
||||
let parsed = text.parse::<i64>();
|
||||
if let Ok(parsed) = parsed {
|
||||
return Some(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested = extract_first_i64(nested_value, candidate_keys);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn extract_first_bool(
|
||||
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 candidate_value = object.get(*candidate_key);
|
||||
if let Some(candidate_value) = candidate_value {
|
||||
if let Some(flag) = candidate_value.as_bool() {
|
||||
return Some(flag);
|
||||
}
|
||||
if let Some(number) = candidate_value.as_i64() {
|
||||
return Some(number != 0);
|
||||
}
|
||||
if let Some(text) = candidate_value.as_str() {
|
||||
if text == "true" || text == "1" {
|
||||
return Some(true);
|
||||
}
|
||||
if text == "false" || text == "0" {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested = extract_first_bool(nested_value, candidate_keys);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
|
||||
let err_json = match transaction.err_json.as_ref() {
|
||||
Some(err_json) => err_json.trim(),
|
||||
None => return false,
|
||||
};
|
||||
if err_json.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if err_json == "null" {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn extract_first_u64(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
@@ -902,6 +1158,28 @@ fn extract_first_number_as_string(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
fn blank_or_null_err_json_is_not_effective_failure() {
|
||||
let mut transaction = crate::ChainTransactionDto::new(
|
||||
"sig-non-trade-effective-error".to_string(),
|
||||
Some(1),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"{}".to_string(),
|
||||
);
|
||||
assert!(!super::transaction_has_effective_error(&transaction));
|
||||
transaction.err_json = Some("".to_string());
|
||||
assert!(!super::transaction_has_effective_error(&transaction));
|
||||
transaction.err_json = Some("null".to_string());
|
||||
assert!(!super::transaction_has_effective_error(&transaction));
|
||||
transaction.err_json = Some("{\"InstructionError\":[0,\"Custom\"]}".to_string());
|
||||
assert!(super::transaction_has_effective_error(&transaction));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_nested_liquidity_amounts() {
|
||||
let payload = serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user