0.5.6
This commit is contained in:
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.5"
|
||||
version = "0.5.6"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobot"
|
||||
|
||||
@@ -26,6 +26,7 @@ mod ids;
|
||||
mod heuristics;
|
||||
mod account_enrichment;
|
||||
mod enriched_classifier;
|
||||
mod signal_correlation;
|
||||
|
||||
/// Runs the listener application bootstrap workflow.
|
||||
pub use crate::app::run_listener_app;
|
||||
@@ -127,3 +128,11 @@ pub use crate::enriched_classifier::KhbbConfirmedTokenAccountActivityEvent;
|
||||
pub use crate::enriched_classifier::KhbbConfirmedMintAccountActivityEvent;
|
||||
/// Unknown token-program-owned account activity.
|
||||
pub use crate::enriched_classifier::KhbbUnknownTokenProgramAccountActivityEvent;
|
||||
/// Correlated signal produced from enriched and heuristic activity.
|
||||
pub use crate::signal_correlation::KhbbCorrelatedSignal;
|
||||
/// Correlated signal indicating a confirmed token account update.
|
||||
pub use crate::signal_correlation::KhbbConfirmedTokenAccountUpdateSignal;
|
||||
/// Correlated signal indicating a potential new token mint.
|
||||
pub use crate::signal_correlation::KhbbPotentialNewTokenMintSignal;
|
||||
/// Correlated signal indicating a potential token bootstrap flow.
|
||||
pub use crate::signal_correlation::KhbbPotentialTokenBootstrapFlowSignal;
|
||||
|
||||
@@ -560,52 +560,54 @@ pub async fn run_listener_runtime(
|
||||
classified.clone(),
|
||||
),
|
||||
);
|
||||
match heuristic_result {
|
||||
Ok(signals) => {
|
||||
for signal in signals {
|
||||
match signal {
|
||||
crate::KhbbHeuristicSignal::PotentialTokenAccountActivity(inner) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %inner.pubkey,
|
||||
context_slot = inner.context_slot,
|
||||
subscription_id = inner.subscription_id,
|
||||
token_program_family = %inner.token_program_family,
|
||||
"heuristic potential token account activity signal"
|
||||
);
|
||||
let heuristic_signals = match heuristic_result {
|
||||
Ok(signals) => {
|
||||
for signal in &signals {
|
||||
match signal {
|
||||
crate::KhbbHeuristicSignal::PotentialTokenAccountActivity(inner) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %inner.pubkey,
|
||||
context_slot = inner.context_slot,
|
||||
subscription_id = inner.subscription_id,
|
||||
token_program_family = %inner.token_program_family,
|
||||
"heuristic potential token account activity signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbHeuristicSignal::PotentialMintActivity(inner) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %inner.pubkey,
|
||||
context_slot = inner.context_slot,
|
||||
token_program_family = %inner.token_program_family,
|
||||
"heuristic potential mint activity signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(inner) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %inner.pubkey,
|
||||
context_slot = inner.context_slot,
|
||||
subscription_id = inner.subscription_id,
|
||||
token_program_family = %inner.token_program_family,
|
||||
"heuristic potential associated token account activity signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbHeuristicSignal::PotentialInitialTokenActivity(_) => {}
|
||||
crate::KhbbHeuristicSignal::PotentialTokenBootstrapActivity(_) => {}
|
||||
}
|
||||
crate::KhbbHeuristicSignal::PotentialMintActivity(inner) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %inner.pubkey,
|
||||
context_slot = inner.context_slot,
|
||||
token_program_family = %inner.token_program_family,
|
||||
"heuristic potential mint activity signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(inner) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %inner.pubkey,
|
||||
context_slot = inner.context_slot,
|
||||
subscription_id = inner.subscription_id,
|
||||
token_program_family = %inner.token_program_family,
|
||||
"heuristic potential associated token account activity signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbHeuristicSignal::PotentialInitialTokenActivity(_) => {}
|
||||
crate::KhbbHeuristicSignal::PotentialTokenBootstrapActivity(_) => {}
|
||||
}
|
||||
signals
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
listener_session_id = session.id,
|
||||
error = %error,
|
||||
"failed to derive heuristic signals from spl-token program activity"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
listener_session_id = session.id,
|
||||
error = %error,
|
||||
"failed to derive heuristic signals from spl-token program activity"
|
||||
);
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
let account_info_result = http_client
|
||||
.get_account_info(
|
||||
&classified.pubkey,
|
||||
@@ -671,6 +673,51 @@ pub async fn run_listener_runtime(
|
||||
lamports = ?inner.lamports,
|
||||
"confirmed token account activity event"
|
||||
);
|
||||
let correlated_result =
|
||||
crate::signal_correlation::correlate_signals(
|
||||
&crate::KhbbEnrichedConfirmedEvent::ConfirmedTokenAccountActivity(
|
||||
inner.clone(),
|
||||
),
|
||||
&heuristic_signals,
|
||||
);
|
||||
match correlated_result {
|
||||
Ok(signals) => {
|
||||
for signal in signals {
|
||||
match signal {
|
||||
crate::KhbbCorrelatedSignal::ConfirmedTokenAccountUpdate(correlated) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %correlated.pubkey,
|
||||
context_slot = correlated.context_slot,
|
||||
owner = ?correlated.owner,
|
||||
lamports = ?correlated.lamports,
|
||||
"correlated confirmed token account update signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbCorrelatedSignal::PotentialTokenBootstrapFlow(correlated) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = ?correlated.pubkey,
|
||||
context_slot = correlated.context_slot,
|
||||
saw_token_program = correlated.saw_token_program,
|
||||
saw_associated_token_account = correlated.saw_associated_token_account,
|
||||
saw_bootstrap_logs = correlated.saw_bootstrap_logs,
|
||||
"correlated potential token bootstrap flow signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbCorrelatedSignal::PotentialNewTokenMint(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
listener_session_id = session.id,
|
||||
error = %error,
|
||||
pubkey = %classified.pubkey,
|
||||
"failed to correlate confirmed token account activity"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(crate::KhbbEnrichedConfirmedEvent::ConfirmedMintAccountActivity(inner))) => {
|
||||
tracing::trace!(
|
||||
@@ -681,6 +728,41 @@ pub async fn run_listener_runtime(
|
||||
lamports = ?inner.lamports,
|
||||
"confirmed mint account activity event"
|
||||
);
|
||||
let correlated_result =
|
||||
crate::signal_correlation::correlate_signals(
|
||||
&crate::KhbbEnrichedConfirmedEvent::ConfirmedMintAccountActivity(
|
||||
inner.clone(),
|
||||
),
|
||||
&heuristic_signals,
|
||||
);
|
||||
match correlated_result {
|
||||
Ok(signals) => {
|
||||
for signal in signals {
|
||||
match signal {
|
||||
crate::KhbbCorrelatedSignal::PotentialNewTokenMint(correlated) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = %correlated.pubkey,
|
||||
context_slot = correlated.context_slot,
|
||||
owner = ?correlated.owner,
|
||||
lamports = ?correlated.lamports,
|
||||
"correlated potential new token mint signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbCorrelatedSignal::ConfirmedTokenAccountUpdate(_) => {}
|
||||
crate::KhbbCorrelatedSignal::PotentialTokenBootstrapFlow(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
listener_session_id = session.id,
|
||||
error = %error,
|
||||
pubkey = %classified.pubkey,
|
||||
"failed to correlate confirmed mint account activity"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(crate::KhbbEnrichedConfirmedEvent::UnknownTokenProgramAccountActivity(inner))) => {
|
||||
tracing::trace!(
|
||||
@@ -691,6 +773,42 @@ pub async fn run_listener_runtime(
|
||||
lamports = ?inner.lamports,
|
||||
"unknown token-program-owned account activity event"
|
||||
);
|
||||
let correlated_result =
|
||||
crate::signal_correlation::correlate_signals(
|
||||
&crate::KhbbEnrichedConfirmedEvent::UnknownTokenProgramAccountActivity(
|
||||
inner.clone(),
|
||||
),
|
||||
&heuristic_signals,
|
||||
);
|
||||
match correlated_result {
|
||||
Ok(signals) => {
|
||||
for signal in signals {
|
||||
match signal {
|
||||
crate::KhbbCorrelatedSignal::PotentialTokenBootstrapFlow(correlated) => {
|
||||
tracing::trace!(
|
||||
listener_session_id = session.id,
|
||||
pubkey = ?correlated.pubkey,
|
||||
context_slot = correlated.context_slot,
|
||||
saw_token_program = correlated.saw_token_program,
|
||||
saw_associated_token_account = correlated.saw_associated_token_account,
|
||||
saw_bootstrap_logs = correlated.saw_bootstrap_logs,
|
||||
"correlated potential token bootstrap flow signal"
|
||||
);
|
||||
}
|
||||
crate::KhbbCorrelatedSignal::ConfirmedTokenAccountUpdate(_) => {}
|
||||
crate::KhbbCorrelatedSignal::PotentialNewTokenMint(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
listener_session_id = session.id,
|
||||
error = %error,
|
||||
pubkey = %classified.pubkey,
|
||||
"failed to correlate unknown token-program-owned account activity"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
|
||||
231
khbb_lib/src/signal_correlation.rs
Normal file
231
khbb_lib/src/signal_correlation.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
// file: khbb_lib/src/signal_correlation.rs
|
||||
|
||||
//! Local correlation of enriched and heuristic signals into stronger trading-oriented signals.
|
||||
|
||||
/// Correlated signal produced from enriched and heuristic activity.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum KhbbCorrelatedSignal {
|
||||
/// Confirmed token account update.
|
||||
ConfirmedTokenAccountUpdate(KhbbConfirmedTokenAccountUpdateSignal),
|
||||
/// Potential new token mint.
|
||||
PotentialNewTokenMint(KhbbPotentialNewTokenMintSignal),
|
||||
/// Potential token bootstrap flow.
|
||||
PotentialTokenBootstrapFlow(KhbbPotentialTokenBootstrapFlowSignal),
|
||||
}
|
||||
|
||||
/// Correlated signal indicating a confirmed token account update.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KhbbConfirmedTokenAccountUpdateSignal {
|
||||
/// Account pubkey.
|
||||
pub pubkey: std::string::String,
|
||||
/// Context slot.
|
||||
pub context_slot: u64,
|
||||
/// Owner program if available.
|
||||
pub owner: std::option::Option<std::string::String>,
|
||||
/// Lamports if available.
|
||||
pub lamports: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
/// Correlated signal indicating a potential new token mint.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KhbbPotentialNewTokenMintSignal {
|
||||
/// Account pubkey.
|
||||
pub pubkey: std::string::String,
|
||||
/// Context slot.
|
||||
pub context_slot: u64,
|
||||
/// Owner program if available.
|
||||
pub owner: std::option::Option<std::string::String>,
|
||||
/// Lamports if available.
|
||||
pub lamports: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
/// Correlated signal indicating a potential token bootstrap flow.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KhbbPotentialTokenBootstrapFlowSignal {
|
||||
/// Account pubkey if one is available from the enriched side.
|
||||
pub pubkey: std::option::Option<std::string::String>,
|
||||
/// Context slot.
|
||||
pub context_slot: u64,
|
||||
/// Whether token program activity was seen.
|
||||
pub saw_token_program: bool,
|
||||
/// Whether associated token account activity was seen.
|
||||
pub saw_associated_token_account: bool,
|
||||
/// Whether bootstrap-style logs were seen.
|
||||
pub saw_bootstrap_logs: bool,
|
||||
}
|
||||
|
||||
/// Correlates an enriched confirmed event with heuristic signals.
|
||||
pub(crate) fn correlate_signals(
|
||||
enriched_event: &crate::KhbbEnrichedConfirmedEvent,
|
||||
heuristic_signals: &[crate::KhbbHeuristicSignal],
|
||||
) -> core::result::Result<std::vec::Vec<KhbbCorrelatedSignal>, crate::KhbbError> {
|
||||
let mut results = std::vec::Vec::<KhbbCorrelatedSignal>::new();
|
||||
match enriched_event {
|
||||
crate::KhbbEnrichedConfirmedEvent::ConfirmedTokenAccountActivity(event) => {
|
||||
results.push(KhbbCorrelatedSignal::ConfirmedTokenAccountUpdate(
|
||||
KhbbConfirmedTokenAccountUpdateSignal {
|
||||
pubkey: event.pubkey.clone(),
|
||||
context_slot: event.context_slot,
|
||||
owner: event.owner.clone(),
|
||||
lamports: event.lamports,
|
||||
},
|
||||
));
|
||||
let mut saw_token_program = false;
|
||||
let mut saw_associated_token_account = false;
|
||||
let mut saw_bootstrap_logs = false;
|
||||
for signal in heuristic_signals {
|
||||
match signal {
|
||||
crate::KhbbHeuristicSignal::PotentialTokenAccountActivity(inner) => {
|
||||
if inner.pubkey == event.pubkey {
|
||||
saw_token_program = true;
|
||||
}
|
||||
},
|
||||
crate::KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(inner) => {
|
||||
if inner.pubkey == event.pubkey {
|
||||
saw_associated_token_account = true;
|
||||
}
|
||||
},
|
||||
crate::KhbbHeuristicSignal::PotentialTokenBootstrapActivity(_) => {
|
||||
saw_bootstrap_logs = true;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
if saw_token_program && (saw_associated_token_account || saw_bootstrap_logs) {
|
||||
results.push(KhbbCorrelatedSignal::PotentialTokenBootstrapFlow(
|
||||
KhbbPotentialTokenBootstrapFlowSignal {
|
||||
pubkey: Some(event.pubkey.clone()),
|
||||
context_slot: event.context_slot,
|
||||
saw_token_program,
|
||||
saw_associated_token_account,
|
||||
saw_bootstrap_logs,
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
crate::KhbbEnrichedConfirmedEvent::ConfirmedMintAccountActivity(event) => {
|
||||
results.push(KhbbCorrelatedSignal::PotentialNewTokenMint(
|
||||
KhbbPotentialNewTokenMintSignal {
|
||||
pubkey: event.pubkey.clone(),
|
||||
context_slot: event.context_slot,
|
||||
owner: event.owner.clone(),
|
||||
lamports: event.lamports,
|
||||
},
|
||||
));
|
||||
},
|
||||
crate::KhbbEnrichedConfirmedEvent::UnknownTokenProgramAccountActivity(event) => {
|
||||
let mut saw_bootstrap_logs = false;
|
||||
for signal in heuristic_signals {
|
||||
if let crate::KhbbHeuristicSignal::PotentialTokenBootstrapActivity(_) = signal {
|
||||
saw_bootstrap_logs = true;
|
||||
}
|
||||
}
|
||||
if saw_bootstrap_logs {
|
||||
results.push(KhbbCorrelatedSignal::PotentialTokenBootstrapFlow(
|
||||
KhbbPotentialTokenBootstrapFlowSignal {
|
||||
pubkey: Some(event.pubkey.clone()),
|
||||
context_slot: event.context_slot,
|
||||
saw_token_program: true,
|
||||
saw_associated_token_account: false,
|
||||
saw_bootstrap_logs,
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn correlate_confirmed_token_account_returns_confirmed_update() {
|
||||
let enriched = crate::KhbbEnrichedConfirmedEvent::ConfirmedTokenAccountActivity(
|
||||
crate::KhbbConfirmedTokenAccountActivityEvent {
|
||||
pubkey: std::string::String::from("SomePubkey"),
|
||||
context_slot: 100,
|
||||
owner: Some(crate::ids::SPL_TOKEN_PROGRAM_ID.to_string()),
|
||||
lamports: Some(123),
|
||||
},
|
||||
);
|
||||
let heuristics = vec![crate::KhbbHeuristicSignal::PotentialTokenAccountActivity(
|
||||
crate::KhbbPotentialTokenAccountActivitySignal {
|
||||
pubkey: std::string::String::from("SomePubkey"),
|
||||
context_slot: 100,
|
||||
subscription_id: 1,
|
||||
token_program_family: std::string::String::from("spl-token"),
|
||||
},
|
||||
)];
|
||||
let result = super::correlate_signals(&enriched, &heuristics);
|
||||
assert!(result.is_ok());
|
||||
let signals = result.expect("correlate token account");
|
||||
assert_eq!(signals.len(), 1);
|
||||
match &signals[0] {
|
||||
super::KhbbCorrelatedSignal::ConfirmedTokenAccountUpdate(inner) => {
|
||||
assert_eq!(inner.pubkey, "SomePubkey");
|
||||
},
|
||||
_ => {
|
||||
panic!("expected confirmed token account update");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correlate_confirmed_mint_returns_potential_new_token_mint() {
|
||||
let enriched = crate::KhbbEnrichedConfirmedEvent::ConfirmedMintAccountActivity(
|
||||
crate::KhbbConfirmedMintAccountActivityEvent {
|
||||
pubkey: std::string::String::from("MintPubkey"),
|
||||
context_slot: 200,
|
||||
owner: Some(crate::ids::SPL_TOKEN_PROGRAM_ID.to_string()),
|
||||
lamports: Some(456),
|
||||
},
|
||||
);
|
||||
let heuristics = vec![];
|
||||
let result = super::correlate_signals(&enriched, &heuristics);
|
||||
assert!(result.is_ok());
|
||||
let signals = result.expect("correlate mint");
|
||||
assert_eq!(signals.len(), 1);
|
||||
match &signals[0] {
|
||||
super::KhbbCorrelatedSignal::PotentialNewTokenMint(inner) => {
|
||||
assert_eq!(inner.pubkey, "MintPubkey");
|
||||
},
|
||||
_ => {
|
||||
panic!("expected potential new token mint");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correlate_confirmed_token_account_returns_bootstrap_flow_when_context_matches() {
|
||||
let enriched = crate::KhbbEnrichedConfirmedEvent::ConfirmedTokenAccountActivity(
|
||||
crate::KhbbConfirmedTokenAccountActivityEvent {
|
||||
pubkey: std::string::String::from("AtaLikePubkey"),
|
||||
context_slot: 300,
|
||||
owner: Some(crate::ids::SPL_TOKEN_PROGRAM_ID.to_string()),
|
||||
lamports: Some(789),
|
||||
},
|
||||
);
|
||||
let heuristics = vec![
|
||||
crate::KhbbHeuristicSignal::PotentialTokenAccountActivity(
|
||||
crate::KhbbPotentialTokenAccountActivitySignal {
|
||||
pubkey: std::string::String::from("AtaLikePubkey"),
|
||||
context_slot: 300,
|
||||
subscription_id: 2,
|
||||
token_program_family: std::string::String::from("spl-token"),
|
||||
},
|
||||
),
|
||||
crate::KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(
|
||||
crate::KhbbPotentialAssociatedTokenAccountActivitySignal {
|
||||
pubkey: std::string::String::from("AtaLikePubkey"),
|
||||
context_slot: 300,
|
||||
subscription_id: 2,
|
||||
token_program_family: std::string::String::from("spl-token"),
|
||||
},
|
||||
),
|
||||
];
|
||||
let result = super::correlate_signals(&enriched, &heuristics);
|
||||
assert!(result.is_ok());
|
||||
let signals = result.expect("correlate bootstrap");
|
||||
assert_eq!(signals.len(), 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user