0.7.26
This commit is contained in:
@@ -138,6 +138,20 @@ impl KbTradeAggregationService {
|
||||
)));
|
||||
},
|
||||
};
|
||||
let base_token_result =
|
||||
crate::get_token_by_id(self.database.as_ref(), pair.base_token_id).await;
|
||||
let base_token_decimals = match base_token_result {
|
||||
Ok(Some(token)) => token.decimals,
|
||||
Ok(None) => None,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let quote_token_result =
|
||||
crate::get_token_by_id(self.database.as_ref(), pair.quote_token_id).await;
|
||||
let quote_token_decimals = match quote_token_result {
|
||||
Ok(Some(token)) => token.decimals,
|
||||
Ok(None) => None,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let pool_tokens_result =
|
||||
crate::list_pool_tokens_by_pool_id(self.database.as_ref(), pool_id).await;
|
||||
let pool_tokens = match pool_tokens_result {
|
||||
@@ -247,10 +261,80 @@ impl KbTradeAggregationService {
|
||||
price_quote_per_base = inferred.2;
|
||||
}
|
||||
}
|
||||
if decoded_event.event_kind.starts_with("raydium_cpmm.")
|
||||
if (decoded_event.event_kind.starts_with("raydium_cpmm.")
|
||||
|| decoded_event.event_kind.starts_with("raydium_clmm."))
|
||||
&& (base_amount_raw.is_none()
|
||||
|| quote_amount_raw.is_none()
|
||||
|| price_quote_per_base.is_none())
|
||||
{
|
||||
let decoded_instruction_index = match decoded_event.instruction_id {
|
||||
Some(instruction_id) => {
|
||||
let instruction_result = crate::get_chain_instruction_by_id(
|
||||
self.database.as_ref(),
|
||||
instruction_id,
|
||||
)
|
||||
.await;
|
||||
let instruction_option = match instruction_result {
|
||||
Ok(instruction_option) => instruction_option,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
match instruction_option {
|
||||
Some(instruction) => Some(instruction.instruction_index),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let payload_input_vault_address =
|
||||
kb_extract_string_by_candidate_keys(&payload, &["inputVault", "input_vault"]);
|
||||
let payload_output_vault_address =
|
||||
kb_extract_string_by_candidate_keys(&payload, &["outputVault", "output_vault"]);
|
||||
let payload_input_token_account = kb_extract_string_by_candidate_keys(
|
||||
&payload,
|
||||
&["inputTokenAccount", "input_token_account"],
|
||||
);
|
||||
let payload_output_token_account = kb_extract_string_by_candidate_keys(
|
||||
&payload,
|
||||
&["outputTokenAccount", "output_token_account"],
|
||||
);
|
||||
let payload_base_vault_address =
|
||||
kb_extract_string_by_candidate_keys(&payload, &["baseVault", "base_vault"]);
|
||||
let payload_quote_vault_address =
|
||||
kb_extract_string_by_candidate_keys(&payload, &["quoteVault", "quote_vault"]);
|
||||
let effective_base_vault_address = match base_vault_address.as_deref() {
|
||||
Some(base_vault_address) => Some(base_vault_address),
|
||||
None => payload_base_vault_address.as_deref(),
|
||||
};
|
||||
let effective_quote_vault_address = match quote_vault_address.as_deref() {
|
||||
Some(quote_vault_address) => Some(quote_vault_address),
|
||||
None => payload_quote_vault_address.as_deref(),
|
||||
};
|
||||
let inferred_result = kb_extract_trade_amounts_from_instruction_token_transfers(
|
||||
transaction.meta_json.as_deref(),
|
||||
decoded_instruction_index,
|
||||
payload_input_vault_address.as_deref(),
|
||||
payload_output_vault_address.as_deref(),
|
||||
payload_input_token_account.as_deref(),
|
||||
payload_output_token_account.as_deref(),
|
||||
effective_base_vault_address,
|
||||
effective_quote_vault_address,
|
||||
);
|
||||
let inferred = match inferred_result {
|
||||
Ok(inferred) => inferred,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if base_amount_raw.is_none() {
|
||||
base_amount_raw = inferred.0;
|
||||
}
|
||||
if quote_amount_raw.is_none() {
|
||||
quote_amount_raw = inferred.1;
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
price_quote_per_base = inferred.2;
|
||||
}
|
||||
}
|
||||
if decoded_event.event_kind.starts_with("raydium_cpmm.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let inferred_result = kb_extract_trade_amounts_from_vault_balance_deltas(
|
||||
transaction.transaction_json.as_str(),
|
||||
@@ -273,9 +357,7 @@ impl KbTradeAggregationService {
|
||||
}
|
||||
}
|
||||
if decoded_event.event_kind.starts_with("raydium_clmm.")
|
||||
&& (base_amount_raw.is_none()
|
||||
|| quote_amount_raw.is_none()
|
||||
|| price_quote_per_base.is_none())
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let inferred_result = kb_extract_trade_amounts_from_vault_balance_deltas(
|
||||
transaction.transaction_json.as_str(),
|
||||
@@ -297,6 +379,15 @@ impl KbTradeAggregationService {
|
||||
price_quote_per_base = inferred.2;
|
||||
}
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
price_quote_per_base =
|
||||
kb_compute_price_quote_per_base_from_raw_amounts_with_decimals(
|
||||
base_amount_raw.as_deref(),
|
||||
quote_amount_raw.as_deref(),
|
||||
base_token_decimals,
|
||||
quote_token_decimals,
|
||||
);
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
price_quote_per_base = kb_compute_price_quote_per_base_with_decimals(
|
||||
transaction.meta_json.as_deref(),
|
||||
@@ -884,6 +975,182 @@ fn kb_extract_trade_amounts_from_vault_balance_deltas(
|
||||
return Ok((base_amount_raw, quote_amount_raw, price_quote_per_base));
|
||||
}
|
||||
|
||||
fn kb_extract_trade_amounts_from_instruction_token_transfers(
|
||||
meta_json: std::option::Option<&str>,
|
||||
instruction_index: std::option::Option<u32>,
|
||||
input_vault_address: std::option::Option<&str>,
|
||||
output_vault_address: std::option::Option<&str>,
|
||||
input_token_account: std::option::Option<&str>,
|
||||
output_token_account: std::option::Option<&str>,
|
||||
base_vault_address: std::option::Option<&str>,
|
||||
quote_vault_address: std::option::Option<&str>,
|
||||
) -> Result<KbExtractedTradeAmounts, crate::KbError> {
|
||||
let meta_json = match meta_json {
|
||||
Some(meta_json) => meta_json,
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let instruction_index = match instruction_index {
|
||||
Some(instruction_index) => u64::from(instruction_index),
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let input_vault_address = match input_vault_address {
|
||||
Some(input_vault_address) => input_vault_address.trim(),
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let output_vault_address = match output_vault_address {
|
||||
Some(output_vault_address) => output_vault_address.trim(),
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let input_token_account = match input_token_account {
|
||||
Some(input_token_account) => input_token_account.trim(),
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let output_token_account = match output_token_account {
|
||||
Some(output_token_account) => output_token_account.trim(),
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let base_vault_address = match base_vault_address {
|
||||
Some(base_vault_address) => base_vault_address.trim(),
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let quote_vault_address = match quote_vault_address {
|
||||
Some(quote_vault_address) => quote_vault_address.trim(),
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
if input_vault_address.is_empty()
|
||||
|| output_vault_address.is_empty()
|
||||
|| input_token_account.is_empty()
|
||||
|| output_token_account.is_empty()
|
||||
|| base_vault_address.is_empty()
|
||||
|| quote_vault_address.is_empty()
|
||||
{
|
||||
return Ok((None, None, None));
|
||||
}
|
||||
let meta_value_result = serde_json::from_str::<serde_json::Value>(meta_json);
|
||||
let meta_value = match meta_value_result {
|
||||
Ok(meta_value) => meta_value,
|
||||
Err(error) => {
|
||||
return Err(crate::KbError::Json(format!(
|
||||
"cannot parse meta_json for instruction-scoped token transfer amount extraction: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
};
|
||||
let inner_groups_option =
|
||||
meta_value.get("innerInstructions").and_then(|value| return value.as_array());
|
||||
let inner_groups = match inner_groups_option {
|
||||
Some(inner_groups) => inner_groups,
|
||||
None => return Ok((None, None, None)),
|
||||
};
|
||||
let mut input_amount_raw = None;
|
||||
let mut output_amount_raw = None;
|
||||
for inner_group in inner_groups {
|
||||
let group_index_option = inner_group.get("index").and_then(|value| return value.as_u64());
|
||||
let group_index = match group_index_option {
|
||||
Some(group_index) => group_index,
|
||||
None => continue,
|
||||
};
|
||||
if group_index != instruction_index {
|
||||
continue;
|
||||
}
|
||||
let instructions_option =
|
||||
inner_group.get("instructions").and_then(|value| return value.as_array());
|
||||
let instructions = match instructions_option {
|
||||
Some(instructions) => instructions,
|
||||
None => continue,
|
||||
};
|
||||
for instruction in instructions {
|
||||
if !kb_is_spl_token_transfer_instruction(instruction) {
|
||||
continue;
|
||||
}
|
||||
let parsed_option = instruction.get("parsed");
|
||||
let parsed = match parsed_option {
|
||||
Some(parsed) => parsed,
|
||||
None => continue,
|
||||
};
|
||||
let info_option = parsed.get("info");
|
||||
let info = match info_option {
|
||||
Some(info) => info,
|
||||
None => continue,
|
||||
};
|
||||
let source_option = kb_extract_string_by_candidate_keys(info, &["source"]);
|
||||
let source = match source_option {
|
||||
Some(source) => source,
|
||||
None => continue,
|
||||
};
|
||||
let destination_option = kb_extract_string_by_candidate_keys(info, &["destination"]);
|
||||
let destination = match destination_option {
|
||||
Some(destination) => destination,
|
||||
None => continue,
|
||||
};
|
||||
let amount_option = kb_extract_scalar_as_string_by_candidate_keys(info, &["amount"]);
|
||||
let amount = match amount_option {
|
||||
Some(amount) => amount,
|
||||
None => continue,
|
||||
};
|
||||
if input_amount_raw.is_none()
|
||||
&& kb_account_equals(source.as_str(), input_token_account)
|
||||
&& kb_account_equals(destination.as_str(), input_vault_address)
|
||||
{
|
||||
input_amount_raw = Some(amount.clone());
|
||||
continue;
|
||||
}
|
||||
if output_amount_raw.is_none()
|
||||
&& kb_account_equals(source.as_str(), output_vault_address)
|
||||
&& kb_account_equals(destination.as_str(), output_token_account)
|
||||
{
|
||||
output_amount_raw = Some(amount);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if input_amount_raw.is_none() && output_amount_raw.is_none() {
|
||||
return Ok((None, None, None));
|
||||
}
|
||||
if kb_account_equals(input_vault_address, base_vault_address)
|
||||
&& kb_account_equals(output_vault_address, quote_vault_address)
|
||||
{
|
||||
return Ok((input_amount_raw, output_amount_raw, None));
|
||||
}
|
||||
if kb_account_equals(input_vault_address, quote_vault_address)
|
||||
&& kb_account_equals(output_vault_address, base_vault_address)
|
||||
{
|
||||
return Ok((output_amount_raw, input_amount_raw, None));
|
||||
}
|
||||
return Ok((None, None, None));
|
||||
}
|
||||
|
||||
fn kb_is_spl_token_transfer_instruction(instruction: &serde_json::Value) -> bool {
|
||||
let program_id_option = instruction.get("programId").and_then(|value| return value.as_str());
|
||||
if let Some(program_id) = program_id_option {
|
||||
let spl_token_program_id = crate::SPL_TOKEN_PROGRAM_ID.to_string();
|
||||
let spl_token_2022_program_id = crate::SPL_TOKEN_2022_PROGRAM_ID.to_string();
|
||||
if program_id != spl_token_program_id.as_str()
|
||||
&& program_id != spl_token_2022_program_id.as_str()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let parsed_type_option = instruction
|
||||
.get("parsed")
|
||||
.and_then(|parsed| return parsed.get("type"))
|
||||
.and_then(|value| return value.as_str());
|
||||
match parsed_type_option {
|
||||
Some("transfer") => return true,
|
||||
Some("transferChecked") => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn kb_account_equals(left: &str, right: &str) -> bool {
|
||||
let left = left.trim();
|
||||
let right = right.trim();
|
||||
if left.is_empty() || right.is_empty() {
|
||||
return false;
|
||||
}
|
||||
return left == right;
|
||||
}
|
||||
|
||||
fn kb_extract_pump_fun_amounts_from_transaction(
|
||||
transaction_json: &str,
|
||||
meta_json: std::option::Option<&str>,
|
||||
@@ -1167,6 +1434,57 @@ fn kb_compute_ui_delta_abs(
|
||||
return Some(delta);
|
||||
}
|
||||
|
||||
fn kb_compute_price_quote_per_base_from_raw_amounts_with_decimals(
|
||||
base_amount_raw: std::option::Option<&str>,
|
||||
quote_amount_raw: std::option::Option<&str>,
|
||||
base_decimals: std::option::Option<u8>,
|
||||
quote_decimals: std::option::Option<u8>,
|
||||
) -> std::option::Option<f64> {
|
||||
let base_decimals = match base_decimals {
|
||||
Some(base_decimals) => base_decimals,
|
||||
None => return None,
|
||||
};
|
||||
let quote_decimals = match quote_decimals {
|
||||
Some(quote_decimals) => quote_decimals,
|
||||
None => return None,
|
||||
};
|
||||
let base_amount_raw = match base_amount_raw {
|
||||
Some(base_amount_raw) => base_amount_raw.trim(),
|
||||
None => return None,
|
||||
};
|
||||
let quote_amount_raw = match quote_amount_raw {
|
||||
Some(quote_amount_raw) => quote_amount_raw.trim(),
|
||||
None => return None,
|
||||
};
|
||||
if base_amount_raw.is_empty() || quote_amount_raw.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let base_amount_result = base_amount_raw.parse::<f64>();
|
||||
let base_amount = match base_amount_result {
|
||||
Ok(base_amount) => base_amount,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let quote_amount_result = quote_amount_raw.parse::<f64>();
|
||||
let quote_amount = match quote_amount_result {
|
||||
Ok(quote_amount) => quote_amount,
|
||||
Err(_) => return None,
|
||||
};
|
||||
if base_amount <= 0.0 || quote_amount <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let base_scale = 10_f64.powi(i32::from(base_decimals));
|
||||
let quote_scale = 10_f64.powi(i32::from(quote_decimals));
|
||||
if base_scale <= 0.0 || quote_scale <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let base_ui_amount = base_amount / base_scale;
|
||||
let quote_ui_amount = quote_amount / quote_scale;
|
||||
if base_ui_amount <= 0.0 || quote_ui_amount <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
return Some(quote_ui_amount / base_ui_amount);
|
||||
}
|
||||
|
||||
fn kb_compute_price_quote_per_base_from_raw_amounts(
|
||||
base_amount_raw: std::option::Option<&str>,
|
||||
quote_amount_raw: std::option::Option<&str>,
|
||||
|
||||
Reference in New Issue
Block a user