This commit is contained in:
2026-05-05 05:03:11 +02:00
parent 3e994995d7
commit f2c227e08f
132 changed files with 5767 additions and 4461 deletions

View File

@@ -31,3 +31,5 @@ tracing-subscriber.workspace = true
[dev-dependencies]
tempfile.workspace = true
[lints]
workspace = true

View File

@@ -20,7 +20,7 @@ pub struct KbConfig {
impl KbConfig {
/// Returns the default path of the JSON configuration file.
pub fn default_path() -> std::path::PathBuf {
kb_workspace_root_dir().join("config.json")
return kb_workspace_root_dir().join("config.json");
}
/// Loads a configuration from a JSON file and validates it.
@@ -34,7 +34,7 @@ impl KbConfig {
"cannot read configuration file '{}': {error}",
path_ref.display()
)));
}
},
};
let config_result = serde_json::from_str::<Self>(&content);
let config = match config_result {
@@ -44,36 +44,28 @@ impl KbConfig {
"cannot parse configuration file '{}': {error}",
path_ref.display()
)));
}
},
};
let validation_result = config.validate();
match validation_result {
Ok(()) => Ok(config),
Err(error) => Err(error),
Ok(()) => return Ok(config),
Err(error) => return Err(error),
}
}
/// Validates the current configuration.
pub fn validate(&self) -> Result<(), crate::KbError> {
if self.app.name.trim().is_empty() {
return Err(crate::KbError::Config(
"app.name must not be empty".to_string(),
));
return Err(crate::KbError::Config("app.name must not be empty".to_string()));
}
if self.app.environment.trim().is_empty() {
return Err(crate::KbError::Config(
"app.environment must not be empty".to_string(),
));
return Err(crate::KbError::Config("app.environment must not be empty".to_string()));
}
if self.logging.level.trim().is_empty() {
return Err(crate::KbError::Config(
"logging.level must not be empty".to_string(),
));
return Err(crate::KbError::Config("logging.level must not be empty".to_string()));
}
if self.logging.directory.trim().is_empty() {
return Err(crate::KbError::Config(
"logging.directory must not be empty".to_string(),
));
return Err(crate::KbError::Config("logging.directory must not be empty".to_string()));
}
if self.logging.file_prefix.trim().is_empty() {
return Err(crate::KbError::Config(
@@ -81,9 +73,7 @@ impl KbConfig {
));
}
if self.data.sqlite_path.trim().is_empty() {
return Err(crate::KbError::Config(
"data.sqlite_path must not be empty".to_string(),
));
return Err(crate::KbError::Config("data.sqlite_path must not be empty".to_string()));
}
if self.data.wallets_directory.trim().is_empty() {
return Err(crate::KbError::Config(
@@ -131,7 +121,7 @@ impl KbConfig {
return Err(error);
}
}
Ok(())
return Ok(());
}
/// Creates the basic runtime directories required by the current configuration.
@@ -165,7 +155,7 @@ impl KbConfig {
}
}
}
Ok(())
return Ok(());
}
/// Finds one HTTP endpoint by its logical name.
@@ -173,10 +163,11 @@ impl KbConfig {
&self,
endpoint_name: &str,
) -> std::option::Option<&KbHttpEndpointConfig> {
self.solana
return self
.solana
.http_endpoints
.iter()
.find(|endpoint| endpoint.name == endpoint_name)
.find(|endpoint| return endpoint.name == endpoint_name);
}
/// Returns a named WebSocket endpoint by reference.
@@ -184,10 +175,11 @@ impl KbConfig {
&self,
endpoint_name: &str,
) -> std::option::Option<&KbWsEndpointConfig> {
self.solana
return self
.solana
.ws_endpoints
.iter()
.find(|endpoint| endpoint.name == endpoint_name)
.find(|endpoint| return endpoint.name == endpoint_name);
}
fn validate_http_endpoint(
@@ -196,11 +188,9 @@ impl KbConfig {
endpoint_names: &mut std::vec::Vec<std::string::String>,
) -> Result<(), crate::KbError> {
if endpoint.name.trim().is_empty() {
return Err(crate::KbError::Config(
"http endpoint name must not be empty".to_string(),
));
return Err(crate::KbError::Config("http endpoint name must not be empty".to_string()));
}
if endpoint_names.iter().any(|name| name == &endpoint.name) {
if endpoint_names.iter().any(|name| return name == &endpoint.name) {
return Err(crate::KbError::Config(format!(
"duplicated endpoint name '{}'",
endpoint.name
@@ -237,7 +227,7 @@ impl KbConfig {
)));
}
endpoint_names.push(endpoint.name.clone());
Ok(())
return Ok(());
}
fn validate_ws_endpoint(
@@ -246,11 +236,9 @@ impl KbConfig {
endpoint_names: &mut std::vec::Vec<std::string::String>,
) -> Result<(), crate::KbError> {
if endpoint.name.trim().is_empty() {
return Err(crate::KbError::Config(
"ws endpoint name must not be empty".to_string(),
));
return Err(crate::KbError::Config("ws endpoint name must not be empty".to_string()));
}
if endpoint_names.iter().any(|name| name == &endpoint.name) {
if endpoint_names.iter().any(|name| return name == &endpoint.name) {
return Err(crate::KbError::Config(format!(
"duplicated endpoint name '{}'",
endpoint.name
@@ -299,7 +287,7 @@ impl KbConfig {
)));
}
endpoint_names.push(endpoint.name.clone());
Ok(())
return Ok(());
}
}
@@ -350,7 +338,7 @@ pub struct KbLoggingConfig {
impl KbLoggingConfig {
/// Returns the resolved logging directory path.
pub fn directory_path(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.directory)
return kb_resolve_workspace_relative_path(&self.directory);
}
}
@@ -366,12 +354,12 @@ pub struct KbDataConfig {
impl KbDataConfig {
/// Returns the resolved SQLite database path.
pub fn sqlite_path_buf(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.sqlite_path)
return kb_resolve_workspace_relative_path(&self.sqlite_path);
}
/// Returns the resolved wallets directory path.
pub fn wallets_directory_path(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.wallets_directory)
return kb_resolve_workspace_relative_path(&self.wallets_directory);
}
}
@@ -433,7 +421,7 @@ impl KbHttpEndpointConfig {
Some(env_var_name) => env_var_name,
None => {
return Ok(self.url.clone());
}
},
};
let api_key_result = std::env::var(env_var_name);
let api_key = match api_key_result {
@@ -443,13 +431,13 @@ impl KbHttpEndpointConfig {
"cannot resolve api key env var '{}' for http endpoint '{}': {}",
env_var_name, self.name, error
)));
}
},
};
let placeholder = format!("${{{}}}", env_var_name);
if self.url.contains(&placeholder) {
return Ok(self.url.replace(&placeholder, &api_key));
}
Ok(self.url.clone())
return Ok(self.url.clone());
}
}
@@ -487,7 +475,7 @@ pub struct KbWsEndpointConfig {
impl KbWsEndpointConfig {
/// Returns the resolved endpoint URL.
pub fn resolved_url(&self) -> Result<std::string::String, crate::KbError> {
kb_resolve_endpoint_url(&self.url, &self.api_key_env_var)
return kb_resolve_endpoint_url(&self.url, &self.api_key_env_var);
}
}
@@ -512,7 +500,7 @@ pub struct KbSqliteDatabaseConfig {
impl KbSqliteDatabaseConfig {
/// Returns the resolved SQLite database path.
pub fn path_buf(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.path)
return kb_resolve_workspace_relative_path(&self.path);
}
}
@@ -531,8 +519,8 @@ pub struct KbDatabaseConfig {
fn kb_workspace_root_dir() -> std::path::PathBuf {
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
match manifest_dir.parent() {
Some(parent) => parent.to_path_buf(),
None => manifest_dir,
Some(parent) => return parent.to_path_buf(),
None => return manifest_dir,
}
}
@@ -541,7 +529,7 @@ fn kb_resolve_workspace_relative_path<P: AsRef<std::path::Path>>(path: P) -> std
if input_path.is_absolute() {
return input_path;
}
kb_workspace_root_dir().join(input_path)
return kb_workspace_root_dir().join(input_path);
}
fn kb_resolve_endpoint_url(
@@ -553,7 +541,7 @@ fn kb_resolve_endpoint_url(
Some(env_var_name) => env_var_name,
None => {
return Ok(url.to_string());
}
},
};
let placeholder = format!("${{{env_var_name}}}");
if !url.contains(&placeholder) {
@@ -567,7 +555,7 @@ fn kb_resolve_endpoint_url(
"environment variable '{}' is required to resolve endpoint url '{}': {error}",
env_var_name, url
)));
}
},
};
Ok(url.replace(&placeholder, &env_value))
return Ok(url.replace(&placeholder, &env_value));
}

View File

@@ -87,6 +87,7 @@ pub use queries::get_dex_by_code;
pub use queries::get_dex_decoded_event_by_key;
pub use queries::get_known_http_endpoint;
pub use queries::get_known_ws_endpoint;
pub use queries::get_latest_pump_fun_create_payload_by_mint;
pub use queries::get_launch_attribution_by_decoded_event_id;
pub use queries::get_launch_surface_by_code;
pub use queries::get_launch_surface_key_by_match;
@@ -98,6 +99,7 @@ pub use queries::get_pair_metric_by_pair_id;
pub use queries::get_pool_by_address;
pub use queries::get_pool_listing_by_pool_id;
pub use queries::get_pool_origin_by_pool_id;
pub use queries::get_token_by_id;
pub use queries::get_token_by_mint;
pub use queries::get_trade_event_by_decoded_event_id;
pub use queries::get_wallet_by_address;
@@ -108,6 +110,7 @@ pub use queries::insert_chain_instruction;
pub use queries::insert_db_runtime_event;
pub use queries::insert_onchain_observation;
pub use queries::list_chain_instructions_by_transaction_id;
pub use queries::list_chain_transaction_signatures_for_replay;
pub use queries::list_db_metadata;
pub use queries::list_dex_decoded_events_by_transaction_id;
pub use queries::list_dexes;
@@ -135,12 +138,14 @@ pub use queries::list_recent_swaps;
pub use queries::list_recent_token_burn_events;
pub use queries::list_recent_token_mint_events;
pub use queries::list_tokens;
pub use queries::list_tokens_missing_metadata;
pub use queries::list_trade_events_by_pair_id;
pub use queries::list_trade_events_by_transaction_id;
pub use queries::list_wallet_holdings_by_wallet_id;
pub use queries::list_wallet_participations_by_pool_id;
pub use queries::list_wallet_participations_by_wallet_id;
pub use queries::list_wallets;
pub use queries::update_pair_symbol;
pub use queries::upsert_chain_slot;
pub use queries::upsert_chain_transaction;
pub use queries::upsert_db_metadata;

View File

@@ -19,9 +19,7 @@ pub struct KbDatabase {
impl KbDatabase {
/// Opens a database connection without initializing the schema.
pub async fn connect(
config: &crate::KbDatabaseConfig,
) -> Result<Self, crate::KbError> {
pub async fn connect(config: &crate::KbDatabaseConfig) -> Result<Self, crate::KbError> {
if !config.enabled {
return Err(crate::KbError::Config(
"database is disabled in configuration".to_string(),
@@ -40,11 +38,11 @@ impl KbDatabase {
Ok(pool) => pool,
Err(error) => return Err(error),
};
Ok(Self {
return Ok(Self {
backend: crate::KbDatabaseBackend::Sqlite,
database_url,
connection: KbDatabaseConnection::Sqlite(pool),
})
});
},
}
}
@@ -64,47 +62,40 @@ impl KbDatabase {
return Err(error);
}
}
Ok(database)
return Ok(database);
}
/// Returns the configured backend.
pub fn backend(
&self,
) -> crate::KbDatabaseBackend {
self.backend
pub fn backend(&self) -> crate::KbDatabaseBackend {
return self.backend;
}
/// Returns a displayable database URL-like string.
pub fn database_url(
&self,
) -> &str {
&self.database_url
pub fn database_url(&self) -> &str {
return &self.database_url;
}
/// Pings the database.
pub async fn ping(
&self,
) -> Result<(), crate::KbError> {
pub async fn ping(&self) -> Result<(), crate::KbError> {
match &self.connection {
KbDatabaseConnection::Sqlite(pool) => {
let ping_result = sqlx::query("SELECT 1").execute(pool).await;
match ping_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
"cannot ping sqlite database '{}': {}",
self.database_url,
error
))),
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot ping sqlite database '{}': {}",
self.database_url, error
)));
},
}
},
}
}
/// Returns the underlying connection enum.
pub(crate) fn connection(
&self,
) -> &KbDatabaseConnection {
&self.connection
pub(crate) fn connection(&self) -> &KbDatabaseConnection {
return &self.connection;
}
}

View File

@@ -33,7 +33,7 @@ impl KbAnalysisSignalDto {
score: std::option::Option<f64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
id: None,
signal_kind,
severity,
@@ -42,7 +42,7 @@ impl KbAnalysisSignalDto {
score,
payload,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -63,7 +63,7 @@ impl TryFrom<crate::KbAnalysisSignalEntity> for KbAnalysisSignalDto {
"cannot parse analysis signal created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let payload_result = serde_json::from_str::<serde_json::Value>(&entity.payload_json);
let payload = match payload_result {
@@ -73,9 +73,9 @@ impl TryFrom<crate::KbAnalysisSignalEntity> for KbAnalysisSignalDto {
"cannot parse analysis signal payload_json '{}': {}",
entity.payload_json, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
signal_kind: entity.signal_kind,
severity,
@@ -84,6 +84,6 @@ impl TryFrom<crate::KbAnalysisSignalEntity> for KbAnalysisSignalDto {
score: entity.score,
payload,
created_at,
})
});
}
}

View File

@@ -49,7 +49,7 @@ impl KbChainInstructionDto {
parsed_type: std::option::Option<std::string::String>,
parsed_json: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
transaction_id,
parent_instruction_id,
@@ -63,7 +63,7 @@ impl KbChainInstructionDto {
parsed_type,
parsed_json,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -79,7 +79,7 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot convert chain instruction instruction_index '{}' to u32: {}",
entity.instruction_index, error
)));
}
},
};
let inner_instruction_index = match entity.inner_instruction_index {
Some(inner_instruction_index) => {
@@ -91,9 +91,9 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot convert chain instruction inner_instruction_index '{}' to u32: {}",
inner_instruction_index, error
)));
}
},
}
}
},
None => None,
};
let stack_height = match entity.stack_height {
@@ -106,9 +106,9 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot convert chain instruction stack_height '{}' to u32: {}",
stack_height, error
)));
}
},
}
}
},
None => None,
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
@@ -119,9 +119,9 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot parse chain instruction created_at '{}': {}",
entity.created_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
transaction_id: entity.transaction_id,
parent_instruction_id: entity.parent_instruction_id,
@@ -135,6 +135,6 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
parsed_type: entity.parsed_type,
parsed_json: entity.parsed_json,
created_at,
})
});
}
}

View File

@@ -25,13 +25,13 @@ impl KbChainSlotDto {
block_time_unix: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
slot,
parent_slot,
block_time_unix,
created_at: now,
updated_at: now,
}
};
}
}
@@ -47,7 +47,7 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot convert chain slot '{}' to u64: {}",
entity.slot, error
)));
}
},
};
let parent_slot = match entity.parent_slot {
Some(parent_slot) => {
@@ -59,9 +59,9 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot convert chain parent_slot '{}' to u64: {}",
parent_slot, error
)));
}
},
}
}
},
None => None,
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
@@ -72,7 +72,7 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot parse chain slot created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -82,14 +82,14 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot parse chain slot updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
slot,
parent_slot,
block_time_unix: entity.block_time_unix,
created_at,
updated_at,
})
});
}
}

View File

@@ -43,7 +43,7 @@ impl KbChainTransactionDto {
transaction_json: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
signature,
slot,
@@ -55,7 +55,7 @@ impl KbChainTransactionDto {
transaction_json,
created_at: now,
updated_at: now,
}
};
}
}
@@ -73,9 +73,9 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
"cannot convert chain transaction slot '{}' to u64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
@@ -86,7 +86,7 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
"cannot parse chain transaction created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -96,9 +96,9 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
"cannot parse chain transaction updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
signature: entity.signature,
slot,
@@ -110,6 +110,6 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
transaction_json: entity.transaction_json,
created_at,
updated_at,
})
});
}
}

View File

@@ -16,11 +16,11 @@ pub struct KbDbMetadataDto {
impl KbDbMetadataDto {
/// Creates a new metadata DTO with the current UTC timestamp.
pub fn new(key: std::string::String, value: std::string::String) -> Self {
Self {
return Self {
key,
value,
updated_at: chrono::Utc::now(),
}
};
}
}
@@ -36,22 +36,22 @@ impl TryFrom<crate::KbDbMetadataEntity> for KbDbMetadataDto {
"cannot parse db metadata timestamp '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
key: entity.key,
value: entity.value,
updated_at: parsed.with_timezone(&chrono::Utc),
})
});
}
}
impl From<KbDbMetadataDto> for crate::KbDbMetadataEntity {
fn from(dto: KbDbMetadataDto) -> Self {
Self {
return Self {
key: dto.key,
value: dto.value,
updated_at: dto.updated_at.to_rfc3339(),
}
};
}
}

View File

@@ -27,14 +27,14 @@ impl KbDbRuntimeEventDto {
source: std::string::String,
message: std::string::String,
) -> Self {
Self {
return Self {
id: None,
event_kind,
level,
source,
message,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -50,20 +50,20 @@ impl TryFrom<crate::KbDbRuntimeEventEntity> for KbDbRuntimeEventDto {
"cannot parse runtime event created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let level_result = crate::KbDbRuntimeEventLevel::from_i16(entity.level);
let level = match level_result {
Ok(level) => level,
Err(error) => return Err(error),
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
event_kind: entity.event_kind,
level,
source: entity.source,
message: entity.message,
created_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbDexDto {
is_enabled: bool,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
code,
name,
@@ -42,7 +42,7 @@ impl KbDexDto {
is_enabled,
created_at: now,
updated_at: now,
}
};
}
}
@@ -58,7 +58,7 @@ impl TryFrom<crate::KbDexEntity> for KbDexDto {
"cannot parse dex created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -68,9 +68,9 @@ impl TryFrom<crate::KbDexEntity> for KbDexDto {
"cannot parse dex updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
code: entity.code,
name: entity.name,
@@ -79,6 +79,6 @@ impl TryFrom<crate::KbDexEntity> for KbDexDto {
is_enabled: entity.is_enabled != 0,
created_at,
updated_at,
})
});
}
}

View File

