0.7.28
This commit is contained in:
650
kb_lib/src/trade_amount_resolution.rs
Normal file
650
kb_lib/src/trade_amount_resolution.rs
Normal file
@@ -0,0 +1,650 @@
|
||||
// file: kb_lib/src/trade_amount_resolution.rs
|
||||
|
||||
//! Trade amount resolution orchestration.
|
||||
//!
|
||||
//! This module resolves base/quote raw amounts and quote/base price for one
|
||||
//! decoded trade candidate by applying protocol-specific and generic fallback
|
||||
//! strategies in deterministic order.
|
||||
|
||||
/// Input context required to resolve trade amounts.
|
||||
pub(crate) struct TradeAmountResolutionInput<'a> {
|
||||
/// Database connection.
|
||||
pub(crate) database: &'a crate::Database,
|
||||
/// Persisted transaction row.
|
||||
pub(crate) transaction: &'a crate::ChainTransactionDto,
|
||||
/// Decoded DEX event row.
|
||||
pub(crate) decoded_event: &'a crate::DexDecodedEventDto,
|
||||
/// Decoded event payload.
|
||||
pub(crate) payload: &'a serde_json::Value,
|
||||
/// Pool account address.
|
||||
pub(crate) pool_address: &'a str,
|
||||
/// Base token mint, when known.
|
||||
pub(crate) base_token_mint: std::option::Option<&'a str>,
|
||||
/// Quote token mint, when known.
|
||||
pub(crate) quote_token_mint: std::option::Option<&'a str>,
|
||||
/// Base token decimals, when known.
|
||||
pub(crate) base_token_decimals: std::option::Option<u8>,
|
||||
/// Quote token decimals, when known.
|
||||
pub(crate) quote_token_decimals: std::option::Option<u8>,
|
||||
/// Base token vault address, when known.
|
||||
pub(crate) base_vault_address: std::option::Option<&'a str>,
|
||||
/// Quote token vault address, when known.
|
||||
pub(crate) quote_vault_address: std::option::Option<&'a str>,
|
||||
}
|
||||
|
||||
/// Resolved raw trade amounts and quote/base price.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct TradeAmountResolution {
|
||||
/// Base amount in raw token units.
|
||||
pub(crate) base_amount_raw: std::option::Option<std::string::String>,
|
||||
/// Quote amount in raw token units.
|
||||
pub(crate) quote_amount_raw: std::option::Option<std::string::String>,
|
||||
/// Quote/base price.
|
||||
pub(crate) price_quote_per_base: std::option::Option<f64>,
|
||||
}
|
||||
|
||||
/// Resolves trade amounts from payload and protocol-specific fallbacks.
|
||||
pub(crate) async fn resolve_trade_amounts(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
) -> Result<crate::trade_amount_resolution::TradeAmountResolution, crate::Error> {
|
||||
let mut base_amount_raw = crate::trade_amount_resolution::extract_amount_string(
|
||||
input.payload,
|
||||
&["baseAmountRaw", "base_amount_raw", "baseAmount", "amountBase", "amountInBase"],
|
||||
);
|
||||
let mut quote_amount_raw = crate::trade_amount_resolution::extract_amount_string(
|
||||
input.payload,
|
||||
&[
|
||||
"quoteAmountRaw",
|
||||
"quote_amount_raw",
|
||||
"quoteAmount",
|
||||
"amountQuote",
|
||||
"amountOutQuote",
|
||||
],
|
||||
);
|
||||
let mut price_quote_per_base = None;
|
||||
if input.decoded_event.event_kind.starts_with("pump_swap.")
|
||||
&& (base_amount_raw.is_none()
|
||||
|| quote_amount_raw.is_none()
|
||||
|| price_quote_per_base.is_none())
|
||||
{
|
||||
let resolution_result = crate::trade_amount_resolution::apply_pump_swap_amount_fallbacks(
|
||||
input,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut price_quote_per_base,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("pump_fun.")
|
||||
&& (base_amount_raw.is_none()
|
||||
|| quote_amount_raw.is_none()
|
||||
|| price_quote_per_base.is_none())
|
||||
{
|
||||
let resolution_result = crate::trade_amount_resolution::apply_pump_fun_amount_fallback(
|
||||
input,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut price_quote_per_base,
|
||||
);
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if (input.decoded_event.event_kind.starts_with("raydium_cpmm.")
|
||||
|| input.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 resolution_result =
|
||||
crate::trade_amount_resolution::apply_raydium_instruction_amount_fallback(
|
||||
input,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut price_quote_per_base,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("raydium_cpmm.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let resolution_result = crate::trade_amount_resolution::apply_vault_balance_delta_fallback(
|
||||
input,
|
||||
input.base_vault_address,
|
||||
input.quote_vault_address,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut price_quote_per_base,
|
||||
);
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("raydium_clmm.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let resolution_result = crate::trade_amount_resolution::apply_vault_balance_delta_fallback(
|
||||
input,
|
||||
input.base_vault_address,
|
||||
input.quote_vault_address,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut price_quote_per_base,
|
||||
);
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
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,
|
||||
);
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
price_quote_per_base =
|
||||
crate::trade_solana_amounts::compute_price_quote_per_base_with_decimals(
|
||||
input.transaction.meta_json.as_deref(),
|
||||
input.transaction.transaction_json.as_str(),
|
||||
input.base_vault_address,
|
||||
input.quote_vault_address,
|
||||
);
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
price_quote_per_base =
|
||||
crate::trade_metric_update::compute_price_quote_per_base_from_raw_amounts(
|
||||
base_amount_raw.as_deref(),
|
||||
quote_amount_raw.as_deref(),
|
||||
);
|
||||
}
|
||||
return Ok(crate::trade_amount_resolution::TradeAmountResolution {
|
||||
base_amount_raw,
|
||||
quote_amount_raw,
|
||||
price_quote_per_base,
|
||||
});
|
||||
}
|
||||
|
||||
async fn apply_pump_swap_amount_fallbacks(
|
||||
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> {
|
||||
let pool_owner_result = match (input.base_token_mint, input.quote_token_mint) {
|
||||
(Some(base_mint), Some(quote_mint)) => {
|
||||
crate::trade_pump_swap_amounts::resolve_pump_swap_trade_amounts_from_pool_balance_deltas(
|
||||
input.transaction.meta_json.as_deref(),
|
||||
input.pool_address,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
input.decoded_event.event_kind.as_str(),
|
||||
input.base_token_decimals,
|
||||
input.quote_token_decimals,
|
||||
)
|
||||
},
|
||||
_ => Ok(crate::trade_pump_swap_amounts::PumpSwapPoolBalanceDeltaResolution::MissingData),
|
||||
};
|
||||
let pool_owner_resolution = match pool_owner_result {
|
||||
Ok(pool_owner_resolution) => pool_owner_resolution,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let pool_owner_resolution_label = pool_owner_resolution.as_label();
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
pool_account = ?input.decoded_event.pool_account,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
transaction_signature = %input.transaction.signature,
|
||||
base_mint = ?input.base_token_mint,
|
||||
quote_mint = ?input.quote_token_mint,
|
||||
pool_owner_resolution = %pool_owner_resolution_label,
|
||||
"pump_swap pool-owner delta resolution result"
|
||||
);
|
||||
match pool_owner_resolution {
|
||||
crate::trade_pump_swap_amounts::PumpSwapPoolBalanceDeltaResolution::Matched(amounts) => {
|
||||
*base_amount_raw = Some(amounts.base_amount_raw);
|
||||
*quote_amount_raw = Some(amounts.quote_amount_raw);
|
||||
*price_quote_per_base = Some(amounts.price_quote_per_base);
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
pool_account = ?input.decoded_event.pool_account,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
base_mint = ?input.base_token_mint,
|
||||
quote_mint = ?input.quote_token_mint,
|
||||
base_amount_raw = ?base_amount_raw,
|
||||
quote_amount_raw = ?quote_amount_raw,
|
||||
price_quote_per_base = ?price_quote_per_base,
|
||||
"pump_swap trade amounts recovered from pool-owner token balance deltas"
|
||||
);
|
||||
},
|
||||
crate::trade_pump_swap_amounts::PumpSwapPoolBalanceDeltaResolution::DirectionMismatch => {
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
pool_account = ?input.decoded_event.pool_account,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
transaction_signature = %input.transaction.signature,
|
||||
"pump_swap pool-owner full-transaction delta direction mismatch; continuing with instruction-scoped fallbacks"
|
||||
);
|
||||
},
|
||||
crate::trade_pump_swap_amounts::PumpSwapPoolBalanceDeltaResolution::MissingData => {},
|
||||
}
|
||||
let decoded_instruction_index_result =
|
||||
crate::trade_amount_resolution::load_decoded_instruction_index(
|
||||
input.database,
|
||||
input.decoded_event,
|
||||
)
|
||||
.await;
|
||||
let decoded_instruction_index = match decoded_instruction_index_result {
|
||||
Ok(decoded_instruction_index) => decoded_instruction_index,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let payload_user_base_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["userBaseTokenAccount", "user_base_token_account"],
|
||||
);
|
||||
let payload_user_quote_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["userQuoteTokenAccount", "user_quote_token_account"],
|
||||
);
|
||||
let payload_pool_base_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["poolBaseTokenAccount", "pool_base_token_account"],
|
||||
);
|
||||
let payload_pool_quote_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["poolQuoteTokenAccount", "pool_quote_token_account"],
|
||||
);
|
||||
let effective_base_vault_address = match input.base_vault_address {
|
||||
Some(base_vault_address) => Some(base_vault_address),
|
||||
None => payload_pool_base_token_account.as_deref(),
|
||||
};
|
||||
let effective_quote_vault_address = match input.quote_vault_address {
|
||||
Some(quote_vault_address) => Some(quote_vault_address),
|
||||
None => payload_pool_quote_token_account.as_deref(),
|
||||
};
|
||||
let (input_vault_address, output_vault_address, input_token_account, output_token_account) =
|
||||
if input.decoded_event.event_kind.ends_with(".buy") {
|
||||
(
|
||||
effective_quote_vault_address,
|
||||
effective_base_vault_address,
|
||||
payload_user_quote_token_account.as_deref(),
|
||||
payload_user_base_token_account.as_deref(),
|
||||
)
|
||||
} else if input.decoded_event.event_kind.ends_with(".sell") {
|
||||
(
|
||||
effective_base_vault_address,
|
||||
effective_quote_vault_address,
|
||||
payload_user_base_token_account.as_deref(),
|
||||
payload_user_quote_token_account.as_deref(),
|
||||
)
|
||||
} else {
|
||||
(None, None, None, None)
|
||||
};
|
||||
let inferred_result =
|
||||
crate::trade_solana_amounts::extract_trade_amounts_from_instruction_token_transfers(
|
||||
input.transaction.meta_json.as_deref(),
|
||||
decoded_instruction_index,
|
||||
input_vault_address,
|
||||
output_vault_address,
|
||||
input_token_account,
|
||||
output_token_account,
|
||||
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 base_amount_raw.is_none() || quote_amount_raw.is_none() {
|
||||
let fallback_result =
|
||||
crate::trade_solana_amounts::extract_trade_amounts_from_vault_balance_deltas(
|
||||
input.transaction.transaction_json.as_str(),
|
||||
input.transaction.meta_json.as_deref(),
|
||||
effective_base_vault_address,
|
||||
effective_quote_vault_address,
|
||||
);
|
||||
let fallback = match fallback_result {
|
||||
Ok(fallback) => fallback,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if base_amount_raw.is_none() {
|
||||
*base_amount_raw = fallback.0;
|
||||
}
|
||||
if quote_amount_raw.is_none() {
|
||||
*quote_amount_raw = fallback.1;
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
*price_quote_per_base = fallback.2;
|
||||
}
|
||||
}
|
||||
|
||||
if base_amount_raw.is_none() || quote_amount_raw.is_none() || price_quote_per_base.is_none() {
|
||||
let transaction_value_result =
|
||||
crate::trade_pump_swap_amounts::build_transaction_value_with_meta_json(
|
||||
input.transaction.transaction_json.as_str(),
|
||||
input.transaction.meta_json.as_deref(),
|
||||
);
|
||||
let transaction_value = match transaction_value_result {
|
||||
Ok(transaction_value) => transaction_value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let fallback_amounts = match (input.base_token_mint, input.quote_token_mint) {
|
||||
(Some(base_mint), Some(quote_mint)) => {
|
||||
crate::trade_pump_swap_amounts::try_build_pump_swap_trade_amounts_from_token_balance_deltas(
|
||||
&transaction_value,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
)
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
if let Some(fallback_amounts) = fallback_amounts {
|
||||
if base_amount_raw.is_none() {
|
||||
*base_amount_raw = crate::trade_pump_swap_amounts::convert_ui_amount_to_raw_string(
|
||||
fallback_amounts.base_amount,
|
||||
input.base_token_decimals,
|
||||
);
|
||||
}
|
||||
if quote_amount_raw.is_none() {
|
||||
*quote_amount_raw = crate::trade_pump_swap_amounts::convert_ui_amount_to_raw_string(
|
||||
fallback_amounts.quote_amount,
|
||||
input.quote_token_decimals,
|
||||
);
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
*price_quote_per_base = Some(fallback_amounts.price_quote_per_base);
|
||||
}
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
pool_account = ?input.decoded_event.pool_account,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
base_mint = ?input.base_token_mint,
|
||||
quote_mint = ?input.quote_token_mint,
|
||||
base_amount_raw = ?base_amount_raw,
|
||||
quote_amount_raw = ?quote_amount_raw,
|
||||
price_quote_per_base = ?price_quote_per_base,
|
||||
"pump_swap trade amounts recovered from token balance deltas"
|
||||
);
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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>,
|
||||
price_quote_per_base: &mut std::option::Option<f64>,
|
||||
) -> Result<(), crate::Error> {
|
||||
let inferred_result = crate::trade_solana_amounts::extract_pump_fun_amounts_from_transaction(
|
||||
input.transaction.transaction_json.as_str(),
|
||||
input.transaction.meta_json.as_deref(),
|
||||
input.base_vault_address,
|
||||
input.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;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn apply_raydium_instruction_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> {
|
||||
let decoded_instruction_index_result =
|
||||
crate::trade_amount_resolution::load_decoded_instruction_index(
|
||||
input.database,
|
||||
input.decoded_event,
|
||||
)
|
||||
.await;
|
||||
let decoded_instruction_index = match decoded_instruction_index_result {
|
||||
Ok(decoded_instruction_index) => decoded_instruction_index,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let payload_input_vault_address =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["inputVault", "input_vault"],
|
||||
);
|
||||
let payload_output_vault_address =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["outputVault", "output_vault"],
|
||||
);
|
||||
let payload_input_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["inputTokenAccount", "input_token_account"],
|
||||
);
|
||||
let payload_output_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["outputTokenAccount", "output_token_account"],
|
||||
);
|
||||
let payload_base_vault_address =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["baseVault", "base_vault"],
|
||||
);
|
||||
let payload_quote_vault_address =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["quoteVault", "quote_vault"],
|
||||
);
|
||||
let effective_base_vault_address = match input.base_vault_address {
|
||||
Some(base_vault_address) => Some(base_vault_address),
|
||||
None => payload_base_vault_address.as_deref(),
|
||||
};
|
||||
let effective_quote_vault_address = match input.quote_vault_address {
|
||||
Some(quote_vault_address) => Some(quote_vault_address),
|
||||
None => payload_quote_vault_address.as_deref(),
|
||||
};
|
||||
let inferred_result =
|
||||
crate::trade_solana_amounts::extract_trade_amounts_from_instruction_token_transfers(
|
||||
input.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;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn apply_vault_balance_delta_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_vault_address: std::option::Option<&str>,
|
||||
quote_vault_address: std::option::Option<&str>,
|
||||
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> {
|
||||
let inferred_result =
|
||||
crate::trade_solana_amounts::extract_trade_amounts_from_vault_balance_deltas(
|
||||
input.transaction.transaction_json.as_str(),
|
||||
input.transaction.meta_json.as_deref(),
|
||||
base_vault_address,
|
||||
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;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn load_decoded_instruction_index(
|
||||
database: &crate::Database,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
) -> Result<std::option::Option<u32>, crate::Error> {
|
||||
let instruction_id = match decoded_event.instruction_id {
|
||||
Some(instruction_id) => instruction_id,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let instruction_result =
|
||||
crate::query_chain_instructions_get_by_id(database, instruction_id).await;
|
||||
let instruction_option = match instruction_result {
|
||||
Ok(instruction_option) => instruction_option,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
match instruction_option {
|
||||
Some(instruction) => return Ok(Some(instruction.instruction_index)),
|
||||
None => return Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_amount_string(
|
||||
payload: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<std::string::String> {
|
||||
return crate::trade_amount_resolution::extract_scalar_as_string_by_candidate_keys(
|
||||
payload,
|
||||
candidate_keys,
|
||||
);
|
||||
}
|
||||
|
||||
fn extract_string_by_candidate_keys(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<std::string::String> {
|
||||
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 {
|
||||
let direct_text_option = direct.as_str();
|
||||
if let Some(direct_text) = direct_text_option {
|
||||
return Some(direct_text.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested_result = crate::trade_amount_resolution::extract_string_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_string_by_candidate_keys(
|
||||
nested_value,
|
||||
candidate_keys,
|
||||
);
|
||||
if nested_result.is_some() {
|
||||
return nested_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn extract_scalar_as_string_by_candidate_keys(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<std::string::String> {
|
||||
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(text) = direct.as_str() {
|
||||
return Some(text.to_string());
|
||||
}
|
||||
if let Some(number) = direct.as_i64() {
|
||||
return Some(number.to_string());
|
||||
}
|
||||
if let Some(number) = direct.as_u64() {
|
||||
return Some(number.to_string());
|
||||
}
|
||||
if let Some(number) = direct.as_f64() {
|
||||
return Some(number.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested_result =
|
||||
crate::trade_amount_resolution::extract_scalar_as_string_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_scalar_as_string_by_candidate_keys(
|
||||
nested_value,
|
||||
candidate_keys,
|
||||
);
|
||||
if nested_result.is_some() {
|
||||
return nested_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
Reference in New Issue
Block a user