@@ -49,7 +49,7 @@ impl KbDexDecodedEventDto {
market_account: std::option::Option<std::string::String>,
payload_json: std::string::String,
) -> Self {
Self {
return Self {
id: None,
transaction_id,
instruction_id,
@@ -63,7 +63,7 @@ impl KbDexDecodedEventDto {
market_account,
payload_json,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -79,9 +79,9 @@ impl TryFrom<crate::KbDexDecodedEventEntity> for KbDexDecodedEventDto {
"cannot parse dex decoded event created_at '{}': {}",
entity.created_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
transaction_id: entity.transaction_id,
instruction_id: entity.instruction_id,
@@ -95,6 +95,6 @@ impl TryFrom<crate::KbDexDecodedEventEntity> for KbDexDecodedEventDto {
market_account: entity.market_account,
payload_json: entity.payload_json,
created_at,
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbKnownHttpEndpointDto {
enabled: bool,
roles: std::vec::Vec<std::string::String>,
) -> Self {
Self {
return Self {
name,
provider,
url,
@@ -38,7 +38,7 @@ impl KbKnownHttpEndpointDto {
roles,
last_seen_at: None,
updated_at: chrono::Utc::now(),
}
};
}
}
@@ -55,7 +55,7 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
"cannot parse known http endpoint roles_json '{}': {}",
entity.roles_json, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -65,7 +65,7 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
"cannot parse known http endpoint updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
let last_seen_at = match entity.last_seen_at {
Some(last_seen_at_text) => {
@@ -77,12 +77,12 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
"cannot parse known http endpoint last_seen_at '{}': {}",
last_seen_at_text, error
)));
}
},
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
name: entity.name,
provider: entity.provider,
url: entity.url,
@@ -90,7 +90,7 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
roles,
last_seen_at,
updated_at,
})
});
}
}
@@ -106,16 +106,16 @@ impl TryFrom<KbKnownHttpEndpointDto> for crate::KbKnownHttpEndpointEntity {
"cannot serialize known http endpoint roles: {}",
error
)));
}
},
};
Ok(Self {
return Ok(Self {
name: dto.name,
provider: dto.provider,
url: dto.url,
enabled: if dto.enabled { 1 } else { 0 },
roles_json,
last_seen_at: dto.last_seen_at.map(|value| value.to_rfc3339()),
last_seen_at: dto.last_seen_at.map(|value| return value.to_rfc3339()),
updated_at: dto.updated_at.to_rfc3339(),
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbKnownWsEndpointDto {
enabled: bool,
roles: std::vec::Vec<std::string::String>,
) -> Self {
Self {
return Self {
name,
provider,
url,
@@ -38,7 +38,7 @@ impl KbKnownWsEndpointDto {
roles,
last_seen_at: None,
updated_at: chrono::Utc::now(),
}
};
}
}
@@ -55,7 +55,7 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
"cannot parse known ws endpoint roles_json '{}': {}",
entity.roles_json, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -65,7 +65,7 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
"cannot parse known ws endpoint updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
let last_seen_at = match entity.last_seen_at {
Some(last_seen_at_text) => {
@@ -77,12 +77,12 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
"cannot parse known ws endpoint last_seen_at '{}': {}",
last_seen_at_text, error
)));
}
},
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
name: entity.name,
provider: entity.provider,
url: entity.url,
@@ -90,7 +90,7 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
roles,
last_seen_at,
updated_at,
})
});
}
}
@@ -106,16 +106,16 @@ impl TryFrom<KbKnownWsEndpointDto> for crate::KbKnownWsEndpointEntity {
"cannot serialize known ws endpoint roles: {}",
error
)));
}
},
};
Ok(Self {
return Ok(Self {
name: dto.name,
provider: dto.provider,
url: dto.url,
enabled: if dto.enabled { 1 } else { 0 },
roles_json,
last_seen_at: dto.last_seen_at.map(|value| value.to_rfc3339()),
last_seen_at: dto.last_seen_at.map(|value| return value.to_rfc3339()),
updated_at: dto.updated_at.to_rfc3339(),
})
});
}
}

View File

@@ -45,7 +45,7 @@ impl KbLaunchAttributionDto {
matched_value: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
launch_surface_id,
transaction_id,
@@ -58,7 +58,7 @@ impl KbLaunchAttributionDto {
matched_value,
attributed_at: now,
updated_at: now,
}
};
}
}
@@ -74,7 +74,7 @@ impl TryFrom<crate::KbLaunchAttributionEntity> for KbLaunchAttributionDto {
"cannot parse launch_attribution attributed_at '{}': {}",
entity.attributed_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -84,9 +84,9 @@ impl TryFrom<crate::KbLaunchAttributionEntity> for KbLaunchAttributionDto {
"cannot parse launch_attribution updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
launch_surface_id: entity.launch_surface_id,
transaction_id: entity.transaction_id,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbLaunchAttributionEntity> for KbLaunchAttributionDto {
matched_value: entity.matched_value,
attributed_at,
updated_at,
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbLaunchSurfaceDto {
is_enabled: bool,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
code,
name,
@@ -38,7 +38,7 @@ impl KbLaunchSurfaceDto {
is_enabled,
created_at: now,
updated_at: now,
}
};
}
}
@@ -54,7 +54,7 @@ impl TryFrom<crate::KbLaunchSurfaceEntity> for KbLaunchSurfaceDto {
"cannot parse launch_surface created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -64,9 +64,9 @@ impl TryFrom<crate::KbLaunchSurfaceEntity> for KbLaunchSurfaceDto {
"cannot parse launch_surface updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
code: entity.code,
name: entity.name,
@@ -74,6 +74,6 @@ impl TryFrom<crate::KbLaunchSurfaceEntity> for KbLaunchSurfaceDto {
is_enabled: entity.is_enabled != 0,
created_at,
updated_at,
})
});
}
}

View File

@@ -27,14 +27,14 @@ impl KbLaunchSurfaceKeyDto {
match_value: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
launch_surface_id,
match_kind,
match_value,
created_at: now,
updated_at: now,
}
};
}
}
@@ -50,7 +50,7 @@ impl TryFrom<crate::KbLaunchSurfaceKeyEntity> for KbLaunchSurfaceKeyDto {
"cannot parse launch_surface_key created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -60,15 +60,15 @@ impl TryFrom<crate::KbLaunchSurfaceKeyEntity> for KbLaunchSurfaceKeyDto {
"cannot parse launch_surface_key updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
launch_surface_id: entity.launch_surface_id,
match_kind: entity.match_kind,
match_value: entity.match_value,
created_at,
updated_at,
})
});
}
}

View File

@@ -57,7 +57,7 @@ impl KbLiquidityEventDto {
quote_amount: std::string::String,
lp_amount: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -74,7 +74,7 @@ impl KbLiquidityEventDto {
quote_amount,
lp_amount,
executed_at: chrono::Utc::now(),
}
};
}
}
@@ -95,7 +95,7 @@ impl TryFrom<crate::KbLiquidityEventEntity> for KbLiquidityEventDto {
"cannot parse liquidity event executed_at '{}': {}",
entity.executed_at, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -107,12 +107,12 @@ impl TryFrom<crate::KbLiquidityEventEntity> for KbLiquidityEventDto {
"cannot convert liquidity event slot '{}' to u64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -129,6 +129,6 @@ impl TryFrom<crate::KbLiquidityEventEntity> for KbLiquidityEventDto {
quote_amount: entity.quote_amount,
lp_amount: entity.lp_amount,
executed_at,
})
});
}
}

View File

@@ -38,7 +38,7 @@ impl KbObservedTokenDto {
status: crate::KbObservedTokenStatus,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
mint,
symbol,
@@ -49,16 +49,14 @@ impl KbObservedTokenDto {
first_seen_at: now,
last_seen_at: now,
updated_at: now,
}
};
}
}
impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
type Error = crate::KbError;
fn try_from(
entity: crate::KbObservedTokenEntity,
) -> Result<Self, Self::Error> {
fn try_from(entity: crate::KbObservedTokenEntity) -> Result<Self, Self::Error> {
let status_result = crate::KbObservedTokenStatus::from_i16(entity.status);
let status = match status_result {
Ok(status) => status,
@@ -70,8 +68,7 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse observed token first_seen_at '{}': {}",
entity.first_seen_at,
error
entity.first_seen_at, error
)));
},
};
@@ -81,8 +78,7 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse observed token last_seen_at '{}': {}",
entity.last_seen_at,
error
entity.last_seen_at, error
)));
},
};
@@ -92,8 +88,7 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse observed token updated_at '{}': {}",
entity.updated_at,
error
entity.updated_at, error
)));
},
};
@@ -105,15 +100,14 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot convert observed token decimals '{}' to u8: {}",
decimals,
error
decimals, error
)));
},
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
mint: entity.mint,
symbol: entity.symbol,
@@ -124,6 +118,6 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
first_seen_at,
last_seen_at,
updated_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbOnchainObservationDto {
slot: std::option::Option<u64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
id: None,
observation_kind,
source_kind,
@@ -42,7 +42,7 @@ impl KbOnchainObservationDto {
slot,
payload,
observed_at: chrono::Utc::now(),
}
};
}
}
@@ -63,7 +63,7 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
"cannot parse on-chain observation observed_at '{}': {}",
entity.observed_at, error
)));
}
},
};
let payload_result = serde_json::from_str::<serde_json::Value>(&entity.payload_json);
let payload = match payload_result {
@@ -73,7 +73,7 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
"cannot parse on-chain observation payload_json '{}': {}",
entity.payload_json, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -85,12 +85,12 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
"cannot convert on-chain observation slot '{}' to u64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
observation_kind: entity.observation_kind,
source_kind,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
slot,
payload,
observed_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbPairDto {
symbol: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -42,7 +42,7 @@ impl KbPairDto {
symbol,
first_seen_at: now,
updated_at: now,
}
};
}
}
@@ -58,7 +58,7 @@ impl TryFrom<crate::KbPairEntity> for KbPairDto {
"cannot parse pair first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -68,9 +68,9 @@ impl TryFrom<crate::KbPairEntity> for KbPairDto {
"cannot parse pair updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -79,6 +79,6 @@ impl TryFrom<crate::KbPairEntity> for KbPairDto {
symbol: entity.symbol,
first_seen_at,
updated_at,
})
});
}
}

View File

@@ -45,7 +45,7 @@ impl KbPairAnalyticSignalDto {
last_transaction_id: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pair_id,
signal_kind,
@@ -58,7 +58,7 @@ impl KbPairAnalyticSignalDto {
last_transaction_id,
created_at: now,
updated_at: now,
}
};
}
}
@@ -80,7 +80,7 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
"cannot parse pair_analytic_signal signal_value_json '{}': {}",
entity.signal_value_json, error
)));
}
},
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(entity.created_at.as_str());
let created_at = match created_at_result {
@@ -90,7 +90,7 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
"cannot parse pair_analytic_signal created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(entity.updated_at.as_str());
let updated_at = match updated_at_result {
@@ -100,9 +100,9 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
"cannot parse pair_analytic_signal updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pair_id: entity.pair_id,
signal_kind: entity.signal_kind,
@@ -115,6 +115,6 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
last_transaction_id: entity.last_transaction_id,
created_at,
updated_at,
})
});
}
}

View File

@@ -64,7 +64,7 @@ impl KbPairCandleDto {
last_trade_signature: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pair_id,
timeframe_seconds,
@@ -83,7 +83,7 @@ impl KbPairCandleDto {
last_trade_signature,
created_at: now,
updated_at: now,
}
};
}
}
@@ -99,7 +99,7 @@ impl TryFrom<crate::KbPairCandleEntity> for KbPairCandleDto {
"cannot parse pair_candle created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -109,9 +109,9 @@ impl TryFrom<crate::KbPairCandleEntity> for KbPairCandleDto {
"cannot parse pair_candle updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pair_id: entity.pair_id,
timeframe_seconds: entity.timeframe_seconds,
@@ -130,6 +130,6 @@ impl TryFrom<crate::KbPairCandleEntity> for KbPairCandleDto {
last_trade_signature: entity.last_trade_signature,
created_at,
updated_at,
})
});
}
}

View File

@@ -39,7 +39,7 @@ impl KbPairMetricDto {
/// Creates a new pair-metric DTO.
pub fn new(pair_id: i64) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pair_id,
first_slot: None,
@@ -54,7 +54,7 @@ impl KbPairMetricDto {
last_price_quote_per_base: None,
created_at: now,
updated_at: now,
}
};
}
}
@@ -70,7 +70,7 @@ impl TryFrom<crate::KbPairMetricEntity> for KbPairMetricDto {
"cannot parse pair_metric created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -80,9 +80,9 @@ impl TryFrom<crate::KbPairMetricEntity> for KbPairMetricDto {
"cannot parse pair_metric updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pair_id: entity.pair_id,
first_slot: entity.first_slot,
@@ -97,6 +97,6 @@ impl TryFrom<crate::KbPairMetricEntity> for KbPairMetricDto {
last_price_quote_per_base: entity.last_price_quote_per_base,
created_at,
updated_at,
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbPoolDto {
status: crate::KbPoolStatus,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
address,
@@ -38,7 +38,7 @@ impl KbPoolDto {
status,
first_seen_at: now,
updated_at: now,
}
};
}
}
@@ -64,7 +64,7 @@ impl TryFrom<crate::KbPoolEntity> for KbPoolDto {
"cannot parse pool first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -74,9 +74,9 @@ impl TryFrom<crate::KbPoolEntity> for KbPoolDto {
"cannot parse pool updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
address: entity.address,
@@ -84,6 +84,6 @@ impl TryFrom<crate::KbPoolEntity> for KbPoolDto {
status,
first_seen_at,
updated_at,
})
});
}
}

View File

@@ -42,7 +42,7 @@ impl KbPoolListingDto {
initial_price_quote: std::option::Option<f64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -54,7 +54,7 @@ impl KbPoolListingDto {
initial_quote_reserve,
initial_price_quote,
updated_at: now,
}
};
}
}
@@ -75,7 +75,7 @@ impl TryFrom<crate::KbPoolListingEntity> for KbPoolListingDto {
"cannot parse pool_listing detected_at '{}': {}",
entity.detected_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -85,9 +85,9 @@ impl TryFrom<crate::KbPoolListingEntity> for KbPoolListingDto {
"cannot parse pool_listing updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbPoolListingEntity> for KbPoolListingDto {
initial_quote_reserve: entity.initial_quote_reserve,
initial_price_quote: entity.initial_price_quote,
updated_at,
})
});
}
}

View File

@@ -54,7 +54,7 @@ impl KbPoolOriginDto {
launch_attribution_id: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -70,7 +70,7 @@ impl KbPoolOriginDto {
launch_attribution_id,
created_at: now,
updated_at: now,
}
};
}
}
@@ -91,7 +91,7 @@ impl TryFrom<crate::KbPoolOriginEntity> for KbPoolOriginDto {
"cannot parse pool_origin created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -101,9 +101,9 @@ impl TryFrom<crate::KbPoolOriginEntity> for KbPoolOriginDto {
"cannot parse pool_origin updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -119,6 +119,6 @@ impl TryFrom<crate::KbPoolOriginEntity> for KbPoolOriginDto {
launch_attribution_id: entity.launch_attribution_id,
created_at,
updated_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbPoolTokenDto {
token_order: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pool_id,
token_id,
@@ -42,7 +42,7 @@ impl KbPoolTokenDto {
token_order,
created_at: now,
updated_at: now,
}
};
}
}
@@ -63,7 +63,7 @@ impl TryFrom<crate::KbPoolTokenEntity> for KbPoolTokenDto {
"cannot parse pool_token created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -73,9 +73,9 @@ impl TryFrom<crate::KbPoolTokenEntity> for KbPoolTokenDto {
"cannot parse pool_token updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pool_id: entity.pool_id,
token_id: entity.token_id,
@@ -84,6 +84,6 @@ impl TryFrom<crate::KbPoolTokenEntity> for KbPoolTokenDto {
token_order: entity.token_order,
created_at,
updated_at,
})
});
}
}

View File

@@ -54,7 +54,7 @@ impl KbSwapDto {
price_quote: std::option::Option<std::string::String>,
trade_side: crate::KbSwapTradeSide,
) -> Self {
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -70,7 +70,7 @@ impl KbSwapDto {
price_quote,
trade_side,
executed_at: chrono::Utc::now(),
}
};
}
}
@@ -91,7 +91,7 @@ impl TryFrom<crate::KbSwapEntity> for KbSwapDto {
"cannot parse swap executed_at '{}': {}",
entity.executed_at, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -103,12 +103,12 @@ impl TryFrom<crate::KbSwapEntity> for KbSwapDto {
"cannot convert swap slot '{}' to u64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -124,6 +124,6 @@ impl TryFrom<crate::KbSwapEntity> for KbSwapDto {
price_quote: entity.price_quote,
trade_side,
executed_at,
})
});
}
}

View File

@@ -36,7 +36,7 @@ impl KbTokenDto {
is_quote_token: bool,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
mint,
symbol,
@@ -46,7 +46,7 @@ impl KbTokenDto {
is_quote_token,
first_seen_at: now,
updated_at: now,
}
};
}
}
@@ -62,7 +62,7 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
"cannot parse token first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -72,7 +72,7 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
"cannot parse token updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
let decimals = match entity.decimals {
Some(decimals) => {
@@ -84,12 +84,12 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
"cannot convert token decimals '{}' to u8: {}",
decimals, error
)));
}
},
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
mint: entity.mint,
symbol: entity.symbol,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
is_quote_token: entity.is_quote_token != 0,
first_seen_at,
updated_at,
})
});
}
}

View File

@@ -39,7 +39,7 @@ impl KbTokenBurnEventDto {
amount: std::string::String,
supply_after: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
token_id,
signature,
@@ -50,24 +50,21 @@ impl KbTokenBurnEventDto {
amount,
supply_after,
executed_at: chrono::Utc::now(),
}
};
}
}
impl TryFrom<crate::KbTokenBurnEventEntity> for KbTokenBurnEventDto {
type Error = crate::KbError;
fn try_from(
entity: crate::KbTokenBurnEventEntity,
) -> Result<Self, Self::Error> {
fn try_from(entity: crate::KbTokenBurnEventEntity) -> Result<Self, Self::Error> {
let executed_at_result = chrono::DateTime::parse_from_rfc3339(&entity.executed_at);
let executed_at = match executed_at_result {
Ok(executed_at) => executed_at.with_timezone(&chrono::Utc),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse token burn event executed_at '{}': {}",
entity.executed_at,
error
entity.executed_at, error
)));
},
};
@@ -79,15 +76,14 @@ impl TryFrom<crate::KbTokenBurnEventEntity> for KbTokenBurnEventDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot convert token burn event slot '{}' to u64: {}",
slot,
error
slot, error
)));
},
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
token_id: entity.token_id,
signature: entity.signature,
@@ -98,6 +94,6 @@ impl TryFrom<crate::KbTokenBurnEventEntity> for KbTokenBurnEventDto {
amount: entity.amount,
supply_after: entity.supply_after,
executed_at,
})
});
}
}

View File

@@ -39,7 +39,7 @@ impl KbTokenMintEventDto {
amount: std::string::String,
supply_after: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
token_id,
signature,
@@ -50,7 +50,7 @@ impl KbTokenMintEventDto {
amount,
supply_after,
executed_at: chrono::Utc::now(),
}
};
}
}
@@ -66,7 +66,7 @@ impl TryFrom<crate::KbTokenMintEventEntity> for KbTokenMintEventDto {
"cannot parse token mint event executed_at '{}': {}",
entity.executed_at, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -78,12 +78,12 @@ impl TryFrom<crate::KbTokenMintEventEntity> for KbTokenMintEventDto {
"cannot convert token mint event slot '{}' to u64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
token_id: entity.token_id,
signature: entity.signature,
@@ -94,6 +94,6 @@ impl TryFrom<crate::KbTokenMintEventEntity> for KbTokenMintEventDto {
amount: entity.amount,
supply_after: entity.supply_after,
executed_at,
})
});
}
}

View File

@@ -66,7 +66,7 @@ impl KbTradeEventDto {
payload_json: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -86,7 +86,7 @@ impl KbTradeEventDto {
payload_json,
created_at: now,
updated_at: now,
}
};
}
}
@@ -108,7 +108,7 @@ impl TryFrom<crate::KbTradeEventEntity> for KbTradeEventDto {
"cannot parse trade_event created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -118,9 +118,9 @@ impl TryFrom<crate::KbTradeEventEntity> for KbTradeEventDto {
"cannot parse trade_event updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -140,14 +140,14 @@ impl TryFrom<crate::KbTradeEventEntity> for KbTradeEventDto {
payload_json: entity.payload_json,
created_at,
updated_at,
})
});
}
}
fn kb_trade_side_from_string(value: &str) -> crate::KbSwapTradeSide {
match value {
"BuyBase" => crate::KbSwapTradeSide::BuyBase,
"SellBase" => crate::KbSwapTradeSide::SellBase,
_ => crate::KbSwapTradeSide::Unknown,
"BuyBase" => return crate::KbSwapTradeSide::BuyBase,
"SellBase" => return crate::KbSwapTradeSide::SellBase,
_ => return crate::KbSwapTradeSide::Unknown,
}
}

View File

@@ -24,13 +24,13 @@ impl KbWalletDto {
label: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
address,
label,
first_seen_at: now,
last_seen_at: now,
}
};
}
}
@@ -46,7 +46,7 @@ impl TryFrom<crate::KbWalletEntity> for KbWalletDto {
"cannot parse wallet first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let last_seen_at_result = chrono::DateTime::parse_from_rfc3339(&entity.last_seen_at);
let last_seen_at = match last_seen_at_result {
@@ -56,14 +56,14 @@ impl TryFrom<crate::KbWalletEntity> for KbWalletDto {
"cannot parse wallet last_seen_at '{}': {}",
entity.last_seen_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
address: entity.address,
label: entity.label,
first_seen_at,
last_seen_at,
})
});
}
}

View File

@@ -54,7 +54,7 @@ impl KbWalletHoldingDto {
source_endpoint_name: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
wallet_id,
token_id,
@@ -70,7 +70,7 @@ impl KbWalletHoldingDto {
source_endpoint_name,
created_at: now,
updated_at: now,
}
};
}
}
@@ -91,7 +91,7 @@ impl TryFrom<crate::KbWalletHoldingEntity> for KbWalletHoldingDto {
"cannot parse wallet_holding created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -101,9 +101,9 @@ impl TryFrom<crate::KbWalletHoldingEntity> for KbWalletHoldingDto {
"cannot parse wallet_holding updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
wallet_id: entity.wallet_id,
token_id: entity.token_id,

View File

@@ -52,8 +52,7 @@ impl KbWalletParticipationDto {
pair_id,
role.as_str(),
);
Self {
return Self {
id: None,
wallet_id,
transaction_id,
@@ -66,7 +65,7 @@ impl KbWalletParticipationDto {
source_endpoint_name,
created_at: now,
updated_at: now,
}
};
}
}
@@ -87,7 +86,7 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
"cannot parse wallet_participation created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -97,9 +96,9 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
"cannot parse wallet_participation updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
wallet_id: entity.wallet_id,
transaction_id: entity.transaction_id,
@@ -112,7 +111,7 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
source_endpoint_name: entity.source_endpoint_name,
created_at,
updated_at,
})
});
}
}
@@ -127,8 +126,8 @@ fn kb_build_wallet_participation_unique_key(
let decoded_event_id_value = decoded_event_id.unwrap_or_default();
let pool_id_value = pool_id.unwrap_or_default();
let pair_id_value = pair_id.unwrap_or_default();
format!(
return format!(
"{}:{}:{}:{}:{}:{}",
wallet_id, transaction_id, decoded_event_id_value, pool_id_value, pair_id_value, role
)
);
}

View File

@@ -48,6 +48,7 @@ pub use chain_slot::get_chain_slot;
pub use chain_slot::list_recent_chain_slots;
pub use chain_slot::upsert_chain_slot;
pub use chain_transaction::get_chain_transaction_by_signature;
pub use chain_transaction::list_chain_transaction_signatures_for_replay;
pub use chain_transaction::list_recent_chain_transactions;
pub use chain_transaction::upsert_chain_transaction;
pub use db_metadata::get_db_metadata;
@@ -59,6 +60,7 @@ pub use dex::get_dex_by_code;
pub use dex::list_dexes;
pub use dex::upsert_dex;
pub use dex_decoded_event::get_dex_decoded_event_by_key;
pub use dex_decoded_event::get_latest_pump_fun_create_payload_by_mint;
pub use dex_decoded_event::list_dex_decoded_events_by_transaction_id;
pub use dex_decoded_event::upsert_dex_decoded_event;
pub use known_http_endpoint::get_known_http_endpoint;
@@ -85,6 +87,7 @@ pub use onchain_observation::insert_onchain_observation;
pub use onchain_observation::list_recent_onchain_observations;
pub use pair::get_pair_by_pool_id;
pub use pair::list_pairs;
pub use pair::update_pair_symbol;
pub use pair::upsert_pair;
pub use pair_analytic_signal::get_pair_analytic_signal_by_key;
pub use pair_analytic_signal::list_pair_analytic_signals_by_pair_id;
@@ -108,8 +111,10 @@ pub use pool_token::list_pool_tokens_by_pool_id;
pub use pool_token::upsert_pool_token;
pub use swap::list_recent_swaps;
pub use swap::upsert_swap;
pub use token::get_token_by_id;
pub use token::get_token_by_mint;
pub use token::list_tokens;
pub use token::list_tokens_missing_metadata;
pub use token::upsert_token;
pub use token_burn_event::list_recent_token_burn_events;
pub use token_burn_event::upsert_token_burn_event;

View File

@@ -15,7 +15,7 @@ pub async fn insert_analysis_signal(
"cannot serialize analysis signal payload: {}",
error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -49,10 +49,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_analysis_signals on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -92,7 +92,7 @@ LIMIT ?
"cannot list analysis signals on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -103,8 +103,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -58,10 +58,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_chain_instructions on sqlite: {}",
error
)));
}
},
};
Ok(insert_result.last_insert_rowid())
}
return Ok(insert_result.last_insert_rowid());
},
}
}
@@ -103,7 +103,7 @@ ORDER BY instruction_index ASC, inner_instruction_index ASC, id ASC
"cannot list kb_chain_instructions for transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -114,8 +114,8 @@ ORDER BY instruction_index ASC, inner_instruction_index ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -141,8 +141,8 @@ WHERE transaction_id = ?
transaction_id, error
)));
}
Ok(())
}
return Ok(());
},
}
}
@@ -163,9 +163,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
async fn make_transaction(database: &crate::KbDatabase) -> i64 {
@@ -179,9 +179,9 @@ mod tests {
None,
r#"{"transaction":{"message":{"instructions":[]}}}"#.to_string(),
);
crate::upsert_chain_transaction(database, &dto)
return crate::upsert_chain_transaction(database, &dto)
.await
.expect("chain transaction upsert must succeed")
.expect("chain transaction upsert must succeed");
}
#[tokio::test]

View File

@@ -15,7 +15,7 @@ pub async fn upsert_chain_slot(
"cannot convert chain slot '{}' to i64: {}",
dto.slot, error
)));
}
},
};
let parent_slot = match dto.parent_slot {
Some(parent_slot) => {
@@ -27,9 +27,9 @@ pub async fn upsert_chain_slot(
"cannot convert chain parent_slot '{}' to i64: {}",
parent_slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -63,8 +63,8 @@ ON CONFLICT(slot) DO UPDATE SET
error
)));
}
Ok(dto.slot)
}
return Ok(dto.slot);
},
}
}
@@ -81,7 +81,7 @@ pub async fn get_chain_slot(
"cannot convert requested chain slot '{}' to i64: {}",
slot, error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -108,19 +108,19 @@ LIMIT 1
"cannot fetch kb_chain_slots for slot '{}' on sqlite: {}",
slot, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbChainSlotDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -157,7 +157,7 @@ LIMIT ?
"cannot list kb_chain_slots on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -168,8 +168,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -190,9 +190,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]

View File

@@ -17,9 +17,9 @@ pub async fn upsert_chain_transaction(
"cannot convert chain transaction slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -80,13 +80,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_chain_transactions id for signature '{}' on sqlite: {}",
dto.signature, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_chain_transactions id for signature '{}' on sqlite: {}",
dto.signature, error
)));
},
}
}
},
}
}
@@ -126,19 +128,19 @@ LIMIT 1
"cannot fetch kb_chain_transactions for signature '{}' on sqlite: {}",
signature, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbChainTransactionDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -181,7 +183,7 @@ LIMIT ?
"cannot list kb_chain_transactions on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -192,8 +194,49 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
/// Lists persisted chain transaction signatures for local pipeline replay.
pub async fn list_chain_transaction_signatures_for_replay(
database: &crate::KbDatabase,
limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let effective_limit = match limit {
Some(limit) => {
if limit <= 0 {
10_000
} else {
limit
}
},
None => 10_000,
};
let query_result = sqlx::query_scalar::<sqlx::Sqlite, std::string::String>(
r#"
SELECT signature
FROM kb_chain_transactions
ORDER BY id ASC
LIMIT ?
"#,
)
.bind(effective_limit)
.fetch_all(pool)
.await;
match query_result {
Ok(signatures) => return Ok(signatures),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot list kb_chain_transactions signatures for local replay on sqlite: {}",
error
)));
},
}
},
}
}
@@ -214,9 +257,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]
@@ -249,10 +292,7 @@ mod tests {
assert_eq!(fetched.signature, "sig-chain-transaction-1");
assert_eq!(fetched.slot, Some(515151));
assert_eq!(fetched.block_time_unix, Some(1_700_000_001));
assert_eq!(
fetched.source_endpoint_name,
Some("helius_primary_http".to_string())
);
assert_eq!(fetched.source_endpoint_name, Some("helius_primary_http".to_string()));
assert_eq!(fetched.version_text, Some("0".to_string()));
assert_eq!(fetched.meta_json, Some(r#"{"fee":5000}"#.to_string()));
let listed = crate::list_recent_chain_transactions(&database, 10)

View File

@@ -29,13 +29,15 @@ ON CONFLICT(key) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
"cannot upsert kb_db_metadata on sqlite: {}",
error
))),
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_db_metadata on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -67,19 +69,19 @@ LIMIT 1
"cannot read kb_db_metadata '{}' on sqlite: {}",
key, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDbMetadataDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -108,7 +110,7 @@ ORDER BY key ASC
"cannot list kb_db_metadata on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -119,8 +121,8 @@ ORDER BY key ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -146,9 +148,7 @@ mod tests {
.await
.expect("database init must succeed");
let dto = crate::KbDbMetadataDto::new("schema_version".to_string(), "0.5.0".to_string());
crate::upsert_db_metadata(&database, &dto)
.await
.expect("upsert must succeed");
crate::upsert_db_metadata(&database, &dto).await.expect("upsert must succeed");
let fetched = crate::get_db_metadata(&database, "schema_version")
.await
.expect("fetch must succeed");
@@ -156,9 +156,7 @@ mod tests {
let fetched = fetched.expect("metadata must exist");
assert_eq!(fetched.key, "schema_version");
assert_eq!(fetched.value, "0.5.0");
let listed = crate::list_db_metadata(&database)
.await
.expect("list must succeed");
let listed = crate::list_db_metadata(&database).await.expect("list must succeed");
assert!(!listed.is_empty());
}
}

View File

@@ -35,10 +35,10 @@ VALUES (?, ?, ?, ?, ?)
"cannot insert kb_db_runtime_events on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -76,7 +76,7 @@ LIMIT ?
"cannot list runtime events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -87,8 +87,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -56,13 +56,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_dexes id for code '{}' on sqlite: {}",
dto.code, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_dexes id for code '{}' on sqlite: {}",
dto.code, error
)));
},
}
}
},
}
}
@@ -99,19 +101,19 @@ LIMIT 1
"cannot read kb_dexes '{}' on sqlite: {}",
code, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDexDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -145,7 +147,7 @@ ORDER BY code ASC
"cannot list kb_dexes on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -156,8 +158,8 @@ ORDER BY code ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -184,13 +186,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -200,9 +196,7 @@ mod tests {
.expect("get dex must succeed");
assert!(dex.is_some());
assert_eq!(dex.expect("dex must exist").name, "Raydium");
let dexes = crate::list_dexes(&database)
.await
.expect("list dexes must succeed");
let dexes = crate::list_dexes(&database).await.expect("list dexes must succeed");
assert_eq!(dexes.len(), 1);
}
}

View File

@@ -77,13 +77,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_dex_decoded_events id on sqlite: {}",
error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_dex_decoded_events id on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -135,19 +137,19 @@ LIMIT 1
"cannot fetch kb_dex_decoded_events on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDexDecodedEventDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -189,7 +191,7 @@ ORDER BY id ASC
"cannot list kb_dex_decoded_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -200,8 +202,48 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
/// Returns the latest Pump.fun create payload associated with a token mint.
pub async fn get_latest_pump_fun_create_payload_by_mint(
database: &crate::KbDatabase,
mint: &str,
) -> Result<std::option::Option<std::string::String>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let payload_result = sqlx::query_scalar::<sqlx::Sqlite, std::string::String>(
r#"
SELECT payload_json
FROM kb_dex_decoded_events
WHERE protocol_name = 'pump_fun'
AND event_kind IN ('pump_fun.create', 'pump_fun.create_v2_token')
AND (
token_a_mint = ?
OR json_extract(payload_json, '$.mint') = ?
OR json_extract(payload_json, '$.tokenMint') = ?
)
ORDER BY id DESC
LIMIT 1
"#,
)
.bind(mint)
.bind(mint)
.bind(mint)
.fetch_optional(pool)
.await;
match payload_result {
Ok(payload_option) => return Ok(payload_option),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot read latest pump.fun create payload for mint '{}' on sqlite: {}",
mint, error
)));
},
}
},
}
}
@@ -228,7 +270,7 @@ mod tests {
};
let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
match database_result {
Ok(database) => database,
Ok(database) => return database,
Err(error) => panic!("database init must succeed: {}", error),
}
}

View File

@@ -46,13 +46,15 @@ ON CONFLICT(name) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
"cannot upsert kb_known_http_endpoints on sqlite: {}",
error
))),
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_known_http_endpoints on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -88,19 +90,19 @@ LIMIT 1
"cannot read known http endpoint '{}' on sqlite: {}",
name, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbKnownHttpEndpointDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -133,7 +135,7 @@ ORDER BY name ASC
"cannot list known http endpoints on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -144,8 +146,8 @@ ORDER BY name ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -187,9 +189,7 @@ mod tests {
let fetched = fetched.expect("endpoint must exist");
assert_eq!(fetched.provider, "helius");
assert_eq!(fetched.roles.len(), 2);
let listed = crate::list_known_http_endpoints(&database)
.await
.expect("list must succeed");
let listed = crate::list_known_http_endpoints(&database).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -12,7 +12,6 @@ pub async fn upsert_known_ws_endpoint(
Ok(entity) => entity,
Err(error) => return Err(error),
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
@@ -46,13 +45,15 @@ ON CONFLICT(name) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
"cannot upsert kb_known_ws_endpoints on sqlite: {}",
error
))),
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_known_ws_endpoints on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -88,19 +89,19 @@ LIMIT 1
"cannot read known ws endpoint '{}' on sqlite: {}",
name, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbKnownWsEndpointDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -133,7 +134,7 @@ ORDER BY name ASC
"cannot list known ws endpoints on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -144,8 +145,8 @@ ORDER BY name ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -187,9 +188,7 @@ mod tests {
let fetched = fetched.expect("endpoint must exist");
assert_eq!(fetched.provider, "solana");
assert_eq!(fetched.roles.len(), 2);
let listed = crate::list_known_ws_endpoints(&database)
.await
.expect("list must succeed");
let listed = crate::list_known_ws_endpoints(&database).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -68,13 +68,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_attributions id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_attributions id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
)));
},
}
}
},
}
}
@@ -85,9 +87,8 @@ pub async fn get_launch_attribution_by_decoded_event_id(
) -> Result<std::option::Option<crate::KbLaunchAttributionDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -105,10 +106,10 @@ FROM kb_launch_attributions
WHERE decoded_event_id = ?
LIMIT 1
"#,
)
.bind(decoded_event_id)
.fetch_optional(pool)
.await;
)
.bind(decoded_event_id)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -116,13 +117,13 @@ LIMIT 1
"cannot read kb_launch_attributions by decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchAttributionDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbLaunchAttributionDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -133,9 +134,8 @@ pub async fn list_launch_attributions_by_pool_id(
) -> Result<std::vec::Vec<crate::KbLaunchAttributionDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -153,10 +153,10 @@ FROM kb_launch_attributions
WHERE pool_id = ?
ORDER BY attributed_at ASC, id ASC
"#,
)
.bind(pool_id)
.fetch_all(pool)
.await;
)
.bind(pool_id)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -164,7 +164,7 @@ ORDER BY attributed_at ASC, id ASC
"cannot list kb_launch_attributions by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -175,7 +175,7 @@ ORDER BY attributed_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -53,13 +53,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surfaces id for code '{}' on sqlite: {}",
dto.code, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surfaces id for code '{}' on sqlite: {}",
dto.code, error
)));
},
}
}
},
}
}
@@ -70,9 +72,8 @@ pub async fn get_launch_surface_by_code(
) -> Result<std::option::Option<crate::KbLaunchSurfaceDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
SELECT
id,
code,
@@ -85,10 +86,10 @@ FROM kb_launch_surfaces
WHERE code = ?
LIMIT 1
"#,
)
.bind(code)
.fetch_optional(pool)
.await;
)
.bind(code)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -96,13 +97,13 @@ LIMIT 1
"cannot read kb_launch_surfaces '{}' on sqlite: {}",
code, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchSurfaceDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbLaunchSurfaceDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -112,9 +113,8 @@ pub async fn list_launch_surfaces(
) -> Result<std::vec::Vec<crate::KbLaunchSurfaceDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
SELECT
id,
code,
@@ -126,9 +126,9 @@ SELECT
FROM kb_launch_surfaces
ORDER BY code ASC
"#,
)
.fetch_all(pool)
.await;
)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -136,7 +136,7 @@ ORDER BY code ASC
"cannot list kb_launch_surfaces on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -147,7 +147,7 @@ ORDER BY code ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -50,13 +50,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surface_keys id for '{}:{}' on sqlite: {}",
dto.match_kind, dto.match_value, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surface_keys id for '{}:{}' on sqlite: {}",
dto.match_kind, dto.match_value, error
)));
},
}
}
},
}
}
@@ -68,9 +70,8 @@ pub async fn get_launch_surface_key_by_match(
) -> Result<std::option::Option<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -82,11 +83,11 @@ FROM kb_launch_surface_keys
WHERE match_kind = ? AND match_value = ?
LIMIT 1
"#,
)
.bind(match_kind)
.bind(match_value)
.fetch_optional(pool)
.await;
)
.bind(match_kind)
.bind(match_value)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -94,13 +95,13 @@ LIMIT 1
"cannot read kb_launch_surface_keys '{}:{}' on sqlite: {}",
match_kind, match_value, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -111,9 +112,8 @@ pub async fn list_launch_surface_keys_by_surface_id(
) -> Result<std::vec::Vec<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -125,10 +125,10 @@ FROM kb_launch_surface_keys
WHERE launch_surface_id = ?
ORDER BY match_kind ASC, match_value ASC
"#,
)
.bind(launch_surface_id)
.fetch_all(pool)
.await;
)
.bind(launch_surface_id)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -136,7 +136,7 @@ ORDER BY match_kind ASC, match_value ASC
"cannot list kb_launch_surface_keys by surface_id '{}' on sqlite: {}",
launch_surface_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -147,7 +147,7 @@ ORDER BY match_kind ASC, match_value ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -17,9 +17,9 @@ pub async fn upsert_liquidity_event(
"cannot convert liquidity event slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -96,13 +96,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_liquidity_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_liquidity_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -150,7 +152,7 @@ LIMIT ?
"cannot list kb_liquidity_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -161,7 +163,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -63,13 +63,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match select_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_observed_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_observed_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
)));
},
}
}
},
}
}
@@ -108,19 +110,19 @@ LIMIT 1
"cannot read observed token '{}' on sqlite: {}",
mint, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbObservedTokenDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -162,7 +164,7 @@ LIMIT ?
"cannot list observed tokens on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -173,8 +175,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -222,9 +224,7 @@ mod tests {
assert_eq!(fetched.symbol.as_deref(), Some("WSOL"));
assert_eq!(fetched.decimals, Some(9));
assert_eq!(fetched.status, crate::KbObservedTokenStatus::Active);
let listed = crate::list_observed_tokens(&database, 10)
.await
.expect("list must succeed");
let listed = crate::list_observed_tokens(&database, 10).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -15,7 +15,7 @@ pub async fn insert_onchain_observation(
"cannot serialize on-chain observation payload: {}",
error
)));
}
},
};
let slot_i64 = match dto.slot {
Some(slot) => {
@@ -27,9 +27,9 @@ pub async fn insert_onchain_observation(
"cannot convert on-chain observation slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -64,10 +64,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_onchain_observations on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -107,7 +107,7 @@ LIMIT ?
"cannot list on-chain observations on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -118,8 +118,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -25,7 +25,11 @@ ON CONFLICT(pool_id) DO UPDATE SET
dex_id = excluded.dex_id,
base_token_id = excluded.base_token_id,
quote_token_id = excluded.quote_token_id,
symbol = excluded.symbol,
symbol = CASE
WHEN excluded.symbol IS NOT NULL AND trim(excluded.symbol) <> ''
THEN excluded.symbol
ELSE kb_pairs.symbol
END,
updated_at = excluded.updated_at
"#,
)
@@ -56,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pairs id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pairs id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
)));
},
}
}
},
}
}
@@ -99,19 +105,54 @@ LIMIT 1
"cannot read kb_pairs by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPairDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
/// Updates the display symbol of one normalized pair row.
pub async fn update_pair_symbol(
database: &crate::KbDatabase,
pair_id: i64,
symbol: std::option::Option<&str>,
) -> Result<bool, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
r#"
UPDATE kb_pairs
SET
symbol = ?,
updated_at = ?
WHERE id = ?
"#,
)
.bind(symbol)
.bind(chrono::Utc::now().to_rfc3339())
.bind(pair_id)
.execute(pool)
.await;
match query_result {
Ok(done) => return Ok(done.rows_affected() > 0),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot update kb_pairs symbol for id '{}' on sqlite: {}",
pair_id, error
)));
},
}
},
}
}
@@ -145,7 +186,7 @@ ORDER BY id ASC
"cannot list kb_pairs on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -156,8 +197,8 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -184,13 +225,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -248,13 +283,8 @@ mod tests {
.await
.expect("get pair must succeed");
assert!(pair.is_some());
assert_eq!(
pair.expect("pair must exist").symbol.as_deref(),
Some("BASE/WSOL")
);
let pairs = crate::list_pairs(&database)
.await
.expect("list pairs must succeed");
assert_eq!(pair.expect("pair must exist").symbol.as_deref(), Some("BASE/WSOL"));
let pairs = crate::list_pairs(&database).await.expect("list pairs must succeed");
assert_eq!(pairs.len(), 1);
}
}

View File

@@ -15,7 +15,7 @@ pub async fn upsert_pair_analytic_signal(
"cannot serialize pair analytic signal payload: {}",
error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -77,13 +77,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_analytic_signals id on sqlite: {}",
error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_analytic_signals id on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -97,9 +99,8 @@ pub async fn get_pair_analytic_signal_by_key(
) -> Result<std::option::Option<crate::KbPairAnalyticSignalDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
SELECT
id,
pair_id,
@@ -117,13 +118,13 @@ FROM kb_pair_analytic_signals
WHERE pair_id = ? AND signal_kind = ? AND timeframe_seconds = ? AND bucket_start_unix = ?
LIMIT 1
"#,
)
.bind(pair_id)
.bind(signal_kind)
.bind(timeframe_seconds)
.bind(bucket_start_unix)
.fetch_optional(pool)
.await;
)
.bind(pair_id)
.bind(signal_kind)
.bind(timeframe_seconds)
.bind(bucket_start_unix)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -131,13 +132,13 @@ LIMIT 1
"cannot read kb_pair_analytic_signals by key on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairAnalyticSignalDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPairAnalyticSignalDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -148,9 +149,8 @@ pub async fn list_pair_analytic_signals_by_pair_id(
) -> Result<std::vec::Vec<crate::KbPairAnalyticSignalDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
SELECT
id,
pair_id,
@@ -168,10 +168,10 @@ FROM kb_pair_analytic_signals
WHERE pair_id = ?
ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
"#,
)
.bind(pair_id)
.fetch_all(pool)
.await;
)
.bind(pair_id)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -179,7 +179,7 @@ ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
"cannot list kb_pair_analytic_signals by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -190,7 +190,7 @@ ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -87,13 +87,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_candles id for pair_id '{}' timeframe '{}' bucket '{}' on sqlite: {}",
dto.pair_id, dto.timeframe_seconds, dto.bucket_start_unix, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_candles id for pair_id '{}' timeframe '{}' bucket '{}' on sqlite: {}",
dto.pair_id, dto.timeframe_seconds, dto.bucket_start_unix, error
)));
},
}
}
},
}
}
@@ -144,13 +146,13 @@ LIMIT 1
"cannot read kb_pair_candles by key on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairCandleDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPairCandleDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -199,7 +201,7 @@ ORDER BY bucket_start_unix ASC, id ASC
"cannot list kb_pair_candles by pair_id '{}' timeframe '{}' on sqlite: {}",
pair_id, timeframe_seconds, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -210,7 +212,7 @@ ORDER BY bucket_start_unix ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -74,13 +74,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_metrics id for pair_id '{}' on sqlite: {}",
dto.pair_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_metrics id for pair_id '{}' on sqlite: {}",
dto.pair_id, error
)));
},
}
}
},
}
}
@@ -123,13 +125,13 @@ LIMIT 1
"cannot read kb_pair_metrics by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairMetricDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPairMetricDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -169,7 +171,7 @@ ORDER BY pair_id ASC
"cannot list kb_pair_metrics on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -180,7 +182,7 @@ ORDER BY pair_id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -53,13 +53,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pools id for address '{}' on sqlite: {}",
dto.address, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pools id for address '{}' on sqlite: {}",
dto.address, error
)));
},
}
}
},
}
}
@@ -95,19 +97,19 @@ LIMIT 1
"cannot read kb_pools '{}' on sqlite: {}",
address, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPoolDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -140,7 +142,7 @@ ORDER BY id ASC
"cannot list kb_pools on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -151,8 +153,8 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -179,13 +181,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -206,13 +202,8 @@ mod tests {
.await
.expect("get pool must succeed");
assert!(pool.is_some());
assert_eq!(
pool.expect("pool must exist").pool_kind,
crate::KbPoolKind::Amm
);
let pools = crate::list_pools(&database)
.await
.expect("list pools must succeed");
assert_eq!(pool.expect("pool must exist").pool_kind, crate::KbPoolKind::Amm);
let pools = crate::list_pools(&database).await.expect("list pools must succeed");
assert_eq!(pools.len(), 1);
}
}

View File

@@ -66,13 +66,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_listings id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_listings id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
)));
},
}
}
},
}
}
@@ -112,19 +114,19 @@ LIMIT 1
"cannot read kb_pool_listings by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPoolListingDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -161,7 +163,7 @@ ORDER BY detected_at ASC, id ASC
"cannot list kb_pool_listings on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -172,8 +174,8 @@ ORDER BY detected_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -200,13 +202,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");

View File

@@ -69,13 +69,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_origins id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_origins id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
)));
},
}
}
},
}
}
@@ -119,13 +121,13 @@ LIMIT 1
"cannot read kb_pool_origins by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPoolOriginDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPoolOriginDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -166,7 +168,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_pool_origins on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -177,7 +179,7 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -56,13 +56,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_tokens id on sqlite: {}",
error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_tokens id on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -99,7 +101,7 @@ ORDER BY token_order ASC, id ASC
"cannot list kb_pool_tokens for pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -110,8 +112,8 @@ ORDER BY token_order ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -138,13 +140,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");

View File

@@ -17,9 +17,9 @@ pub async fn upsert_swap(
"cannot convert swap slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -93,13 +93,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_swaps id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_swaps id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -146,7 +148,7 @@ LIMIT ?
"cannot list kb_swaps on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -157,7 +159,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
)));
},
}
}
},
}
}
@@ -104,22 +106,71 @@ LIMIT 1
"cannot read kb_tokens '{}' on sqlite: {}",
mint, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbTokenDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
/// Reads one normalized token row by internal id.
pub async fn get_token_by_id(
database: &crate::KbDatabase,
token_id: i64,
) -> Result<std::option::Option<crate::KbTokenDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE id = ?
LIMIT 1
"#,
)
.bind(token_id)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot read kb_tokens id '{}' on sqlite: {}",
token_id, error
)));
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbTokenDto::try_from(entity);
match dto_result {
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
/// Lists all normalized token rows ordered by mint.
pub async fn list_tokens(
@@ -152,7 +203,7 @@ ORDER BY mint ASC, id ASC
"cannot list kb_tokens on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -163,7 +214,161 @@ ORDER BY mint ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
}
/// Lists token rows whose display or mint metadata is incomplete.
pub async fn list_tokens_missing_metadata(
database: &crate::KbDatabase,
limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<crate::KbTokenDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let entities_result = match limit {
Some(limit) => {
sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE symbol IS NULL
OR trim(symbol) = ''
OR name IS NULL
OR trim(name) = ''
OR decimals IS NULL
OR token_program IS NULL
OR trim(token_program) = ''
ORDER BY updated_at ASC, id ASC
LIMIT ?
"#,
)
.bind(limit)
.fetch_all(pool)
.await
},
None => {
sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE symbol IS NULL
OR trim(symbol) = ''
OR name IS NULL
OR trim(name) = ''
OR decimals IS NULL
OR token_program IS NULL
OR trim(token_program) = ''
ORDER BY updated_at ASC, id ASC
"#,
)
.fetch_all(pool)
.await
},
};
let entities = match entities_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot list kb_tokens missing metadata on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
let dto_result = crate::KbTokenDto::try_from(entity);
let dto = match dto_result {
Ok(dto) => dto,
Err(error) => return Err(error),
};
dtos.push(dto);
}
return Ok(dtos);
},
}
}
#[cfg(test)]
mod tests {
async fn make_database() -> std::sync::Arc<crate::KbDatabase> {
let tempdir_result = tempfile::tempdir();
let tempdir = match tempdir_result {
Ok(tempdir) => tempdir,
Err(error) => panic!("tempdir must succeed: {}", error),
};
let database_path = tempdir.path().join("token_query.sqlite3");
let config = crate::KbDatabaseConfig {
enabled: true,
backend: crate::KbDatabaseBackend::Sqlite,
sqlite: crate::KbSqliteDatabaseConfig {
path: database_path.to_string_lossy().to_string(),
create_if_missing: true,
busy_timeout_ms: 5000,
max_connections: 1,
auto_initialize_schema: true,
use_wal: true,
},
};
let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
let database = match database_result {
Ok(database) => database,
Err(error) => panic!("database init must succeed: {}", error),
};
return std::sync::Arc::new(database);
}
#[tokio::test]
async fn list_tokens_missing_metadata_only_returns_incomplete_rows() {
let database = make_database().await;
let incomplete = crate::KbTokenDto::new(
"IncompleteMint111".to_string(),
None,
None,
None,
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let complete = crate::KbTokenDto::new(
"CompleteMint111".to_string(),
Some("CMP".to_string()),
Some("Complete".to_string()),
Some(6),
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let incomplete_result = crate::upsert_token(database.as_ref(), &incomplete).await;
if let Err(error) = incomplete_result {
panic!("incomplete token upsert must succeed: {}", error);
}
let complete_result = crate::upsert_token(database.as_ref(), &complete).await;
if let Err(error) = complete_result {
panic!("complete token upsert must succeed: {}", error);
}
let missing_result = crate::list_tokens_missing_metadata(database.as_ref(), None).await;
let missing = match missing_result {
Ok(missing) => missing,
Err(error) => panic!("missing metadata list must succeed: {}", error),
};
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].mint, "IncompleteMint111");
}
}

View File

@@ -17,9 +17,9 @@ pub async fn upsert_token_burn_event(
"cannot convert token burn event slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_token_burn_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_token_burn_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -126,7 +128,7 @@ LIMIT ?
"cannot list kb_token_burn_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -137,8 +139,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -165,13 +167,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -299,9 +295,7 @@ mod tests {
assert!(liquidity_id > 0);
assert!(mint_id > 0);
assert!(burn_id > 0);
let swaps = crate::list_recent_swaps(&database, 10)
.await
.expect("swaps list must succeed");
let swaps = crate::list_recent_swaps(&database, 10).await.expect("swaps list must succeed");
let liquidity_events = crate::list_recent_liquidity_events(&database, 10)
.await
.expect("liquidity list must succeed");

View File

@@ -17,9 +17,9 @@ pub async fn upsert_token_mint_event(
"cannot convert token mint event slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_token_mint_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_token_mint_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -126,7 +128,7 @@ LIMIT ?
"cannot list kb_token_mint_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -137,7 +139,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -90,13 +90,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_trade_events id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_trade_events id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
)));
},
}
}
},
}
}
@@ -144,13 +146,13 @@ LIMIT 1
"cannot read kb_trade_events by decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbTradeEventDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbTradeEventDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -198,7 +200,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_trade_events by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -209,8 +211,8 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -258,7 +260,7 @@ ORDER BY id ASC
"cannot list kb_trade_events by transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -269,15 +271,15 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
fn kb_trade_side_to_string(value: crate::KbSwapTradeSide) -> &'static str {
match value {
crate::KbSwapTradeSide::BuyBase => "BuyBase",
crate::KbSwapTradeSide::SellBase => "SellBase",
crate::KbSwapTradeSide::Unknown => "Unknown",
crate::KbSwapTradeSide::BuyBase => return "BuyBase",
crate::KbSwapTradeSide::SellBase => return "SellBase",
crate::KbSwapTradeSide::Unknown => return "Unknown",
}
}

View File

@@ -47,13 +47,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_wallets id for address '{}' on sqlite: {}",
dto.address, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallets id for address '{}' on sqlite: {}",
dto.address, error
)));
},
}
}
},
}
}
@@ -87,13 +89,13 @@ LIMIT 1
"cannot read kb_wallets '{}' on sqlite: {}",
address, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbWalletDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -124,7 +126,7 @@ ORDER BY address ASC
"cannot list kb_wallets on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -135,7 +137,7 @@ ORDER BY address ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -76,13 +76,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_holdings id for wallet_id '{}' token_id '{}' on sqlite: {}",
dto.wallet_id, dto.token_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_holdings id for wallet_id '{}' token_id '{}' on sqlite: {}",
dto.wallet_id, dto.token_id, error
)));
},
}
}
},
}
}
@@ -128,13 +130,13 @@ LIMIT 1
"cannot read kb_wallet_holdings by wallet_id '{}' token_id '{}' on sqlite: {}",
wallet_id, token_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletHoldingDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbWalletHoldingDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -178,7 +180,7 @@ ORDER BY token_id ASC, id ASC
"cannot list kb_wallet_holdings by wallet_id '{}' on sqlite: {}",
wallet_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -189,7 +191,7 @@ ORDER BY token_id ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_participations id for unique_key '{}' on sqlite: {}",
dto.unique_key, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_participations id for unique_key '{}' on sqlite: {}",
dto.unique_key, error
)));
},
}
}
},
}
}
@@ -107,13 +109,13 @@ LIMIT 1
"cannot read kb_wallet_participations by unique_key '{}' on sqlite: {}",
unique_key, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletParticipationDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbWalletParticipationDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -154,7 +156,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_wallet_participations by wallet_id '{}' on sqlite: {}",
wallet_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -165,8 +167,8 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -207,7 +209,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_wallet_participations by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -218,7 +220,7 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,10 @@ pub(crate) fn sqlite_database_url_from_config(
) -> Result<std::string::String, crate::KbError> {
let path = config.sqlite.path.trim();
if path.is_empty() {
return Err(crate::KbError::Config(
"database.sqlite.path must not be empty".to_string(),
));
return Err(crate::KbError::Config("database.sqlite.path must not be empty".to_string()));
}
let database_path = config.sqlite.path_buf();
Ok(format!("sqlite://{}", database_path.display()))
return Ok(format!("sqlite://{}", database_path.display()));
}
/// Opens a SQLite pool according to configuration.
@@ -22,9 +20,7 @@ pub(crate) async fn connect_sqlite(
) -> Result<sqlx::SqlitePool, crate::KbError> {
let path = config.sqlite.path.trim();
if path.is_empty() {
return Err(crate::KbError::Config(
"database.sqlite.path must not be empty".to_string(),
));
return Err(crate::KbError::Config("database.sqlite.path must not be empty".to_string()));
}
if config.sqlite.max_connections == 0 {
return Err(crate::KbError::Config(
@@ -49,9 +45,7 @@ pub(crate) async fn connect_sqlite(
.filename(&database_path)
.create_if_missing(config.sqlite.create_if_missing)
.foreign_keys(true)
.busy_timeout(std::time::Duration::from_millis(
config.sqlite.busy_timeout_ms,
));
.busy_timeout(std::time::Duration::from_millis(config.sqlite.busy_timeout_ms));
if config.sqlite.use_wal {
connect_options = connect_options.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);
}
@@ -59,10 +53,13 @@ pub(crate) async fn connect_sqlite(
sqlx::sqlite::SqlitePoolOptions::new().max_connections(config.sqlite.max_connections);
let connect_result = pool_options.connect_with(connect_options).await;
match connect_result {
Ok(pool) => Ok(pool),
Err(error) => Err(crate::KbError::Db(format!(
"cannot open sqlite database '{}': {}",
database_path.display(), error
))),
Ok(pool) => return Ok(pool),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot open sqlite database '{}': {}",
database_path.display(),
error
)));
},
}
}

View File

@@ -21,28 +21,28 @@ impl KbAnalysisSignalSeverity {
/// Converts the severity to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Info => 0,
Self::Low => 1,
Self::Medium => 2,
Self::High => 3,
Self::Critical => 4,
Self::Info => return 0,
Self::Low => return 1,
Self::Medium => return 2,
Self::High => return 3,
Self::Critical => return 4,
}
}
/// Restores a severity from its stable integer representation.
pub fn from_i16(
value: i16,
) -> Result<Self, crate::KbError> {
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Info),
1 => Ok(Self::Low),
2 => Ok(Self::Medium),
3 => Ok(Self::High),
4 => Ok(Self::Critical),
_ => Err(crate::KbError::Db(format!(
"invalid KbAnalysisSignalSeverity value: {}",
value
))),
0 => return Ok(Self::Info),
1 => return Ok(Self::Low),
2 => return Ok(Self::Medium),
3 => return Ok(Self::High),
4 => return Ok(Self::Critical),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbAnalysisSignalSeverity value: {}",
value
)));
},
}
}
}

View File

@@ -15,20 +15,22 @@ impl KbLiquidityEventKind {
/// Converts the event kind to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Add => 0,
Self::Remove => 1,
Self::Add => return 0,
Self::Remove => return 1,
}
}
/// Restores an event kind from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Add),
1 => Ok(Self::Remove),
_ => Err(crate::KbError::Db(format!(
"invalid KbLiquidityEventKind value: {}",
value
))),
0 => return Ok(Self::Add),
1 => return Ok(Self::Remove),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbLiquidityEventKind value: {}",
value
)));
},
}
}
}

View File

@@ -21,26 +21,28 @@ impl KbObservationSourceKind {
/// Converts the source kind to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::HttpRpc => 0,
Self::WsRpc => 1,
Self::Grpc => 2,
Self::Dex => 3,
Self::Other => 4,
Self::HttpRpc => return 0,
Self::WsRpc => return 1,
Self::Grpc => return 2,
Self::Dex => return 3,
Self::Other => return 4,
}
}
/// Restores a source kind from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::HttpRpc),
1 => Ok(Self::WsRpc),
2 => Ok(Self::Grpc),
3 => Ok(Self::Dex),
4 => Ok(Self::Other),
_ => Err(crate::KbError::Db(format!(
"invalid KbObservationSourceKind value: {}",
value
))),
0 => return Ok(Self::HttpRpc),
1 => return Ok(Self::WsRpc),
2 => return Ok(Self::Grpc),
3 => return Ok(Self::Dex),
4 => return Ok(Self::Other),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbObservationSourceKind value: {}",
value
)));
},
}
}
}

View File

@@ -19,24 +19,26 @@ impl KbObservedTokenStatus {
/// Converts the status to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::New => 0,
Self::Active => 1,
Self::Ignored => 2,
Self::Blocked => 3,
Self::New => return 0,
Self::Active => return 1,
Self::Ignored => return 2,
Self::Blocked => return 3,
}
}
/// Restores a status from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::New),
1 => Ok(Self::Active),
2 => Ok(Self::Ignored),
3 => Ok(Self::Blocked),
_ => Err(crate::KbError::Db(format!(
"invalid KbObservedTokenStatus value: {}",
value
))),
0 => return Ok(Self::New),
1 => return Ok(Self::Active),
2 => return Ok(Self::Ignored),
3 => return Ok(Self::Blocked),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbObservedTokenStatus value: {}",
value
)));
},
}
}
}

View File

@@ -1,4 +1,3 @@
// file: kb_lib/src/db/types/pool_kind.rs
//! Pool kind.
@@ -22,28 +21,23 @@ impl KbPoolKind {
/// Converts the kind to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Unknown => 0,
Self::Amm => 1,
Self::Clmm => 2,
Self::BondingCurve => 3,
Self::OrderBook => 4,
Self::Unknown => return 0,
Self::Amm => return 1,
Self::Clmm => return 2,
Self::BondingCurve => return 3,
Self::OrderBook => return 4,
}
}
/// Restores a kind from its stable integer representation.
pub fn from_i16(
value: i16,
) -> Result<Self, crate::KbError> {
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Unknown),
1 => Ok(Self::Amm),
2 => Ok(Self::Clmm),
3 => Ok(Self::BondingCurve),
4 => Ok(Self::OrderBook),
_ => Err(crate::KbError::Db(format!(
"invalid KbPoolKind value: {}",
value
))),
0 => return Ok(Self::Unknown),
1 => return Ok(Self::Amm),
2 => return Ok(Self::Clmm),
3 => return Ok(Self::BondingCurve),
4 => return Ok(Self::OrderBook),
_ => return Err(crate::KbError::Db(format!("invalid KbPoolKind value: {}", value))),
}
}
}

View File

@@ -21,26 +21,23 @@ impl KbPoolStatus {
/// Converts the status to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Unknown => 0,
Self::Pending => 1,
Self::Active => 2,
Self::Inactive => 3,
Self::Closed => 4,
Self::Unknown => return 0,
Self::Pending => return 1,
Self::Active => return 2,
Self::Inactive => return 3,
Self::Closed => return 4,
}
}
/// Restores a status from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Unknown),
1 => Ok(Self::Pending),
2 => Ok(Self::Active),
3 => Ok(Self::Inactive),
4 => Ok(Self::Closed),
_ => Err(crate::KbError::Db(format!(
"invalid KbPoolStatus value: {}",
value
))),
0 => return Ok(Self::Unknown),
1 => return Ok(Self::Pending),
2 => return Ok(Self::Active),
3 => return Ok(Self::Inactive),
4 => return Ok(Self::Closed),
_ => return Err(crate::KbError::Db(format!("invalid KbPoolStatus value: {}", value))),
}
}
}

View File

@@ -21,26 +21,25 @@ impl KbPoolTokenRole {
/// Converts the role to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Base => 0,
Self::Quote => 1,
Self::LpMint => 2,
Self::Reserve => 3,
Self::Other => 4,
Self::Base => return 0,
Self::Quote => return 1,
Self::LpMint => return 2,
Self::Reserve => return 3,
Self::Other => return 4,
}
}
/// Restores a role from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Base),
1 => Ok(Self::Quote),
2 => Ok(Self::LpMint),
3 => Ok(Self::Reserve),
4 => Ok(Self::Other),
_ => Err(crate::KbError::Db(format!(
"invalid KbPoolTokenRole value: {}",
value
))),
0 => return Ok(Self::Base),
1 => return Ok(Self::Quote),
2 => return Ok(Self::LpMint),
3 => return Ok(Self::Reserve),
4 => return Ok(Self::Other),
_ => {
return Err(crate::KbError::Db(format!("invalid KbPoolTokenRole value: {}", value)));
},
}
}
}

View File

@@ -21,26 +21,28 @@ impl KbDbRuntimeEventLevel {
/// Converts the level to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Trace => 0,
Self::Debug => 1,
Self::Info => 2,
Self::Warn => 3,
Self::Error => 4,
Self::Trace => return 0,
Self::Debug => return 1,
Self::Info => return 2,
Self::Warn => return 3,
Self::Error => return 4,
}
}
/// Restores a level from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Trace),
1 => Ok(Self::Debug),
2 => Ok(Self::Info),
3 => Ok(Self::Warn),
4 => Ok(Self::Error),
_ => Err(crate::KbError::Db(format!(
"invalid KbDbRuntimeEventLevel value: {}",
value
))),
0 => return Ok(Self::Trace),
1 => return Ok(Self::Debug),
2 => return Ok(Self::Info),
3 => return Ok(Self::Warn),
4 => return Ok(Self::Error),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbDbRuntimeEventLevel value: {}",
value
)));
},
}
}
}

View File

@@ -17,22 +17,24 @@ impl KbSwapTradeSide {
/// Converts the trade side to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Unknown => 0,
Self::BuyBase => 1,
Self::SellBase => 2,
Self::Unknown => return 0,
Self::BuyBase => return 1,
Self::SellBase => return 2,
}
}
/// Restores a trade side from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Unknown),
1 => Ok(Self::BuyBase),
2 => Ok(Self::SellBase),
_ => Err(crate::KbError::Db(format!(
"invalid KbSwapTradeSide value: {}",
value
))),
0 => return Ok(Self::Unknown),
1 => return Ok(Self::BuyBase),
2 => return Ok(Self::SellBase),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbSwapTradeSide value: {}",
value
)));
},
}
}
}

View File

@@ -12,11 +12,11 @@ pub struct KbDetectionPersistenceService {
impl KbDetectionPersistenceService {
/// Creates a new detection persistence service.
pub fn new(database: std::sync::Arc<crate::KbDatabase>) -> Self {
Self { database }
return Self { database };
}
/// Returns the shared database handle.
pub fn database(&self) -> &std::sync::Arc<crate::KbDatabase> {
&self.database
return &self.database;
}
/// Persists one on-chain observation.
@@ -32,7 +32,7 @@ impl KbDetectionPersistenceService {
input.slot,
input.payload.clone(),
);
crate::insert_onchain_observation(self.database.as_ref(), &dto).await
return crate::insert_onchain_observation(self.database.as_ref(), &dto).await;
}
/// Persists one analysis signal.
@@ -48,7 +48,7 @@ impl KbDetectionPersistenceService {
input.score,
input.payload.clone(),
);
crate::insert_analysis_signal(self.database.as_ref(), &dto).await
return crate::insert_analysis_signal(self.database.as_ref(), &dto).await;
}
/// Registers one token candidate from a technical source.
@@ -104,11 +104,7 @@ impl KbDetectionPersistenceService {
Ok(signal_id) => signal_id,
Err(error) => return Err(error),
};
Ok(crate::KbDetectionTokenCandidateResult {
token_id,
observation_id,
signal_id,
})
return Ok(crate::KbDetectionTokenCandidateResult { token_id, observation_id, signal_id });
}
/// Registers one pool candidate from a technical source.
@@ -154,7 +150,7 @@ impl KbDetectionPersistenceService {
"cannot register pool candidate: no known dex matches program id '{}'",
input.dex_program_id
)));
}
},
};
let dex_id = match matched_dex.id {
Some(dex_id) => dex_id,
@@ -162,7 +158,7 @@ impl KbDetectionPersistenceService {
return Err(crate::KbError::Db(
"cannot register pool candidate: matched dex has no id".to_string(),
));
}
},
};
let pool_dto = crate::KbPoolDto::new(
dex_id,
@@ -221,13 +217,13 @@ impl KbDetectionPersistenceService {
Ok(signal_id) => signal_id,
Err(error) => return Err(error),
};
Ok(crate::KbDetectionPoolCandidateResult {
return Ok(crate::KbDetectionPoolCandidateResult {
dex_id,
pool_id,
pool_listing_id,
observation_id,
signal_id,
})
});
}
}
@@ -248,9 +244,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]
@@ -293,14 +289,8 @@ mod tests {
.expect("list signals must succeed");
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"So11111111111111111111111111111111111111112"
);
assert_eq!(
signals[0].object_key,
"So11111111111111111111111111111111111111112"
);
assert_eq!(observations[0].object_key, "So11111111111111111111111111111111111111112");
assert_eq!(signals[0].object_key, "So11111111111111111111111111111111111111112");
}
#[tokio::test]
@@ -340,10 +330,7 @@ mod tests {
.await
.expect("get token must succeed");
assert!(token.is_some());
assert_eq!(
token.expect("token must exist").symbol.as_deref(),
Some("TEST")
);
assert_eq!(token.expect("token must exist").symbol.as_deref(), Some("TEST"));
let observations = crate::list_recent_onchain_observations(service.database().as_ref(), 10)
.await
.expect("list observations must succeed");
@@ -352,18 +339,9 @@ mod tests {
.expect("list signals must succeed");
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].related_observation_id,
Some(result.observation_id)
);
assert_eq!(observations[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].related_observation_id, Some(result.observation_id));
}
#[tokio::test]

View File

@@ -37,12 +37,12 @@ pub struct KbSolanaWsDetectionService {
impl KbSolanaWsDetectionService {
/// Creates a new Solana WebSocket detection service.
pub fn new(persistence: crate::KbDetectionPersistenceService) -> Self {
Self { persistence }
return Self { persistence };
}
/// Returns the shared persistence façade.
pub fn persistence(&self) -> &crate::KbDetectionPersistenceService {
&self.persistence
return &self.persistence;
}
/// Processes one Solana WebSocket JSON-RPC notification.
@@ -57,24 +57,22 @@ impl KbSolanaWsDetectionService {
Some(observation_kind) => observation_kind,
None => return Ok(crate::KbSolanaWsDetectionOutcome::Ignored),
};
let token_candidate_result = self
.try_register_token_candidate(endpoint_name.clone(), notification)
.await;
let token_candidate_result =
self.try_register_token_candidate(endpoint_name.clone(), notification).await;
match token_candidate_result {
Ok(Some(result)) => {
return Ok(crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { result });
}
Ok(None) => {}
},
Ok(None) => {},
Err(error) => return Err(error),
}
let pool_candidate_result = self
.try_register_pool_candidate(endpoint_name.clone(), notification)
.await;
let pool_candidate_result =
self.try_register_pool_candidate(endpoint_name.clone(), notification).await;
match pool_candidate_result {
Ok(Some(result)) => {
return Ok(crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { result });
}
Ok(None) => {}
},
Ok(None) => {},
Err(error) => return Err(error),
}
let payload = build_notification_payload(notification);
@@ -93,10 +91,7 @@ impl KbSolanaWsDetectionService {
slot,
payload.clone(),
);
let observation_id_result = self
.persistence
.record_observation(&observation_input)
.await;
let observation_id_result = self.persistence.record_observation(&observation_input).await;
let observation_id = match observation_id_result {
Ok(observation_id) => observation_id,
Err(error) => return Err(error),
@@ -127,7 +122,7 @@ impl KbSolanaWsDetectionService {
return Err(error);
}
}
Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id })
return Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id });
}
/// Tries to register a token candidate from one notification.
@@ -210,8 +205,8 @@ impl KbSolanaWsDetectionService {
);
let result = self.persistence.register_token_candidate(&input).await;
match result {
Ok(result) => Ok(Some(result)),
Err(error) => Err(error),
Ok(result) => return Ok(Some(result)),
Err(error) => return Err(error),
}
}
@@ -288,8 +283,8 @@ impl KbSolanaWsDetectionService {
);
let result = self.persistence.register_pool_candidate(&input).await;
match result {
Ok(result) => Ok(Some(result)),
Err(error) => Err(error),
Ok(result) => return Ok(Some(result)),
Err(error) => return Err(error),
}
}
}
@@ -299,27 +294,27 @@ fn map_notification_method_to_observation_kind(
method: &str,
) -> std::option::Option<std::string::String> {
match method {
"accountNotification" => Some("ws.account_notification".to_string()),
"blockNotification" => Some("ws.block_notification".to_string()),
"logsNotification" => Some("ws.logs_notification".to_string()),
"programNotification" => Some("ws.program_notification".to_string()),
"rootNotification" => Some("ws.root_notification".to_string()),
"signatureNotification" => Some("ws.signature_notification".to_string()),
"slotNotification" => Some("ws.slot_notification".to_string()),
"slotsUpdatesNotification" => Some("ws.slots_updates_notification".to_string()),
"voteNotification" => Some("ws.vote_notification".to_string()),
_ => None,
"accountNotification" => return Some("ws.account_notification".to_string()),
"blockNotification" => return Some("ws.block_notification".to_string()),
"logsNotification" => return Some("ws.logs_notification".to_string()),
"programNotification" => return Some("ws.program_notification".to_string()),
"rootNotification" => return Some("ws.root_notification".to_string()),
"signatureNotification" => return Some("ws.signature_notification".to_string()),
"slotNotification" => return Some("ws.slot_notification".to_string()),
"slotsUpdatesNotification" => return Some("ws.slots_updates_notification".to_string()),
"voteNotification" => return Some("ws.vote_notification".to_string()),
_ => return None,
}
}
/// Wraps one raw notification into a normalized JSON payload.
fn build_notification_payload(notification: &crate::KbJsonRpcWsNotification) -> serde_json::Value {
serde_json::json!({
return serde_json::json!({
"jsonrpc": notification.jsonrpc,
"method": notification.method,
"subscription": notification.params.subscription,
"result": notification.params.result,
})
});
}
/// Builds one logical object key from the notification result.
@@ -340,8 +335,7 @@ fn build_object_key(
if let Some(slot) = slot_option {
return format!("slot:{slot}");
}
format!("subscription:{subscription}")
return format!("subscription:{subscription}");
}
/// Extracts a slot number from one notification result.
@@ -364,7 +358,7 @@ fn extract_slot_from_result(method: &str, result: &serde_json::Value) -> std::op
return Some(slot);
}
}
None
return None;
}
/// Extracts a pubkey from one notification result.
@@ -379,7 +373,7 @@ fn extract_pubkey_from_result(
return Some(pubkey.to_string());
}
}
None
return None;
}
/// Extracts a signature from one notification result.
@@ -394,13 +388,13 @@ fn extract_signature_from_result(
return Some(signature.to_string());
}
}
None
return None;
}
/// Extracts one account-like JSON object from one notification result.
fn extract_account_value_from_result<'a>(
result: &'a serde_json::Value,
) -> std::option::Option<&'a serde_json::Value> {
fn extract_account_value_from_result(
result: &serde_json::Value,
) -> std::option::Option<&serde_json::Value> {
if let Some(account) = result.get("account") {
return Some(account);
}
@@ -412,7 +406,7 @@ fn extract_account_value_from_result<'a>(
return Some(value);
}
}
None
return None;
}
/// Extracts the parsed account type from one account-like JSON object.
@@ -431,8 +425,8 @@ fn extract_parsed_account_type(
};
let type_option = parsed.get("type").and_then(serde_json::Value::as_str);
match type_option {
Some(parsed_type) => Some(parsed_type.to_string()),
None => None,
Some(parsed_type) => return Some(parsed_type.to_string()),
None => return None,
}
}
@@ -440,12 +434,10 @@ fn extract_parsed_account_type(
fn extract_account_owner(
account_value: &serde_json::Value,
) -> std::option::Option<std::string::String> {
let owner_option = account_value
.get("owner")
.and_then(serde_json::Value::as_str);
let owner_option = account_value.get("owner").and_then(serde_json::Value::as_str);
match owner_option {
Some(owner) => Some(owner.to_string()),
None => None,
Some(owner) => return Some(owner.to_string()),
None => return None,
}
}
@@ -475,8 +467,8 @@ fn extract_decimals_from_account_value(
};
let converted = u8::try_from(decimals);
match converted {
Ok(decimals) => Some(decimals),
Err(_) => None,
Ok(decimals) => return Some(decimals),
Err(_) => return None,
}
}
@@ -489,7 +481,7 @@ fn extract_program_notification_owner(
Some(account_value) => account_value,
None => return None,
};
extract_account_owner(account_value)
return extract_account_owner(account_value);
}
/// Extracts the parsed token amount decimals from one parsed token account notification.
@@ -516,17 +508,15 @@ fn extract_token_account_decimals_from_account_value(
Some(token_amount) => token_amount,
None => return None,
};
let decimals_option = token_amount
.get("decimals")
.and_then(serde_json::Value::as_u64);
let decimals_option = token_amount.get("decimals").and_then(serde_json::Value::as_u64);
let decimals = match decimals_option {
Some(decimals) => decimals,
None => return None,
};
let convert_result = u8::try_from(decimals);
match convert_result {
Ok(decimals) => Some(decimals),
Err(_) => None,
Ok(decimals) => return Some(decimals),
Err(_) => return None,
}
}
@@ -551,8 +541,8 @@ fn extract_parsed_account_mint(
};
let mint_option = info.get("mint").and_then(serde_json::Value::as_str);
match mint_option {
Some(mint) => Some(mint.to_string()),
None => None,
Some(mint) => return Some(mint.to_string()),
None => return None,
}
}
@@ -580,7 +570,7 @@ fn extract_logs_lines(result: &serde_json::Value) -> std::vec::Vec<std::string::
lines.push(line.to_string());
}
}
lines
return lines;
}
/// Extracts the error field from a signature notification result.
@@ -593,8 +583,8 @@ fn extract_signature_notification_err(
None => return None,
};
match value.get("err") {
Some(err) => Some(err.clone()),
None => None,
Some(err) => return Some(err.clone()),
None => return None,
}
}
@@ -621,8 +611,8 @@ fn build_signal_kind_for_notification(
}
}
}
"signal.account_notification.generic".to_string()
}
return "signal.account_notification.generic".to_string();
},
"logsNotification" => {
let lines = extract_logs_lines(result);
for line in &lines {
@@ -639,21 +629,21 @@ fn build_signal_kind_for_notification(
return "signal.logs_notification.initialize_account".to_string();
}
}
"signal.logs_notification.generic".to_string()
}
return "signal.logs_notification.generic".to_string();
},
"signatureNotification" => {
let err_option = extract_signature_notification_err(result);
match err_option {
Some(err) => {
if err.is_null() {
"signal.signature_notification.confirmed".to_string()
return "signal.signature_notification.confirmed".to_string();
} else {
"signal.signature_notification.failed".to_string()
return "signal.signature_notification.failed".to_string();
}
}
None => "signal.signature_notification.generic".to_string(),
},
None => return "signal.signature_notification.generic".to_string(),
}
}
},
"programNotification" => {
let owner_option = extract_program_notification_owner(result);
let owner = match owner_option {
@@ -666,12 +656,9 @@ fn build_signal_kind_for_notification(
if owner == crate::SPL_TOKEN_2022_PROGRAM_ID.to_string() {
return "signal.program_notification.spl_token_2022".to_string();
}
"signal.program_notification.generic".to_string()
}
_ => format!(
"signal.{}",
method.replace("Notification", "").to_lowercase()
),
return "signal.program_notification.generic".to_string();
},
_ => return format!("signal.{}", method.replace("Notification", "").to_lowercase()),
}
}
@@ -681,8 +668,8 @@ fn build_signal_severity_for_notification(
result: &serde_json::Value,
) -> crate::KbAnalysisSignalSeverity {
match method {
"programNotification" => crate::KbAnalysisSignalSeverity::Medium,
"accountNotification" => crate::KbAnalysisSignalSeverity::Low,
"programNotification" => return crate::KbAnalysisSignalSeverity::Medium,
"accountNotification" => return crate::KbAnalysisSignalSeverity::Low,
"logsNotification" => {
let lines = extract_logs_lines(result);
for line in &lines {
@@ -690,22 +677,22 @@ fn build_signal_severity_for_notification(
return crate::KbAnalysisSignalSeverity::Medium;
}
}
crate::KbAnalysisSignalSeverity::Low
}
return crate::KbAnalysisSignalSeverity::Low;
},
"signatureNotification" => {
let err_option = extract_signature_notification_err(result);
match err_option {
Some(err) => {
if err.is_null() {
crate::KbAnalysisSignalSeverity::Low
return crate::KbAnalysisSignalSeverity::Low;
} else {
crate::KbAnalysisSignalSeverity::Medium
return crate::KbAnalysisSignalSeverity::Medium;
}
}
None => crate::KbAnalysisSignalSeverity::Low,
},
None => return crate::KbAnalysisSignalSeverity::Low,
}
}
_ => crate::KbAnalysisSignalSeverity::Low,
},
_ => return crate::KbAnalysisSignalSeverity::Low,
}
}
@@ -726,13 +713,13 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "slotNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -743,11 +730,11 @@ mod tests {
}),
subscription: 1008_u64,
},
}
};
}
fn build_program_mint_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "programNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -773,11 +760,11 @@ mod tests {
}),
subscription: 2048_u64,
},
}
};
}
fn build_program_pool_candidate_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "programNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -798,11 +785,11 @@ mod tests {
}),
subscription: 5555_u64,
},
}
};
}
fn build_logs_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "logsNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -820,11 +807,11 @@ mod tests {
}),
subscription: 3001_u64,
},
}
};
}
fn build_signature_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "signatureNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -838,7 +825,7 @@ mod tests {
}),
subscription: 4001_u64,
},
}
};
}
#[tokio::test]
@@ -859,7 +846,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -894,7 +881,7 @@ mod tests {
assert!(result.token_id > 0);
assert!(result.observation_id > 0);
assert!(result.signal_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let token_result = crate::get_token_by_mint(
@@ -923,14 +910,8 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(observations[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].object_key, "Mint111111111111111111111111111111111111111");
}
#[tokio::test]
@@ -951,7 +932,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -970,10 +951,7 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
signals[0].signal_kind,
"signal.logs_notification.initialize_mint"
);
assert_eq!(signals[0].signal_kind, "signal.logs_notification.initialize_mint");
}
#[tokio::test]
@@ -994,7 +972,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -1013,10 +991,7 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
signals[0].signal_kind,
"signal.signature_notification.confirmed"
);
assert_eq!(signals[0].signal_kind, "signal.signature_notification.confirmed");
}
#[tokio::test]
@@ -1053,7 +1028,7 @@ mod tests {
assert!(result.pool_id > 0);
assert!(result.pool_listing_id > 0);
result.pool_id
}
},
_ => panic!("unexpected detection outcome"),
};
let pool_result = crate::get_pool_by_address(

View File

@@ -29,14 +29,14 @@ impl KbDetectionObservationInput {
slot: std::option::Option<u64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
observation_kind,
source_kind,
endpoint_name,
object_key,
slot,
payload,
}
};
}
}
@@ -67,14 +67,14 @@ impl KbDetectionSignalInput {
score: std::option::Option<f64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
signal_kind,
severity,
object_key,
related_observation_id,
score,
payload,
}
};
}
}
@@ -133,7 +133,7 @@ impl KbDetectionTokenCandidateInput {
signal_score: std::option::Option<f64>,
signal_payload: std::option::Option<serde_json::Value>,
) -> Self {
Self {
return Self {
mint,
symbol,
name,
@@ -149,7 +149,7 @@ impl KbDetectionTokenCandidateInput {
signal_severity,
signal_score,
signal_payload,
}
};
}
}
@@ -207,7 +207,7 @@ impl KbDetectionPoolCandidateInput {
signal_score: std::option::Option<f64>,
signal_payload: std::option::Option<serde_json::Value>,
) -> Self {
Self {
return Self {
pool_address,
dex_program_id,
source_kind,
@@ -219,7 +219,7 @@ impl KbDetectionPoolCandidateInput {
signal_severity,
signal_score,
signal_payload,
}
};
}
}

View File

@@ -20,10 +20,7 @@ impl KbWsDetectionNotificationEnvelope {
endpoint_name: std::option::Option<std::string::String>,
notification: crate::KbJsonRpcWsNotification,
) -> Self {
Self {
endpoint_name,
notification,
}
return Self { endpoint_name, notification };
}
}
@@ -54,7 +51,7 @@ pub struct KbWsDetectionRelay {
impl KbWsDetectionRelay {
/// Creates a new relay.
pub fn new(detector: crate::KbSolanaWsDetectionService) -> Self {
Self { detector }
return Self { detector };
}
/// Creates a bounded relay channel.
@@ -64,7 +61,7 @@ impl KbWsDetectionRelay {
tokio::sync::mpsc::Sender<crate::KbWsDetectionNotificationEnvelope>,
tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
) {
tokio::sync::mpsc::channel(capacity)
return tokio::sync::mpsc::channel(capacity);
}
/// Processes one forwarded notification.
@@ -72,9 +69,10 @@ impl KbWsDetectionRelay {
&self,
envelope: &crate::KbWsDetectionNotificationEnvelope,
) -> Result<crate::KbSolanaWsDetectionOutcome, crate::KbError> {
self.detector
return self
.detector
.process_notification(envelope.endpoint_name.clone(), &envelope.notification)
.await
.await;
}
/// Spawns one background relay worker.
@@ -82,7 +80,7 @@ impl KbWsDetectionRelay {
self,
mut receiver: tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
) -> tokio::task::JoinHandle<crate::KbWsDetectionRelayStats> {
tokio::spawn(async move {
return tokio::spawn(async move {
let mut stats = crate::KbWsDetectionRelayStats::default();
loop {
let recv_result = receiver.recv().await;
@@ -103,25 +101,25 @@ impl KbWsDetectionRelay {
error
);
continue;
}
},
};
match outcome {
crate::KbSolanaWsDetectionOutcome::Ignored => {
stats.ignored_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { .. } => {
stats.observation_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { .. } => {
stats.token_candidate_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { .. } => {
stats.pool_candidate_count += 1;
}
},
}
}
stats
})
return stats;
});
}
}
@@ -142,13 +140,13 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "slotNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -159,7 +157,7 @@ mod tests {
}),
subscription: 1008_u64,
},
}
};
}
#[tokio::test]
@@ -180,7 +178,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected relay outcome"),
}
}

View File

@@ -76,7 +76,7 @@ enum KbDexlabInstructionKind {
impl KbDexlabDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more DexLab events.
@@ -93,7 +93,7 @@ impl KbDexlabDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -104,7 +104,7 @@ impl KbDexlabDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -140,45 +140,24 @@ impl KbDexlabDecoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"pool",
"poolAddress",
"poolAccount",
"amm",
"ammPool",
"poolState",
],
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenA",
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
],
&["tokenA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenB",
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
],
&["tokenB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["payer", "creator", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let fee_tier = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["feeTier", "fee_tier", "tradeFeeTier", "feeRate"],
@@ -246,7 +225,7 @@ impl KbDexlabDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -278,7 +257,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbDexlabInstructionKind::Swap;
}
KbDexlabInstructionKind::Unknown
return KbDexlabInstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -305,7 +284,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -316,7 +295,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -326,7 +305,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -340,7 +319,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -349,7 +328,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -361,11 +340,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
},
}
}
@@ -377,7 +358,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -412,7 +393,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_extract_account(
@@ -422,7 +403,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -432,7 +413,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -462,7 +443,7 @@ mod tests {
.to_string(),
);
dto.id = Some(801);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -498,7 +479,7 @@ mod tests {
),
);
dto.id = Some(802);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -526,7 +507,7 @@ mod tests {
.to_string(),
);
dto.id = Some(803);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -559,7 +540,7 @@ mod tests {
),
);
dto.id = Some(804);
dto
return dto;
}
#[test]
@@ -584,10 +565,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.fee_tier, Some("0.3%".to_string()));
}
},
crate::KbDexlabDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -612,10 +593,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbDexlabDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -76,7 +76,7 @@ enum KbFluxbeamInstructionKind {
impl KbFluxbeamDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more FluxBeam events.
@@ -93,7 +93,7 @@ impl KbFluxbeamDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -104,7 +104,7 @@ impl KbFluxbeamDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -140,50 +140,29 @@ impl KbFluxbeamDecoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"pool",
"poolAddress",
"poolAccount",
"amm",
"ammPool",
"poolState",
],
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let lp_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["lpMint", "lp_mint", "lpTokenMint"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenA",
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
],
&["tokenA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenB",
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
],
&["tokenB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["payer", "creator", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbFluxbeamInstructionKind::CreatePool {
let payload_json = serde_json::json!({
"decoder": "fluxbeam",
@@ -247,7 +226,7 @@ impl KbFluxbeamDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -278,7 +257,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbFluxbeamInstructionKind::Swap;
}
KbFluxbeamInstructionKind::Unknown
return KbFluxbeamInstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -305,7 +284,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -316,7 +295,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -326,7 +305,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -340,7 +319,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -349,7 +328,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -361,11 +340,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
},
}
}
@@ -377,7 +358,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -412,7 +393,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_extract_account(
@@ -422,7 +403,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -432,7 +413,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -462,7 +443,7 @@ mod tests {
.to_string(),
);
dto.id = Some(701);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -499,7 +480,7 @@ mod tests {
),
);
dto.id = Some(702);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -527,7 +508,7 @@ mod tests {
.to_string(),
);
dto.id = Some(703);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -561,7 +542,7 @@ mod tests {
),
);
dto.id = Some(704);
dto
return dto;
}
#[test]
@@ -586,10 +567,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbFluxbeamDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -614,10 +595,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbFluxbeamDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -79,7 +79,7 @@ enum KbMeteoraDammV1InstructionKind {
impl KbMeteoraDammV1Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Meteora DAMM v1 events.
@@ -96,7 +96,7 @@ impl KbMeteoraDammV1Decoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -107,7 +107,7 @@ impl KbMeteoraDammV1Decoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -143,50 +143,29 @@ impl KbMeteoraDammV1Decoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"pool",
"poolAddress",
"poolAccount",
"amm",
"ammPool",
"poolState",
],
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
"coinMint",
],
&["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0", "coinMint"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
"pcMint",
],
&["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1", "pcMint"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["config", "poolConfig", "ammConfig", "tradeFeeConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "payer", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDammV1InstructionKind::CreatePool
|| instruction_kind == KbMeteoraDammV1InstructionKind::CreatePoolWithConfig
{
@@ -257,7 +236,7 @@ impl KbMeteoraDammV1Decoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -297,7 +276,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbMeteoraDammV1InstructionKind::Swap;
}
KbMeteoraDammV1InstructionKind::Unknown
return KbMeteoraDammV1InstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -325,7 +304,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -336,7 +315,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -346,7 +325,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -360,7 +339,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -369,7 +348,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -381,11 +360,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
},
}
}
@@ -397,7 +378,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -432,7 +413,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -443,7 +424,7 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
@@ -467,7 +448,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -477,7 +458,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -487,7 +468,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -517,7 +498,7 @@ mod tests {
.to_string(),
);
dto.id = Some(501);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -554,7 +535,7 @@ mod tests {
),
);
dto.id = Some(502);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -582,7 +563,7 @@ mod tests {
.to_string(),
);
dto.id = Some(503);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -615,7 +596,7 @@ mod tests {
),
);
dto.id = Some(504);
dto
return dto;
}
#[test]
@@ -640,10 +621,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert!(event.used_config);
}
},
crate::KbMeteoraDammV1DecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -668,10 +649,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbMeteoraDammV1DecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -82,7 +82,7 @@ enum KbMeteoraDammV2InstructionKind {
impl KbMeteoraDammV2Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Meteora DAMM v2 events.
@@ -99,7 +99,7 @@ impl KbMeteoraDammV2Decoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -110,7 +110,7 @@ impl KbMeteoraDammV2Decoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -148,27 +148,27 @@ impl KbMeteoraDammV2Decoder {
parsed_json.as_ref(),
&["pool", "poolAddress", "poolAccount", "poolState", "cpAmm"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["staticConfig", "dynamicConfig", "config", "poolConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "payer", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolStatic
|| instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolDynamic
|| instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolCustomizable
@@ -178,7 +178,7 @@ impl KbMeteoraDammV2Decoder {
KbMeteoraDammV2InstructionKind::CreatePoolDynamic => "dynamic".to_string(),
KbMeteoraDammV2InstructionKind::CreatePoolCustomizable => {
"customizable".to_string()
}
},
_ => "unknown".to_string(),
};
let payload_json = serde_json::json!({
@@ -249,7 +249,7 @@ impl KbMeteoraDammV2Decoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -278,7 +278,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_classify_instruction_kind(
@@ -333,7 +333,7 @@ fn kb_classify_instruction_kind(
{
return KbMeteoraDammV2InstructionKind::Swap;
}
KbMeteoraDammV2InstructionKind::Unknown
return KbMeteoraDammV2InstructionKind::Unknown;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -344,7 +344,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -354,7 +354,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -368,7 +368,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -377,7 +377,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -389,11 +389,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
},
}
}
@@ -405,7 +407,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -440,7 +442,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -451,7 +453,7 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
@@ -475,7 +477,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -485,7 +487,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -495,7 +497,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -525,7 +527,7 @@ mod tests {
.to_string(),
);
dto.id = Some(401);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -562,7 +564,7 @@ mod tests {
),
);
dto.id = Some(402);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -590,7 +592,7 @@ mod tests {
.to_string(),
);
dto.id = Some(403);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -623,7 +625,7 @@ mod tests {
),
);
dto.id = Some(404);
dto
return dto;
}
#[test]
@@ -648,10 +650,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.create_kind, "customizable".to_string());
}
},
crate::KbMeteoraDammV2DecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -677,10 +679,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert!(event.used_swap2);
}
},
crate::KbMeteoraDammV2DecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -76,7 +76,7 @@ pub struct KbMeteoraDbcDecoder;
impl KbMeteoraDbcDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Meteora DBC events.
@@ -93,7 +93,7 @@ impl KbMeteoraDbcDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -104,7 +104,7 @@ impl KbMeteoraDbcDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -142,27 +142,27 @@ impl KbMeteoraDbcDecoder {
parsed_json.as_ref(),
&["pool", "poolAccount", "poolState", "virtualPool", "poolKey"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["baseMint", "tokenAMint", "mintA", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["quoteMint", "tokenBMint", "mintB", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["poolConfig", "config", "dbcConfig", "curveConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "poolCreator", "owner", "user"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDbcInstructionKind::CreatePool {
let payload_json = serde_json::json!({
"decoder": "meteora_dbc",
@@ -228,7 +228,7 @@ impl KbMeteoraDbcDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -257,7 +257,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_any_keyword(
@@ -269,7 +269,7 @@ fn kb_log_messages_contain_any_keyword(
return true;
}
}
false
return false;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -280,7 +280,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_log_text(value: &str) -> std::string::String {
@@ -290,7 +290,7 @@ fn kb_normalize_log_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -304,7 +304,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -313,7 +313,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -325,11 +325,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
},
}
}
@@ -341,7 +343,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -376,7 +378,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -387,7 +389,7 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
@@ -411,7 +413,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -421,7 +423,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -431,7 +433,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
fn kb_classify_instruction_kind(
@@ -456,32 +458,21 @@ fn kb_classify_instruction_kind(
}
let has_create_config = kb_value_contains_any_key(
parsed_json,
&[
"poolConfig",
"migrationQuoteThreshold",
"curveConfig",
"dbcConfig",
],
&["poolConfig", "migrationQuoteThreshold", "curveConfig", "dbcConfig"],
);
if has_create_config {
return KbMeteoraDbcInstructionKind::CreatePool;
}
if kb_log_messages_contain_any_keyword(
log_messages,
&[
"create_pool",
"createpool",
"initialize_pool",
"initializepool",
"launch_pool",
],
&["create_pool", "createpool", "initialize_pool", "initializepool", "launch_pool"],
) {
return KbMeteoraDbcInstructionKind::CreatePool;
}
if kb_log_messages_contain_any_keyword(log_messages, &["swap2", "swap"]) {
return KbMeteoraDbcInstructionKind::Swap;
}
KbMeteoraDbcInstructionKind::Unknown
return KbMeteoraDbcInstructionKind::Unknown;
}
#[cfg(test)]
@@ -511,7 +502,7 @@ mod tests {
.to_string(),
);
dto.id = Some(301);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -547,7 +538,7 @@ mod tests {
),
);
dto.id = Some(302);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -575,7 +566,7 @@ mod tests {
.to_string(),
);
dto.id = Some(303);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -607,7 +598,7 @@ mod tests {
),
);
dto.id = Some(304);
dto
return dto;
}
#[test]
@@ -632,10 +623,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.config_account, Some("DbcConfig111".to_string()));
}
},
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -660,10 +651,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
@@ -687,10 +678,10 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {}
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {},
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
@@ -706,10 +697,10 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {}
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {},
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
}

View File

@@ -3,8 +3,7 @@
//! Orca Whirlpools transaction decoder.
/// Orca Whirlpools program id.
pub const KB_ORCA_WHIRLPOOLS_PROGRAM_ID: &str =
"whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
pub const KB_ORCA_WHIRLPOOLS_PROGRAM_ID: &str = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
/// Decoded Orca Whirlpools create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -83,7 +82,7 @@ enum KbOrcaWhirlpoolsInstructionKind {
impl KbOrcaWhirlpoolsDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Orca Whirlpools events.
@@ -100,7 +99,7 @@ impl KbOrcaWhirlpoolsDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -111,7 +110,7 @@ impl KbOrcaWhirlpoolsDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -137,7 +136,8 @@ impl KbOrcaWhirlpoolsDecoder {
Ok(accounts) => accounts,
Err(error) => return Err(error),
};
let parsed_json_result = kb_parse_optional_parsed_json(instruction.parsed_json.as_ref());
let parsed_json_result =
kb_parse_optional_parsed_json(instruction.parsed_json.as_ref());
let parsed_json = match parsed_json_result {
Ok(parsed_json) => parsed_json,
Err(error) => return Err(error),
@@ -146,59 +146,33 @@ impl KbOrcaWhirlpoolsDecoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"whirlpool",
"pool",
"poolAddress",
"poolAccount",
"whirlpoolAddress",
],
&["whirlpool", "pool", "poolAddress", "poolAccount", "whirlpoolAddress"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenMintA",
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
],
&["tokenMintA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenMintB",
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
],
&["tokenMintB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"whirlpoolsConfig",
"config",
"configAccount",
"whirlpoolConfig",
],
&["whirlpoolsConfig", "config", "configAccount", "whirlpoolConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["funder", "creator", "payer", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePool
|| instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2
{
let used_v2 =
instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
let used_v2 = instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
let payload_json = serde_json::json!({
"decoder": "orca_whirlpools",
"eventKind": "create_pool",
@@ -268,7 +242,7 @@ impl KbOrcaWhirlpoolsDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -295,17 +269,13 @@ fn kb_classify_instruction_kind(
return KbOrcaWhirlpoolsInstructionKind::Swap;
}
}
if kb_value_contains_any_key(
parsed_json,
&["tokenProgramA", "tokenProgramB", "memoProgram"],
) && kb_log_messages_contain_keyword(log_messages, "initialize_pool")
if kb_value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& kb_log_messages_contain_keyword(log_messages, "initialize_pool")
{
return KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
}
if kb_value_contains_any_key(
parsed_json,
&["tokenProgramA", "tokenProgramB", "memoProgram"],
) && kb_log_messages_contain_keyword(log_messages, "swap")
if kb_value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& kb_log_messages_contain_keyword(log_messages, "swap")
{
return KbOrcaWhirlpoolsInstructionKind::SwapV2;
}
@@ -327,7 +297,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbOrcaWhirlpoolsInstructionKind::Swap;
}
KbOrcaWhirlpoolsInstructionKind::Unknown
return KbOrcaWhirlpoolsInstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -355,13 +325,10 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(
log_messages: &[std::string::String],
keyword: &str,
) -> bool {
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_text(log_message.as_str());
@@ -369,7 +336,7 @@ fn kb_log_messages_contain_keyword(
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -379,7 +346,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -393,7 +360,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -402,7 +369,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -414,11 +381,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
},
}
}
@@ -430,7 +399,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -466,7 +435,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -477,13 +446,10 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> bool {
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
if let Some(object) = value.as_object() {
for candidate_key in candidate_keys {
if object.contains_key(*candidate_key) {
@@ -504,7 +470,7 @@ fn kb_value_contains_any_key_inner(
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -514,19 +480,17 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(
log_messages: &[std::string::String],
) -> crate::KbSwapTradeSide {
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
if kb_log_messages_contain_keyword(log_messages, "buy") {
return crate::KbSwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -556,7 +520,7 @@ mod tests {
.to_string(),
);
dto.id = Some(601);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -595,7 +559,7 @@ mod tests {
),
);
dto.id = Some(602);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -623,7 +587,7 @@ mod tests {
.to_string(),
);
dto.id = Some(603);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -656,7 +620,7 @@ mod tests {
),
);
dto.id = Some(604);
dto
return dto;
}
#[test]
@@ -682,10 +646,10 @@ mod tests {
);
assert_eq!(event.config_account, Some("OrcaConfig111".to_string()));
assert!(event.used_v2);
}
},
crate::KbOrcaWhirlpoolsDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -711,10 +675,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert!(event.used_v2);
}
},
crate::KbOrcaWhirlpoolsDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -80,7 +80,7 @@ pub struct KbPumpFunDecoder;
impl KbPumpFunDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Pump.fun events.
@@ -97,7 +97,7 @@ impl KbPumpFunDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
if transaction.err_json.is_some() {
return Ok(std::vec::Vec::new());
@@ -111,7 +111,7 @@ impl KbPumpFunDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let has_create_v2_log = kb_log_messages_contain_keyword(&log_messages, "create_v2")
@@ -250,7 +250,7 @@ impl KbPumpFunDecoder {
},
));
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -271,8 +271,8 @@ fn kb_decode_optional_instruction_data(
};
let decode_result = bs58::decode(encoded.as_str()).into_vec();
match decode_result {
Ok(decoded) => Some(decoded),
Err(_) => None,
Ok(decoded) => return Some(decoded),
Err(_) => return None,
}
}
@@ -287,7 +287,7 @@ fn kb_instruction_data_starts_with(
if instruction_data.len() < discriminator.len() {
return false;
}
&instruction_data[0..discriminator.len()] == discriminator
return &instruction_data[0..discriminator.len()] == discriminator;
}
fn kb_extract_u64_argument(
@@ -304,7 +304,7 @@ fn kb_extract_u64_argument(
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&instruction_data[offset..end]);
Some(u64::from_le_bytes(bytes).to_string())
return Some(u64::from_le_bytes(bytes).to_string());
}
fn kb_extract_log_messages(
@@ -332,7 +332,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -343,7 +343,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_parse_accounts_json(
@@ -357,7 +357,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -366,7 +366,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_extract_account(
@@ -376,7 +376,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_normalize_log_text(value: &str) -> std::string::String {
@@ -386,7 +386,7 @@ fn kb_normalize_log_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
#[cfg(test)]
@@ -416,7 +416,7 @@ mod tests {
.to_string(),
);
dto.id = Some(91);
dto
return dto;
}
fn make_instruction() -> crate::KbChainInstructionDto {
@@ -445,7 +445,7 @@ mod tests {
None,
);
dto.id = Some(17);
dto
return dto;
}
#[test]
@@ -470,13 +470,13 @@ mod tests {
Some("AssociatedBondingCurve111".to_string())
);
assert_eq!(event.creator, Some("Creator111".to_string()));
}
},
crate::KbPumpFunDecodedEvent::BuyTrade(_) => {
panic!("unexpected pump_fun buy trade event");
}
},
crate::KbPumpFunDecodedEvent::SellTrade(_) => {
panic!("unexpected pump_fun sell trade event");
}
},
}
}

View File

@@ -46,7 +46,7 @@ pub struct KbPumpSwapDecoder;
impl KbPumpSwapDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more PumpSwap events.
@@ -63,7 +63,7 @@ impl KbPumpSwapDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -74,7 +74,7 @@ impl KbPumpSwapDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -110,40 +110,20 @@ impl KbPumpSwapDecoder {
parsed_json.as_ref(),
&["pool", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenAMint",
"baseMint",
"mintA",
"coinMint",
"token0Mint",
"mint0",
],
&["tokenAMint", "baseMint", "mintA", "coinMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenBMint",
"quoteMint",
"mintB",
"pcMint",
"token1Mint",
"mint1",
],
&["tokenBMint", "quoteMint", "mintB", "pcMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
let pool_v2 = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"poolV2",
"pool_v2",
"ammV2",
"bondingCurveV2",
"bonding_curve_v2",
],
&["poolV2", "pool_v2", "ammV2", "bondingCurveV2", "bonding_curve_v2"],
);
let pool_base_token_account = kb_extract_account(&accounts, 7);
let pool_quote_token_account = kb_extract_account(&accounts, 8);
@@ -200,7 +180,7 @@ impl KbPumpSwapDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -230,7 +210,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -241,7 +221,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_parse_accounts_json(
@@ -255,7 +235,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -264,7 +244,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_extract_account(
@@ -274,7 +254,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_parse_optional_parsed_json(
@@ -286,11 +266,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
},
}
}
@@ -302,7 +284,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -337,7 +319,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
#[cfg(test)]
@@ -367,7 +349,7 @@ mod tests {
.to_string(),
);
dto.id = Some(92);
dto
return dto;
}
fn make_instruction() -> crate::KbChainInstructionDto {
@@ -389,7 +371,8 @@ mod tests {
"UserQuoteAta111",
"PoolBaseVault111",
"PoolQuoteVault111"
]).to_string(),
])
.to_string(),
None,
None,
Some(
@@ -405,7 +388,7 @@ mod tests {
),
);
dto.id = Some(18);
dto
return dto;
}
#[test]
@@ -436,10 +419,10 @@ mod tests {
Some(&serde_json::Value::String("PoolQuoteVault111".to_string()))
);
assert_eq!(event.trade_side, crate::KbSwapTradeSide::BuyBase);
}
},
crate::KbPumpSwapDecodedEvent::SellTrade(_) => {
panic!("unexpected sell event")
}
},
}
}

View File

@@ -44,7 +44,7 @@ pub struct KbRaydiumAmmV4Decoder;
impl KbRaydiumAmmV4Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Raydium AmmV4 events.
@@ -61,7 +61,7 @@ impl KbRaydiumAmmV4Decoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -72,7 +72,7 @@ impl KbRaydiumAmmV4Decoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let has_initialize2_log = kb_log_messages_contain_initialize2(&log_messages);
@@ -139,7 +139,7 @@ impl KbRaydiumAmmV4Decoder {
},
));
}
Ok(decoded_events)
return Ok(decoded_events)
}
}
@@ -168,7 +168,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages
}
fn kb_log_messages_contain_initialize2(log_messages: &[std::string::String]) -> bool {
@@ -177,7 +177,7 @@ fn kb_log_messages_contain_initialize2(log_messages: &[std::string::String]) ->
return true;
}
}
false
return false
}
fn kb_parse_accounts_json(
@@ -191,7 +191,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -200,7 +200,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts)
}
fn kb_extract_account(
@@ -210,7 +210,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone())
}
#[cfg(test)]
@@ -240,7 +240,7 @@ mod tests {
.to_string(),
);
dto.id = Some(42);
dto
return dto
}
fn make_instruction() -> crate::KbChainInstructionDto {
@@ -277,7 +277,7 @@ mod tests {
None,
);
dto.id = Some(7);
dto
return dto
}
#[test]
@@ -300,7 +300,7 @@ mod tests {
assert_eq!(event.token_a_mint, Some("TokenA111".to_string()));
assert_eq!(event.token_b_mint, Some("TokenB111".to_string()));
assert_eq!(event.market_account, Some("Market111".to_string()));
}
},
}
}

View File

@@ -20,28 +20,28 @@ impl KbRaydiumClmmDecodedEvent {
/// Returns the normalized event kind.
pub fn event_kind(&self) -> &'static str {
match self {
crate::KbRaydiumClmmDecodedEvent::SwapV2(_) => "raydium_clmm.swap_v2",
crate::KbRaydiumClmmDecodedEvent::SwapV2(_) => return "raydium_clmm.swap_v2",
}
}
/// Returns the pool account.
pub fn pool_account(&self) -> &str {
match self {
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => event.pool_state.as_str(),
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => return event.pool_state.as_str(),
}
}
/// Returns the normalized base mint.
pub fn base_mint(&self) -> &str {
match self {
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => event.base_mint.as_str(),
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => return event.base_mint.as_str(),
}
}
/// Returns the normalized quote mint.
pub fn quote_mint(&self) -> &str {
match self {
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => event.quote_mint.as_str(),
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => return event.quote_mint.as_str(),
}
}
@@ -51,10 +51,10 @@ impl KbRaydiumClmmDecodedEvent {
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => {
let result = serde_json::to_string(event);
match result {
Ok(payload_json) => Some(payload_json),
Err(_) => None,
Ok(payload_json) => return Some(payload_json),
Err(_) => return None,
}
}
},
}
}
}
@@ -144,7 +144,7 @@ pub fn kb_decode_raydium_clmm_instruction(
None => return decoded,
};
decoded.push(crate::KbRaydiumClmmDecodedEvent::SwapV2(event));
decoded
return decoded;
}
fn kb_decode_swap_v2(
@@ -219,7 +219,7 @@ fn kb_decode_swap_v2(
quote_vault = input_vault.clone();
trade_side = "BuyBase".to_string();
}
Some(crate::KbRaydiumClmmSwapV2Decoded {
return Some(crate::KbRaydiumClmmSwapV2Decoded {
payer,
amm_config,
pool_state,
@@ -239,7 +239,7 @@ fn kb_decode_swap_v2(
other_amount_threshold,
sqrt_price_limit_x64: sqrt_price_limit_x64.to_string(),
is_base_input,
})
});
}
fn kb_clone_account(
@@ -248,8 +248,8 @@ fn kb_clone_account(
) -> std::option::Option<std::string::String> {
let account_option = accounts.get(index);
match account_option {
Some(account) => Some(account.clone()),
None => None,
Some(account) => return Some(account.clone()),
None => return None,
}
}
@@ -263,7 +263,7 @@ fn kb_read_discriminator(data: &[u8]) -> std::option::Option<[u8; 8]> {
bytes[index] = data[index];
index += 1;
}
Some(bytes)
return Some(bytes);
}
fn kb_read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
@@ -276,7 +276,7 @@ fn kb_read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
bytes[index] = data[offset + index];
index += 1;
}
Some(u64::from_le_bytes(bytes))
return Some(u64::from_le_bytes(bytes));
}
fn kb_read_u128_le(data: &[u8], offset: usize) -> std::option::Option<u128> {
@@ -289,7 +289,7 @@ fn kb_read_u128_le(data: &[u8], offset: usize) -> std::option::Option<u128> {
bytes[index] = data[offset + index];
index += 1;
}
Some(u128::from_le_bytes(bytes))
return Some(u128::from_le_bytes(bytes));
}
fn kb_read_bool(data: &[u8], offset: usize) -> std::option::Option<bool> {
@@ -297,9 +297,9 @@ fn kb_read_bool(data: &[u8], offset: usize) -> std::option::Option<bool> {
return None;
}
match data[offset] {
0 => Some(false),
1 => Some(true),
_ => None,
0 => return Some(false),
1 => return Some(true),
_ => return None,
}
}
@@ -349,13 +349,13 @@ fn kb_decode_base58(input: &str) -> std::option::Option<std::vec::Vec<u8>> {
for byte in bytes {
result.push(byte);
}
Some(result)
return Some(result);
}
#[cfg(test)]
mod tests {
fn sample_swap_v2_accounts_json() -> &'static str {
r#"[
return r#"[
"8NQ32SyFKD1d5kenq4oM8Da6C6J9TQSMW1uAgFRveEQr",
"A1BBtTYJd4i3xU8D6Tc2FzU6ZN4oXZWXKZnCxwbHXr8x",
"GUrRxvnWVQSnbcz1eP9D5BqXwPZtRhmrqVfm5wY9meWR",
@@ -371,7 +371,7 @@ mod tests {
"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs",
"8ovxZR2Gv9Mr73aoXLQYTMaZvHCSpnEohzgjVHQwmyHr",
"9MssDxndh2Rn8DmGWL94hXVv22zxfDYHV7tvzfPgcaWe"
]"#
]"#;
}
#[test]
@@ -388,48 +388,21 @@ mod tests {
events[0].pool_account(),
"GUrRxvnWVQSnbcz1eP9D5BqXwPZtRhmrqVfm5wY9meWR"
);
assert_eq!(
event.pool_state,
"GUrRxvnWVQSnbcz1eP9D5BqXwPZtRhmrqVfm5wY9meWR"
);
assert_eq!(
event.input_vault,
"AvRzvwpSVnxsinLGQS3vZLqkZxhXZDM8F2qKccAo7rSq"
);
assert_eq!(
event.output_vault,
"CTkc4xDrpzjWcFLC1cxmUZZjZLSRV46HZa8wu5eKTbuh"
);
assert_eq!(
event.input_vault_mint,
"CKvjP8FrZpaKXjASEtX2nEU9w7M4RKskfnLQbKJBodV"
);
assert_eq!(
event.output_vault_mint,
"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"
);
assert_eq!(
event.base_mint,
"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"
);
assert_eq!(
event.quote_mint,
"CKvjP8FrZpaKXjASEtX2nEU9w7M4RKskfnLQbKJBodV"
);
assert_eq!(
event.base_vault,
"CTkc4xDrpzjWcFLC1cxmUZZjZLSRV46HZa8wu5eKTbuh"
);
assert_eq!(
event.quote_vault,
"AvRzvwpSVnxsinLGQS3vZLqkZxhXZDM8F2qKccAo7rSq"
);
assert_eq!(event.pool_state, "GUrRxvnWVQSnbcz1eP9D5BqXwPZtRhmrqVfm5wY9meWR");
assert_eq!(event.input_vault, "AvRzvwpSVnxsinLGQS3vZLqkZxhXZDM8F2qKccAo7rSq");
assert_eq!(event.output_vault, "CTkc4xDrpzjWcFLC1cxmUZZjZLSRV46HZa8wu5eKTbuh");
assert_eq!(event.input_vault_mint, "CKvjP8FrZpaKXjASEtX2nEU9w7M4RKskfnLQbKJBodV");
assert_eq!(event.output_vault_mint, "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs");
assert_eq!(event.base_mint, "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs");
assert_eq!(event.quote_mint, "CKvjP8FrZpaKXjASEtX2nEU9w7M4RKskfnLQbKJBodV");
assert_eq!(event.base_vault, "CTkc4xDrpzjWcFLC1cxmUZZjZLSRV46HZa8wu5eKTbuh");
assert_eq!(event.quote_vault, "AvRzvwpSVnxsinLGQS3vZLqkZxhXZDM8F2qKccAo7rSq");
assert_eq!(event.trade_side, "BuyBase");
assert_eq!(event.amount, 148441657491969);
assert_eq!(event.other_amount_threshold, 0);
assert_eq!(event.sqrt_price_limit_x64, "0");
assert_eq!(event.is_base_input, true);
}
assert!(event.is_base_input);
},
}
}
@@ -476,15 +449,7 @@ mod tests {
#[test]
fn ignores_legacy_swap_for_now() {
let mut data = std::vec::Vec::<u8>::new();
data.push(248);
data.push(198);
data.push(158);
data.push(145);
data.push(225);
data.push(117);
data.push(135);
data.push(200);
let mut data: std::vec::Vec<u8> = vec![248, 198, 158, 145, 225, 117, 135, 200];
while data.len() < 41 {
data.push(0);
}

View File

@@ -91,32 +91,32 @@ impl KbRaydiumCpmmDecodedEvent {
/// Returns the storage event kind.
pub fn event_kind(&self) -> &'static str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(_) => "raydium_cpmm.swap_base_input",
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(_) => "raydium_cpmm.swap_base_output",
KbRaydiumCpmmDecodedEvent::SwapBaseInput(_) => return "raydium_cpmm.swap_base_input",
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(_) => return "raydium_cpmm.swap_base_output",
}
}
/// Returns the pool account.
pub fn pool_account(&self) -> &str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => event.pool_state.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => event.pool_state.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.pool_state.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.pool_state.as_str(),
}
}
/// Returns the normalized base mint.
pub fn base_mint(&self) -> &str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => event.base_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => event.base_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.base_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.base_mint.as_str(),
}
}
/// Returns the normalized quote mint.
pub fn quote_mint(&self) -> &str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => event.quote_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => event.quote_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.quote_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.quote_mint.as_str(),
}
}
@@ -126,17 +126,17 @@ impl KbRaydiumCpmmDecodedEvent {
crate::KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => {
let result = serde_json::to_string(event);
match result {
Ok(payload) => Some(payload),
Err(_) => None,
Ok(payload) => return Some(payload),
Err(_) => return None,
}
}
},
crate::KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => {
let result = serde_json::to_string(event);
match result {
Ok(payload) => Some(payload),
Err(_) => None,
Ok(payload) => return Some(payload),
Err(_) => return None,
}
}
},
}
}
}
@@ -161,9 +161,7 @@ pub fn kb_decode_raydium_cpmm_instruction(
if data.len() < 24 {
return std::vec::Vec::new();
}
let discriminator = [
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
];
let discriminator = [data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]];
if discriminator == KB_RAYDIUM_CPMM_SWAP_BASE_INPUT_DISCRIMINATOR {
let amount_in = match kb_read_u64_le(data.as_slice(), 8) {
Some(value) => value,
@@ -208,7 +206,7 @@ pub fn kb_decode_raydium_cpmm_instruction(
};
return vec![KbRaydiumCpmmDecodedEvent::SwapBaseOutput(swap)];
}
std::vec::Vec::new()
return std::vec::Vec::new();
}
fn kb_build_raydium_cpmm_swap(
@@ -233,12 +231,8 @@ fn kb_build_raydium_cpmm_swap(
output_vault.as_str(),
);
let input_is_base = normalized.input_is_base;
let trade_side = if input_is_base {
"sell".to_string()
} else {
"buy".to_string()
};
Some(KbRaydiumCpmmSwapDecoded {
let trade_side = if input_is_base { "sell".to_string() } else { "buy".to_string() };
return Some(KbRaydiumCpmmSwapDecoded {
swap_mode,
payer: accounts[0].clone(),
authority: accounts[1].clone(),
@@ -263,7 +257,7 @@ fn kb_build_raydium_cpmm_swap(
minimum_amount_out_raw,
max_amount_in_raw,
amount_out_raw,
})
});
}
struct KbRaydiumCpmmNormalizedPair {
@@ -307,13 +301,13 @@ fn kb_normalize_raydium_cpmm_pair(
input_is_base: true,
};
}
KbRaydiumCpmmNormalizedPair {
return KbRaydiumCpmmNormalizedPair {
base_mint: output_mint.to_string(),
quote_mint: input_mint.to_string(),
base_vault: output_vault.to_string(),
quote_vault: input_vault.to_string(),
input_is_base: false,
}
};
}
fn kb_is_quote_mint(mint: &str) -> bool {
@@ -329,7 +323,7 @@ fn kb_is_quote_mint(mint: &str) -> bool {
if mint == "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB" {
return true;
}
false
return false;
}
fn kb_parse_accounts_json(
@@ -337,16 +331,15 @@ fn kb_parse_accounts_json(
) -> std::option::Option<std::vec::Vec<std::string::String>> {
let result = serde_json::from_str::<std::vec::Vec<std::string::String>>(accounts_json);
match result {
Ok(accounts) => Some(accounts),
Err(_) => None,
Ok(accounts) => return Some(accounts),
Err(_) => return None,
}
}
fn kb_parse_data_json_as_base58(data_json: &str) -> std::option::Option<std::string::String> {
let json_string_result = serde_json::from_str::<std::string::String>(data_json);
match json_string_result {
Ok(value) => return Some(value),
Err(_) => {}
if let Ok(value) = json_string_result {
return Some(value);
}
let trimmed = data_json.trim();
if trimmed.is_empty() {
@@ -356,7 +349,7 @@ fn kb_parse_data_json_as_base58(data_json: &str) -> std::option::Option<std::str
if without_quotes.is_empty() {
return None;
}
Some(without_quotes.to_string())
return Some(without_quotes.to_string());
}
fn kb_read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
@@ -373,7 +366,7 @@ fn kb_read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
data[offset + 6],
data[offset + 7],
];
Some(u64::from_le_bytes(bytes))
return Some(u64::from_le_bytes(bytes));
}
#[cfg(test)]
@@ -402,26 +395,17 @@ mod tests {
assert_eq!(events.len(), 1);
match &events[0] {
crate::KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => {
assert_eq!(
event.pool_state,
"2ErXvV1tKtG3wiHqdofDjMou7Jusdsfasvfh8HrTj5oV"
);
assert_eq!(
event.base_mint,
"Pf9aSicGu3g6tTUBqrRbjNsGape9HopibspX5KSbonk"
);
assert_eq!(
event.quote_mint,
"USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB"
);
assert_eq!(event.input_is_base, true);
assert_eq!(event.pool_state, "2ErXvV1tKtG3wiHqdofDjMou7Jusdsfasvfh8HrTj5oV");
assert_eq!(event.base_mint, "Pf9aSicGu3g6tTUBqrRbjNsGape9HopibspX5KSbonk");
assert_eq!(event.quote_mint, "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB");
assert!(event.input_is_base);
assert_eq!(event.trade_side, "sell");
assert_eq!(event.amount_in_raw.is_some(), true);
assert_eq!(event.minimum_amount_out_raw.is_some(), true);
}
assert!(event.amount_in_raw.is_some());
assert!(event.minimum_amount_out_raw.is_some());
},
_ => {
panic!("expected swap base input");
}
},
}
}
#[test]
@@ -448,26 +432,17 @@ mod tests {
assert_eq!(events.len(), 1);
match &events[0] {
crate::KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => {
assert_eq!(
event.pool_state,
"2ErXvV1tKtG3wiHqdofDjMou7Jusdsfasvfh8HrTj5oV"
);
assert_eq!(
event.base_mint,
"Pf9aSicGu3g6tTUBqrRbjNsGape9HopibspX5KSbonk"
);
assert_eq!(
event.quote_mint,
"USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB"
);
assert_eq!(event.input_is_base, false);
assert_eq!(event.pool_state, "2ErXvV1tKtG3wiHqdofDjMou7Jusdsfasvfh8HrTj5oV");
assert_eq!(event.base_mint, "Pf9aSicGu3g6tTUBqrRbjNsGape9HopibspX5KSbonk");
assert_eq!(event.quote_mint, "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB");
assert!(!event.input_is_base);
assert_eq!(event.trade_side, "buy");
assert_eq!(event.max_amount_in_raw.is_some(), true);
assert_eq!(event.amount_out_raw.is_some(), true);
}
assert!(event.max_amount_in_raw.is_some());
assert!(event.amount_out_raw.is_some());
},
_ => {
panic!("expected swap base output");
}
},
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -33,36 +33,18 @@ pub enum KbError {
impl std::fmt::Display for KbError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Config(message) => {
write!(formatter, "configuration error: {message}")
}
Self::Io(message) => {
write!(formatter, "io error: {message}")
}
Self::Json(message) => {
write!(formatter, "json error: {message}")
}
Self::Tracing(message) => {
write!(formatter, "tracing error: {message}")
}
Self::Http(message) => {
write!(formatter, "http error: {message}")
}
Self::Ws(message) => {
write!(formatter, "websocket error: {message}")
}
Self::Db(message) => {
write!(formatter, "db error: {}", message)
}
Self::InvalidState(message) => {
write!(formatter, "invalid state: {message}")
}
Self::NotConnected(message) => {
write!(formatter, "not connected: {message}")
}
Self::Config(message) => return write!(formatter, "configuration error: {message}"),
Self::Io(message) => return write!(formatter, "io error: {message}"),
Self::Json(message) => return write!(formatter, "json error: {message}"),
Self::Tracing(message) => return write!(formatter, "tracing error: {message}"),
Self::Http(message) => return write!(formatter, "http error: {message}"),
Self::Ws(message) => return write!(formatter, "websocket error: {message}"),
Self::Db(message) => return write!(formatter, "db error: {}", message),
Self::InvalidState(message) => return write!(formatter, "invalid state: {message}"),
Self::NotConnected(message) => return write!(formatter, "not connected: {message}"),
Self::NotImplemented(message) => {
write!(formatter, "not implemented: {message}")
}
return write!(formatter, "not implemented: {message}");
},
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -44,10 +44,10 @@ impl HttpEndpointPool {
"http endpoint pool requires at least one client".to_string(),
));
}
Ok(Self {
return Ok(Self {
clients,
next_index: std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)),
})
});
}
/// Creates a pool from endpoint configurations.
@@ -66,22 +66,22 @@ impl HttpEndpointPool {
};
clients.push(client);
}
Self::new(clients)
return Self::new(clients);
}
/// Creates a pool from the global configuration.
pub fn from_config(config: &crate::KbConfig) -> Result<Self, crate::KbError> {
Self::from_endpoint_configs(config.solana.http_endpoints.clone())
return Self::from_endpoint_configs(config.solana.http_endpoints.clone());
}
/// Returns the number of pooled clients.
pub fn len(&self) -> usize {
self.clients.len()
return self.clients.len();
}
/// Returns whether the pool is empty.
pub fn is_empty(&self) -> bool {
self.clients.is_empty()
return self.clients.is_empty();
}
/// Returns a live snapshot of pooled endpoints.
@@ -94,7 +94,7 @@ impl HttpEndpointPool {
crate::KbHttpEndpointStatus::Disabled => ("Disabled".to_string(), None),
crate::KbHttpEndpointStatus::Paused { remaining_ms } => {
("Paused".to_string(), Some(remaining_ms))
}
},
};
snapshots.push(KbHttpPoolClientSnapshot {
endpoint_name: client.endpoint_name().to_string(),
@@ -106,7 +106,7 @@ impl HttpEndpointPool {
available_concurrency_slots: client.available_concurrency_slots(),
});
}
snapshots
return snapshots;
}
/// Selects one client for a role and method.
@@ -134,13 +134,13 @@ impl HttpEndpointPool {
match status {
crate::KbHttpEndpointStatus::Active => {
active_indices.push(index);
}
},
crate::KbHttpEndpointStatus::Paused { .. } => {
paused_count += 1;
}
},
crate::KbHttpEndpointStatus::Disabled => {
disabled_count += 1;
}
},
}
index += 1;
}
@@ -150,9 +150,7 @@ impl HttpEndpointPool {
required_role, method, paused_count, disabled_count, role_mismatch_count
)));
}
let rotation_seed = self
.next_index
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let rotation_seed = self.next_index.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let active_len = active_indices.len();
let mut offset = 0usize;
while offset < active_len {
@@ -164,7 +162,7 @@ impl HttpEndpointPool {
offset += 1;
}
let fallback_index = active_indices[rotation_seed % active_len];
Ok(self.clients[fallback_index].clone())
return Ok(self.clients[fallback_index].clone());
}
/// Executes one raw JSON-RPC request through the pool.
@@ -174,14 +172,12 @@ impl HttpEndpointPool {
method: std::string::String,
params: std::vec::Vec<serde_json::Value>,
) -> Result<serde_json::Value, crate::KbError> {
let client_result = self
.select_client_for_role_and_method(required_role, &method)
.await;
let client_result = self.select_client_for_role_and_method(required_role, &method).await;
let client = match client_result {
Ok(client) => client,
Err(error) => return Err(error),
};
client.execute_json_rpc_result_raw(method, params).await
return client.execute_json_rpc_result_raw(method, params).await;
}
/// Executes one typed JSON-RPC request through the pool.
@@ -194,28 +190,25 @@ impl HttpEndpointPool {
where
T: serde::de::DeserializeOwned,
{
let client_result = self
.select_client_for_role_and_method(required_role, &method)
.await;
let client_result = self.select_client_for_role_and_method(required_role, &method).await;
let client = match client_result {
Ok(client) => client,
Err(error) => return Err(error),
};
client
.execute_json_rpc_request_typed::<T>(method, params)
.await
return client.execute_json_rpc_request_typed::<T>(method, params).await;
}
/// Executes `getHealth` through the pool.
pub async fn get_health_for_role(
&self,
required_role: &str,
) -> Result<std::string::String, crate::KbError> {
self.execute_json_rpc_request_typed_for_role::<std::string::String>(
required_role,
"getHealth".to_string(),
std::vec::Vec::new(),
)
.await
return self
.execute_json_rpc_request_typed_for_role::<std::string::String>(
required_role,
"getHealth".to_string(),
std::vec::Vec::new(),
)
.await;
}
/// Executes `getSlot` through the pool.
@@ -231,12 +224,13 @@ impl HttpEndpointPool {
Err(error) => return Err(error),
};
let params = kb_pool_build_optional_config_only_params(config_value);
self.execute_json_rpc_request_typed_for_role::<u64>(
required_role,
"getSlot".to_string(),
params,
)
.await
return self
.execute_json_rpc_request_typed_for_role::<u64>(
required_role,
"getSlot".to_string(),
params,
)
.await;
}
/// Executes `sendTransaction` through the pool.
@@ -254,12 +248,13 @@ impl HttpEndpointPool {
};
let params =
kb_pool_build_first_string_optional_config_params(encoded_transaction, config_value);
self.execute_json_rpc_request_typed_for_role::<std::string::String>(
required_role,
"sendTransaction".to_string(),
params,
)
.await
return self
.execute_json_rpc_request_typed_for_role::<std::string::String>(
required_role,
"sendTransaction".to_string(),
params,
)
.await;
}
/// Executes `getTransaction` through the pool and returns the raw result value.
@@ -269,14 +264,29 @@ impl HttpEndpointPool {
signature: std::string::String,
config: std::option::Option<serde_json::Value>,
) -> Result<serde_json::Value, crate::KbError> {
let client_result = self
.select_client_for_role_and_method(required_role, "getTransaction")
.await;
let client_result =
self.select_client_for_role_and_method(required_role, "getTransaction").await;
let client = match client_result {
Ok(client) => client,
Err(error) => return Err(error),
};
client.get_transaction_raw(signature, config).await
return client.get_transaction_raw(signature, config).await;
}
/// Executes `getAccountInfo` through the pool and returns the raw result value.
pub async fn get_account_info_raw_for_role(
&self,
required_role: &str,
address: std::string::String,
config: std::option::Option<serde_json::Value>,
) -> Result<serde_json::Value, crate::KbError> {
let client_result =
self.select_client_for_role_and_method(required_role, "getAccountInfo").await;
let client = match client_result {
Ok(client) => client,
Err(error) => return Err(error),
};
return client.get_account_info_raw(address, config).await;
}
/// Executes `getProgramAccounts` through the pool and returns the raw result value.
@@ -293,7 +303,7 @@ impl HttpEndpointPool {
Ok(client) => client,
Err(error) => return Err(error),
};
client.get_program_accounts_raw(program_id, config).await
return client.get_program_accounts_raw(program_id, config).await;
}
/// Executes `getSignaturesForAddress` through the pool and returns the raw result value.
@@ -310,7 +320,7 @@ impl HttpEndpointPool {
Ok(client) => client,
Err(error) => return Err(error),
};
client.get_signatures_for_address_raw(address, config).await
return client.get_signatures_for_address_raw(address, config).await;
}
/// Executes typed `getSignaturesForAddress` through the pool.
@@ -330,7 +340,7 @@ impl HttpEndpointPool {
Ok(client) => client,
Err(error) => return Err(error),
};
client.get_signatures_for_address(address, config).await
return client.get_signatures_for_address(address, config).await;
}
}
@@ -342,7 +352,7 @@ fn kb_pool_build_optional_config_only_params(
if let Some(config) = config {
params.push(config);
}
params
return params;
}
fn kb_pool_build_first_string_optional_config_params(
@@ -350,11 +360,10 @@ fn kb_pool_build_first_string_optional_config_params(
config: std::option::Option<serde_json::Value>,
) -> std::vec::Vec<serde_json::Value> {
let mut params = vec![serde_json::Value::String(first)];
if let Some(config) = config {
params.push(config);
}
params
return params;
}
fn kb_pool_serialize_optional_json_value<T>(
@@ -368,14 +377,16 @@ where
Some(value) => {
let value_result = serde_json::to_value(value);
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
"cannot serialize {}: {error}",
label
))),
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot serialize {}: {error}",
label
)));
},
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
@@ -472,11 +483,12 @@ mod tests {
}
}
});
Self {
return Self {
url: format!("http://{}", local_addr),
shutdown_tx: Some(shutdown_tx),
}
};
}
async fn shutdown(mut self) {
if let Some(shutdown_tx) = self.shutdown_tx.take() {
let _ = shutdown_tx.send(());
@@ -490,7 +502,7 @@ mod tests {
url: std::string::String,
roles: std::vec::Vec<std::string::String>,
) -> crate::KbHttpEndpointConfig {
crate::KbHttpEndpointConfig {
return crate::KbHttpEndpointConfig {
name: name.to_string(),
enabled: true,
provider: provider.to_string(),
@@ -508,7 +520,7 @@ mod tests {
max_idle_connections_per_host: 4,
max_concurrent_requests_per_endpoint: 2,
pause_after_http_429_ms: Some(1500),
}
};
}
#[tokio::test]
@@ -595,12 +607,8 @@ mod tests {
.expect("pool creation must succeed");
let snapshots = pool.snapshot().await;
assert_eq!(snapshots.len(), 2);
assert!(snapshots.iter().any(|snapshot| snapshot.status == "Active"));
assert!(
snapshots
.iter()
.any(|snapshot| snapshot.status == "Disabled")
);
assert!(snapshots.iter().any(|snapshot| return snapshot.status == "Active"));
assert!(snapshots.iter().any(|snapshot| return snapshot.status == "Disabled"));
}
#[tokio::test]
@@ -669,18 +677,16 @@ mod tests {
client_a.disable().await;
let pool =
crate::HttpEndpointPool::new(vec![client_a]).expect("pool creation must succeed");
let result = pool
.select_client_for_role_and_method("http_queries", "getSlot")
.await;
let result = pool.select_client_for_role_and_method("http_queries", "getSlot").await;
assert!(result.is_err());
let error = result.expect_err("selection must fail");
match error {
crate::KbError::Http(message) => {
assert!(message.contains("no active http endpoint available"));
}
},
other => {
panic!("unexpected error: {other:?}");
}
},
}
}
}

Some files were not shown because too many files have changed in this diff Show More