0.7.27 +Refactor

This commit is contained in:
2026-05-10 00:33:01 +02:00
parent cb2e8e7096
commit 1f0137b9de
261 changed files with 12308 additions and 8928 deletions

View File

@@ -2,12 +2,9 @@
//! DexLab Swap/Pool transaction decoder.
/// DexLab Swap/Pool program id.
pub const KB_DEXLAB_PROGRAM_ID: &str = "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N";
/// Decoded DexLab create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbDexlabCreatePoolDecoded {
pub struct DexlabCreatePoolDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -32,7 +29,7 @@ pub struct KbDexlabCreatePoolDecoded {
/// Decoded DexLab swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbDexlabSwapDecoded {
pub struct DexlabSwapDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -42,7 +39,7 @@ pub struct KbDexlabSwapDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side relative to normalized base.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Optional pool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional token A mint.
@@ -55,25 +52,25 @@ pub struct KbDexlabSwapDecoded {
/// Decoded DexLab event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbDexlabDecodedEvent {
pub enum DexlabDecodedEvent {
/// Pool creation.
CreatePool(KbDexlabCreatePoolDecoded),
CreatePool(DexlabCreatePoolDecoded),
/// Swap.
Swap(KbDexlabSwapDecoded),
Swap(DexlabSwapDecoded),
}
/// DexLab decoder.
#[derive(Debug, Clone, Default)]
pub struct KbDexlabDecoder;
pub struct DexlabDecoder;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KbDexlabInstructionKind {
enum DexlabInstructionKind {
CreatePool,
Swap,
Unknown,
}
impl KbDexlabDecoder {
impl DexlabDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -82,14 +79,14 @@ impl KbDexlabDecoder {
/// Decodes one projected transaction into zero or more DexLab events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbDexlabDecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::DexlabDecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -100,13 +97,13 @@ impl KbDexlabDecoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let log_messages = extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -117,7 +114,7 @@ impl KbDexlabDecoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_DEXLAB_PROGRAM_ID {
if program_id.as_str() != crate::DEXLAB_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -125,44 +122,42 @@ impl KbDexlabDecoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
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 = 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),
};
let instruction_kind =
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
let instruction_kind = classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 0));
let token_a_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 1));
let token_b_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| return kb_extract_account(&accounts, 2));
let creator = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 2));
let creator = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["payer", "creator", "user", "owner"],
)
.or_else(|| return kb_extract_account(&accounts, 3));
let fee_tier = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 3));
let fee_tier = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["feeTier", "fee_tier", "tradeFeeTier", "feeRate"],
);
if instruction_kind == KbDexlabInstructionKind::CreatePool {
if instruction_kind == DexlabInstructionKind::CreatePool {
let payload_json = serde_json::json!({
"decoder": "dexlab",
"eventKind": "create_pool",
@@ -178,8 +173,8 @@ impl KbDexlabDecoder {
"creator": creator,
"feeTier": fee_tier
});
decoded_events.push(crate::KbDexlabDecodedEvent::CreatePool(
crate::KbDexlabCreatePoolDecoded {
decoded_events.push(crate::DexlabDecodedEvent::CreatePool(
crate::DexlabCreatePoolDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -194,8 +189,8 @@ impl KbDexlabDecoder {
));
continue;
}
if instruction_kind == KbDexlabInstructionKind::Swap {
let trade_side = kb_infer_trade_side(&log_messages);
if instruction_kind == DexlabInstructionKind::Swap {
let trade_side = infer_trade_side(&log_messages);
let payload_json = serde_json::json!({
"decoder": "dexlab",
"eventKind": "swap",
@@ -210,57 +205,55 @@ impl KbDexlabDecoder {
"tokenBMint": token_b_mint,
"tradeSide": format!("{:?}", trade_side)
});
decoded_events.push(crate::KbDexlabDecodedEvent::Swap(
crate::KbDexlabSwapDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.clone(),
trade_side,
pool_account,
token_a_mint,
token_b_mint,
payload_json,
},
));
decoded_events.push(crate::DexlabDecodedEvent::Swap(crate::DexlabSwapDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.clone(),
trade_side,
pool_account,
token_a_mint,
token_b_mint,
payload_json,
}));
}
}
return Ok(decoded_events);
}
}
fn kb_classify_instruction_kind(
fn classify_instruction_kind(
parsed_json: std::option::Option<&serde_json::Value>,
log_messages: &[std::string::String],
) -> KbDexlabInstructionKind {
let parsed_instruction_name = kb_extract_string_by_candidate_keys(
) -> DexlabInstructionKind {
let parsed_instruction_name = extract_string_by_candidate_keys(
parsed_json,
&["instruction", "instructionName", "type", "name"],
);
if let Some(parsed_instruction_name) = parsed_instruction_name {
let normalized = kb_normalize_text(parsed_instruction_name.as_str());
let normalized = normalize_text(parsed_instruction_name.as_str());
if normalized.contains("createpool") || normalized.contains("initializepool") {
return KbDexlabInstructionKind::CreatePool;
return DexlabInstructionKind::CreatePool;
}
if normalized == "swap" {
return KbDexlabInstructionKind::Swap;
return DexlabInstructionKind::Swap;
}
}
if kb_log_messages_contain_keyword(log_messages, "create_pool")
|| kb_log_messages_contain_keyword(log_messages, "createpool")
|| kb_log_messages_contain_keyword(log_messages, "initialize_pool")
|| kb_log_messages_contain_keyword(log_messages, "initializepool")
if log_messages_contain_keyword(log_messages, "create_pool")
|| log_messages_contain_keyword(log_messages, "createpool")
|| log_messages_contain_keyword(log_messages, "initialize_pool")
|| log_messages_contain_keyword(log_messages, "initializepool")
{
return KbDexlabInstructionKind::CreatePool;
return DexlabInstructionKind::CreatePool;
}
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbDexlabInstructionKind::Swap;
if log_messages_contain_keyword(log_messages, "swap") {
return DexlabInstructionKind::Swap;
}
return KbDexlabInstructionKind::Unknown;
return DexlabInstructionKind::Unknown;
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -287,10 +280,13 @@ fn kb_extract_log_messages(
return messages;
}
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());
fn log_messages_contain_keyword(
log_messages_contain_keyword: &[std::string::String],
keyword: &str,
) -> bool {
let keyword_normalized = normalize_text(keyword);
for log_message in log_messages_contain_keyword {
let log_normalized = normalize_text(log_message.as_str());
if log_normalized.contains(keyword_normalized.as_str()) {
return true;
}
@@ -298,9 +294,9 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
fn normalize_text(normalize_text: &str) -> std::string::String {
let mut normalized = std::string::String::new();
for character in value.chars() {
for character in normalize_text.chars() {
if character.is_ascii_alphanumeric() {
normalized.push(character.to_ascii_lowercase());
}
@@ -308,14 +304,14 @@ fn kb_normalize_text(value: &str) -> std::string::String {
return normalized;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -331,9 +327,9 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
fn parse_optional_parsed_json(
parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::KbError> {
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
let parsed_json = match parsed_json {
Some(parsed_json) => parsed_json,
None => return Ok(None),
@@ -342,7 +338,7 @@ fn kb_parse_optional_parsed_json(
match value_result {
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
@@ -350,7 +346,7 @@ fn kb_parse_optional_parsed_json(
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -358,10 +354,10 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
return extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
fn extract_string_by_candidate_keys_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -377,7 +373,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
for nested_value in object.values() {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -387,7 +383,7 @@ fn kb_extract_string_by_candidate_keys_inner(
if let Some(array) = value.as_array() {
for nested_value in array {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -396,30 +392,30 @@ fn kb_extract_string_by_candidate_keys_inner(
return None;
}
fn kb_extract_account(
accounts: &[std::string::String],
fn extract_account(
extract_account: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
if index >= accounts.len() {
if index >= extract_account.len() {
return None;
}
return Some(accounts[index].clone());
return Some(extract_account[index].clone());
}
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;
fn infer_trade_side(log_messages: &[std::string::String]) -> crate::SwapTradeSide {
if log_messages_contain_keyword(log_messages, "buy") {
return crate::SwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
if log_messages_contain_keyword(log_messages, "sell") {
return crate::SwapTradeSide::SellBase;
}
return crate::KbSwapTradeSide::Unknown;
return crate::SwapTradeSide::Unknown;
}
#[cfg(test)]
mod tests {
fn make_create_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_create_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-dexlab-create-1".to_string(),
Some(893001),
Some(1779800001),
@@ -446,19 +442,19 @@ mod tests {
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_create_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
801,
None,
0,
None,
Some(crate::KB_DEXLAB_PROGRAM_ID.to_string()),
Some(crate::DEXLAB_PROGRAM_ID.to_string()),
Some("dexlab".to_string()),
Some(1),
serde_json::json!([
"DexlabPool111",
"DexlabTokenA111",
"So11111111111111111111111111111111111111112",
crate::WSOL_MINT_ID,
"DexlabCreator111"
])
.to_string(),
@@ -470,7 +466,7 @@ mod tests {
"instruction": "create_pool",
"pool": "DexlabPool111",
"tokenA": "DexlabTokenA111",
"tokenB": "So11111111111111111111111111111111111111112",
"tokenB": crate::WSOL_MINT_ID,
"payer": "DexlabCreator111",
"feeTier": "0.3%"
}
@@ -482,8 +478,8 @@ mod tests {
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_swap_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-dexlab-swap-1".to_string(),
Some(893002),
Some(1779800002),
@@ -510,21 +506,17 @@ mod tests {
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_swap_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
803,
None,
0,
None,
Some(crate::KB_DEXLAB_PROGRAM_ID.to_string()),
Some(crate::DEXLAB_PROGRAM_ID.to_string()),
Some("dexlab".to_string()),
Some(1),
serde_json::json!([
"DexlabSwapPool111",
"DexlabSwapTokenA111",
"So11111111111111111111111111111111111111112"
])
.to_string(),
serde_json::json!(["DexlabSwapPool111", "DexlabSwapTokenA111", crate::WSOL_MINT_ID])
.to_string(),
None,
None,
Some(
@@ -533,7 +525,7 @@ mod tests {
"instruction": "swap",
"pool": "DexlabSwapPool111",
"tokenA": "DexlabSwapTokenA111",
"tokenB": "So11111111111111111111111111111111111111112"
"tokenB": crate::WSOL_MINT_ID
}
})
.to_string(),
@@ -545,7 +537,7 @@ mod tests {
#[test]
fn dexlab_create_pool_is_detected() {
let decoder = crate::KbDexlabDecoder::new();
let decoder = crate::DexlabDecoder::new();
let transaction = make_create_transaction();
let instructions = vec![make_create_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -555,18 +547,15 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbDexlabDecodedEvent::CreatePool(event) => {
crate::DexlabDecodedEvent::CreatePool(event) => {
assert_eq!(event.transaction_id, 801);
assert_eq!(event.instruction_id, 802);
assert_eq!(event.pool_account, Some("DexlabPool111".to_string()));
assert_eq!(event.token_a_mint, Some("DexlabTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
assert_eq!(event.fee_tier, Some("0.3%".to_string()));
},
crate::KbDexlabDecodedEvent::Swap(_) => {
crate::DexlabDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
},
}
@@ -574,7 +563,7 @@ mod tests {
#[test]
fn dexlab_swap_is_detected() {
let decoder = crate::KbDexlabDecoder::new();
let decoder = crate::DexlabDecoder::new();
let transaction = make_swap_transaction();
let instructions = vec![make_swap_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -584,17 +573,14 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbDexlabDecodedEvent::Swap(event) => {
crate::DexlabDecodedEvent::Swap(event) => {
assert_eq!(event.transaction_id, 803);
assert_eq!(event.instruction_id, 804);
assert_eq!(event.pool_account, Some("DexlabSwapPool111".to_string()));
assert_eq!(event.token_a_mint, Some("DexlabSwapTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
},
crate::KbDexlabDecodedEvent::CreatePool(_) => {
crate::DexlabDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
},
}

View File

@@ -2,12 +2,9 @@
//! FluxBeam transaction decoder.
/// FluxBeam program id.
pub const KB_FLUXBEAM_PROGRAM_ID: &str = "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X";
/// Decoded FluxBeam create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbFluxbeamCreatePoolDecoded {
pub struct FluxbeamCreatePoolDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -32,7 +29,7 @@ pub struct KbFluxbeamCreatePoolDecoded {
/// Decoded FluxBeam swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbFluxbeamSwapDecoded {
pub struct FluxbeamSwapDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -42,7 +39,7 @@ pub struct KbFluxbeamSwapDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side relative to normalized base.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Optional pool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional token A mint.
@@ -55,25 +52,25 @@ pub struct KbFluxbeamSwapDecoded {
/// Decoded FluxBeam event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbFluxbeamDecodedEvent {
pub enum FluxbeamDecodedEvent {
/// Pool creation.
CreatePool(KbFluxbeamCreatePoolDecoded),
CreatePool(FluxbeamCreatePoolDecoded),
/// Swap.
Swap(KbFluxbeamSwapDecoded),
Swap(FluxbeamSwapDecoded),
}
/// FluxBeam decoder.
#[derive(Debug, Clone, Default)]
pub struct KbFluxbeamDecoder;
pub struct FluxbeamDecoder;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KbFluxbeamInstructionKind {
enum FluxbeamInstructionKind {
CreatePool,
Swap,
Unknown,
}
impl KbFluxbeamDecoder {
impl FluxbeamDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -82,14 +79,14 @@ impl KbFluxbeamDecoder {
/// Decodes one projected transaction into zero or more FluxBeam events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbFluxbeamDecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::FluxbeamDecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -100,13 +97,13 @@ impl KbFluxbeamDecoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let log_messages = extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -117,7 +114,7 @@ impl KbFluxbeamDecoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_FLUXBEAM_PROGRAM_ID {
if program_id.as_str() != crate::FLUXBEAM_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -125,45 +122,43 @@ impl KbFluxbeamDecoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
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 = 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),
};
let instruction_kind =
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
let instruction_kind = classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| return kb_extract_account(&accounts, 0));
let lp_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 0));
let lp_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["lpMint", "lp_mint", "lpTokenMint"],
)
.or_else(|| return kb_extract_account(&accounts, 1));
let token_a_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 1));
let token_a_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| return kb_extract_account(&accounts, 2));
let token_b_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 2));
let token_b_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 3));
let creator = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["payer", "creator", "user", "owner"],
)
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbFluxbeamInstructionKind::CreatePool {
.or_else(|| return extract_account(&accounts, 4));
if instruction_kind == FluxbeamInstructionKind::CreatePool {
let payload_json = serde_json::json!({
"decoder": "fluxbeam",
"eventKind": "create_pool",
@@ -179,8 +174,8 @@ impl KbFluxbeamDecoder {
"tokenBMint": token_b_mint,
"creator": creator
});
decoded_events.push(crate::KbFluxbeamDecodedEvent::CreatePool(
crate::KbFluxbeamCreatePoolDecoded {
decoded_events.push(crate::FluxbeamDecodedEvent::CreatePool(
crate::FluxbeamCreatePoolDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -195,8 +190,8 @@ impl KbFluxbeamDecoder {
));
continue;
}
if instruction_kind == KbFluxbeamInstructionKind::Swap {
let trade_side = kb_infer_trade_side(&log_messages);
if instruction_kind == FluxbeamInstructionKind::Swap {
let trade_side = infer_trade_side(&log_messages);
let payload_json = serde_json::json!({
"decoder": "fluxbeam",
"eventKind": "swap",
@@ -211,8 +206,8 @@ impl KbFluxbeamDecoder {
"tokenBMint": token_b_mint,
"tradeSide": format!("{:?}", trade_side)
});
decoded_events.push(crate::KbFluxbeamDecodedEvent::Swap(
crate::KbFluxbeamSwapDecoded {
decoded_events.push(crate::FluxbeamDecodedEvent::Swap(
crate::FluxbeamSwapDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -230,37 +225,37 @@ impl KbFluxbeamDecoder {
}
}
fn kb_classify_instruction_kind(
fn classify_instruction_kind(
parsed_json: std::option::Option<&serde_json::Value>,
log_messages: &[std::string::String],
) -> KbFluxbeamInstructionKind {
let parsed_instruction_name = kb_extract_string_by_candidate_keys(
) -> FluxbeamInstructionKind {
let parsed_instruction_name = extract_string_by_candidate_keys(
parsed_json,
&["instruction", "instructionName", "type", "name"],
);
if let Some(parsed_instruction_name) = parsed_instruction_name {
let normalized = kb_normalize_text(parsed_instruction_name.as_str());
let normalized = normalize_text(parsed_instruction_name.as_str());
if normalized.contains("createpool") || normalized.contains("initializepool") {
return KbFluxbeamInstructionKind::CreatePool;
return FluxbeamInstructionKind::CreatePool;
}
if normalized == "swap" {
return KbFluxbeamInstructionKind::Swap;
return FluxbeamInstructionKind::Swap;
}
}
if kb_log_messages_contain_keyword(log_messages, "create_pool")
|| kb_log_messages_contain_keyword(log_messages, "createpool")
|| kb_log_messages_contain_keyword(log_messages, "initialize_pool")
|| kb_log_messages_contain_keyword(log_messages, "initializepool")
if log_messages_contain_keyword(log_messages, "create_pool")
|| log_messages_contain_keyword(log_messages, "createpool")
|| log_messages_contain_keyword(log_messages, "initialize_pool")
|| log_messages_contain_keyword(log_messages, "initializepool")
{
return KbFluxbeamInstructionKind::CreatePool;
return FluxbeamInstructionKind::CreatePool;
}
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbFluxbeamInstructionKind::Swap;
if log_messages_contain_keyword(log_messages, "swap") {
return FluxbeamInstructionKind::Swap;
}
return KbFluxbeamInstructionKind::Unknown;
return FluxbeamInstructionKind::Unknown;
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -287,10 +282,10 @@ fn kb_extract_log_messages(
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_text(keyword);
fn log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = normalize_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_text(log_message.as_str());
let log_normalized = normalize_text(log_message.as_str());
if log_normalized.contains(keyword_normalized.as_str()) {
return true;
}
@@ -298,7 +293,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
fn normalize_text(value: &str) -> std::string::String {
let mut normalized = std::string::String::new();
for character in value.chars() {
if character.is_ascii_alphanumeric() {
@@ -308,14 +303,14 @@ fn kb_normalize_text(value: &str) -> std::string::String {
return normalized;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -331,9 +326,9 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
fn parse_optional_parsed_json(
parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::KbError> {
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
let parsed_json = match parsed_json {
Some(parsed_json) => parsed_json,
None => return Ok(None),
@@ -342,7 +337,7 @@ fn kb_parse_optional_parsed_json(
match value_result {
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
@@ -350,7 +345,7 @@ fn kb_parse_optional_parsed_json(
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -358,10 +353,10 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
return extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
fn extract_string_by_candidate_keys_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -377,7 +372,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
for nested_value in object.values() {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -387,7 +382,7 @@ fn kb_extract_string_by_candidate_keys_inner(
if let Some(array) = value.as_array() {
for nested_value in array {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -396,7 +391,7 @@ fn kb_extract_string_by_candidate_keys_inner(
return None;
}
fn kb_extract_account(
fn extract_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
@@ -406,20 +401,20 @@ fn kb_extract_account(
return Some(accounts[index].clone());
}
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;
fn infer_trade_side(infer_trade_side: &[std::string::String]) -> crate::SwapTradeSide {
if log_messages_contain_keyword(infer_trade_side, "buy") {
return crate::SwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
if log_messages_contain_keyword(infer_trade_side, "sell") {
return crate::SwapTradeSide::SellBase;
}
return crate::KbSwapTradeSide::Unknown;
return crate::SwapTradeSide::Unknown;
}
#[cfg(test)]
mod tests {
fn make_create_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_create_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-fluxbeam-create-1".to_string(),
Some(892001),
Some(1779700001),
@@ -446,20 +441,20 @@ mod tests {
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_create_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
701,
None,
0,
None,
Some(crate::KB_FLUXBEAM_PROGRAM_ID.to_string()),
Some(crate::FLUXBEAM_PROGRAM_ID.to_string()),
Some("fluxbeam".to_string()),
Some(1),
serde_json::json!([
"FluxPool111",
"FluxLpMint111",
"FluxTokenA111",
"So11111111111111111111111111111111111111112",
crate::WSOL_MINT_ID,
"FluxCreator111"
])
.to_string(),
@@ -472,7 +467,7 @@ mod tests {
"pool": "FluxPool111",
"lpMint": "FluxLpMint111",
"tokenA": "FluxTokenA111",
"tokenB": "So11111111111111111111111111111111111111112",
"tokenB": crate::WSOL_MINT_ID,
"payer": "FluxCreator111"
}
})
@@ -483,8 +478,8 @@ mod tests {
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_swap_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-fluxbeam-swap-1".to_string(),
Some(892002),
Some(1779700002),
@@ -511,20 +506,20 @@ mod tests {
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_swap_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
703,
None,
0,
None,
Some(crate::KB_FLUXBEAM_PROGRAM_ID.to_string()),
Some(crate::FLUXBEAM_PROGRAM_ID.to_string()),
Some("fluxbeam".to_string()),
Some(1),
serde_json::json!([
"FluxSwapPool111",
"FluxSwapLpMint111",
"FluxSwapTokenA111",
"So11111111111111111111111111111111111111112"
crate::WSOL_MINT_ID
])
.to_string(),
None,
@@ -535,7 +530,7 @@ mod tests {
"instruction": "swap",
"pool": "FluxSwapPool111",
"tokenA": "FluxSwapTokenA111",
"tokenB": "So11111111111111111111111111111111111111112"
"tokenB": crate::WSOL_MINT_ID
}
})
.to_string(),
@@ -547,7 +542,7 @@ mod tests {
#[test]
fn fluxbeam_create_pool_is_detected() {
let decoder = crate::KbFluxbeamDecoder::new();
let decoder = crate::FluxbeamDecoder::new();
let transaction = make_create_transaction();
let instructions = vec![make_create_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -557,18 +552,15 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbFluxbeamDecodedEvent::CreatePool(event) => {
crate::FluxbeamDecodedEvent::CreatePool(event) => {
assert_eq!(event.transaction_id, 701);
assert_eq!(event.instruction_id, 702);
assert_eq!(event.pool_account, Some("FluxPool111".to_string()));
assert_eq!(event.lp_mint, Some("FluxLpMint111".to_string()));
assert_eq!(event.token_a_mint, Some("FluxTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
},
crate::KbFluxbeamDecodedEvent::Swap(_) => {
crate::FluxbeamDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
},
}
@@ -576,7 +568,7 @@ mod tests {
#[test]
fn fluxbeam_swap_is_detected() {
let decoder = crate::KbFluxbeamDecoder::new();
let decoder = crate::FluxbeamDecoder::new();
let transaction = make_swap_transaction();
let instructions = vec![make_swap_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -586,17 +578,14 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbFluxbeamDecodedEvent::Swap(event) => {
crate::FluxbeamDecodedEvent::Swap(event) => {
assert_eq!(event.transaction_id, 703);
assert_eq!(event.instruction_id, 704);
assert_eq!(event.pool_account, Some("FluxSwapPool111".to_string()));
assert_eq!(event.token_a_mint, Some("FluxSwapTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
},
crate::KbFluxbeamDecodedEvent::CreatePool(_) => {
crate::FluxbeamDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
},
}

View File

@@ -2,12 +2,9 @@
//! Meteora DAMM v1 transaction decoder.
/// Meteora DAMM v1 program id.
pub const KB_METEORA_DAMM_V1_PROGRAM_ID: &str = "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB";
/// Decoded Meteora DAMM v1 create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbMeteoraDammV1CreatePoolDecoded {
pub struct MeteoraDammV1CreatePoolDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -34,7 +31,7 @@ pub struct KbMeteoraDammV1CreatePoolDecoded {
/// Decoded Meteora DAMM v1 swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbMeteoraDammV1SwapDecoded {
pub struct MeteoraDammV1SwapDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -44,7 +41,7 @@ pub struct KbMeteoraDammV1SwapDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side relative to normalized base.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Optional pool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional token A mint.
@@ -57,26 +54,26 @@ pub struct KbMeteoraDammV1SwapDecoded {
/// Decoded Meteora DAMM v1 event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbMeteoraDammV1DecodedEvent {
pub enum MeteoraDammV1DecodedEvent {
/// Pool creation.
CreatePool(KbMeteoraDammV1CreatePoolDecoded),
CreatePool(MeteoraDammV1CreatePoolDecoded),
/// Swap.
Swap(KbMeteoraDammV1SwapDecoded),
Swap(MeteoraDammV1SwapDecoded),
}
/// Meteora DAMM v1 decoder.
#[derive(Debug, Clone, Default)]
pub struct KbMeteoraDammV1Decoder;
pub struct MeteoraDammV1Decoder;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KbMeteoraDammV1InstructionKind {
enum MeteoraDammV1InstructionKind {
CreatePool,
CreatePoolWithConfig,
Swap,
Unknown,
}
impl KbMeteoraDammV1Decoder {
impl MeteoraDammV1Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -85,14 +82,14 @@ impl KbMeteoraDammV1Decoder {
/// Decodes one projected transaction into zero or more Meteora DAMM v1 events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbMeteoraDammV1DecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::MeteoraDammV1DecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -103,13 +100,13 @@ impl KbMeteoraDammV1Decoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let log_messages = extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -120,7 +117,7 @@ impl KbMeteoraDammV1Decoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_METEORA_DAMM_V1_PROGRAM_ID {
if program_id.as_str() != crate::METEORA_DAMM_V1_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -128,49 +125,47 @@ impl KbMeteoraDammV1Decoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
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 = 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),
};
let instruction_kind =
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
let instruction_kind = classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 0));
let token_a_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0", "coinMint"],
)
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 1));
let token_b_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1", "pcMint"],
)
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 2));
let config_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["config", "poolConfig", "ammConfig", "tradeFeeConfig"],
)
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 3));
let creator = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "payer", "user", "owner"],
)
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDammV1InstructionKind::CreatePool
|| instruction_kind == KbMeteoraDammV1InstructionKind::CreatePoolWithConfig
.or_else(|| return extract_account(&accounts, 4));
if instruction_kind == MeteoraDammV1InstructionKind::CreatePool
|| instruction_kind == MeteoraDammV1InstructionKind::CreatePoolWithConfig
{
let used_config =
instruction_kind == KbMeteoraDammV1InstructionKind::CreatePoolWithConfig;
instruction_kind == MeteoraDammV1InstructionKind::CreatePoolWithConfig;
let payload_json = serde_json::json!({
"decoder": "meteora_damm_v1",
"eventKind": "create_pool",
@@ -187,8 +182,8 @@ impl KbMeteoraDammV1Decoder {
"configAccount": config_account,
"creator": creator
});
decoded_events.push(crate::KbMeteoraDammV1DecodedEvent::CreatePool(
crate::KbMeteoraDammV1CreatePoolDecoded {
decoded_events.push(crate::MeteoraDammV1DecodedEvent::CreatePool(
crate::MeteoraDammV1CreatePoolDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -204,8 +199,8 @@ impl KbMeteoraDammV1Decoder {
));
continue;
}
if instruction_kind == KbMeteoraDammV1InstructionKind::Swap {
let trade_side = kb_infer_trade_side(&log_messages);
if instruction_kind == MeteoraDammV1InstructionKind::Swap {
let trade_side = infer_trade_side(&log_messages);
let payload_json = serde_json::json!({
"decoder": "meteora_damm_v1",
"eventKind": "swap",
@@ -221,8 +216,8 @@ impl KbMeteoraDammV1Decoder {
"tokenBMint": token_b_mint,
"tradeSide": format!("{:?}", trade_side)
});
decoded_events.push(crate::KbMeteoraDammV1DecodedEvent::Swap(
crate::KbMeteoraDammV1SwapDecoded {
decoded_events.push(crate::MeteoraDammV1DecodedEvent::Swap(
crate::MeteoraDammV1SwapDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -240,46 +235,46 @@ impl KbMeteoraDammV1Decoder {
}
}
fn kb_classify_instruction_kind(
fn classify_instruction_kind(
parsed_json: std::option::Option<&serde_json::Value>,
log_messages: &[std::string::String],
) -> KbMeteoraDammV1InstructionKind {
let parsed_instruction_name = kb_extract_string_by_candidate_keys(
) -> MeteoraDammV1InstructionKind {
let parsed_instruction_name = extract_string_by_candidate_keys(
parsed_json,
&["instruction", "instructionName", "type", "name"],
);
if let Some(parsed_instruction_name) = parsed_instruction_name {
let normalized = kb_normalize_text(parsed_instruction_name.as_str());
let normalized = normalize_text(parsed_instruction_name.as_str());
if normalized.contains("initializepoolwithconfig") {
return KbMeteoraDammV1InstructionKind::CreatePoolWithConfig;
return MeteoraDammV1InstructionKind::CreatePoolWithConfig;
}
if normalized.contains("initializepool") {
return KbMeteoraDammV1InstructionKind::CreatePool;
return MeteoraDammV1InstructionKind::CreatePool;
}
if normalized == "swap" {
return KbMeteoraDammV1InstructionKind::Swap;
return MeteoraDammV1InstructionKind::Swap;
}
}
if kb_value_contains_any_key(parsed_json, &["poolConfig", "ammConfig", "tradeFeeConfig"]) {
return KbMeteoraDammV1InstructionKind::CreatePoolWithConfig;
if value_contains_any_key(parsed_json, &["poolConfig", "ammConfig", "tradeFeeConfig"]) {
return MeteoraDammV1InstructionKind::CreatePoolWithConfig;
}
if kb_log_messages_contain_keyword(log_messages, "initialize_pool_with_config")
|| kb_log_messages_contain_keyword(log_messages, "initializepoolwithconfig")
if log_messages_contain_keyword(log_messages, "initialize_pool_with_config")
|| log_messages_contain_keyword(log_messages, "initializepoolwithconfig")
{
return KbMeteoraDammV1InstructionKind::CreatePoolWithConfig;
return MeteoraDammV1InstructionKind::CreatePoolWithConfig;
}
if kb_log_messages_contain_keyword(log_messages, "initialize_pool")
|| kb_log_messages_contain_keyword(log_messages, "initializepool")
if log_messages_contain_keyword(log_messages, "initialize_pool")
|| log_messages_contain_keyword(log_messages, "initializepool")
{
return KbMeteoraDammV1InstructionKind::CreatePool;
return MeteoraDammV1InstructionKind::CreatePool;
}
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbMeteoraDammV1InstructionKind::Swap;
if log_messages_contain_keyword(log_messages, "swap") {
return MeteoraDammV1InstructionKind::Swap;
}
return KbMeteoraDammV1InstructionKind::Unknown;
return MeteoraDammV1InstructionKind::Unknown;
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -307,10 +302,10 @@ fn kb_extract_log_messages(
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_text(keyword);
fn log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = normalize_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_text(log_message.as_str());
let log_normalized = normalize_text(log_message.as_str());
if log_normalized.contains(keyword_normalized.as_str()) {
return true;
}
@@ -318,7 +313,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
fn normalize_text(value: &str) -> std::string::String {
let mut normalized = std::string::String::new();
for character in value.chars() {
if character.is_ascii_alphanumeric() {
@@ -328,14 +323,14 @@ fn kb_normalize_text(value: &str) -> std::string::String {
return normalized;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -351,9 +346,9 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
fn parse_optional_parsed_json(
parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::KbError> {
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
let parsed_json = match parsed_json {
Some(parsed_json) => parsed_json,
None => return Ok(None),
@@ -362,7 +357,7 @@ fn kb_parse_optional_parsed_json(
match value_result {
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
@@ -370,7 +365,7 @@ fn kb_parse_optional_parsed_json(
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -378,10 +373,10 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
return extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
fn extract_string_by_candidate_keys_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -397,7 +392,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
for nested_value in object.values() {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -407,7 +402,7 @@ fn kb_extract_string_by_candidate_keys_inner(
if let Some(array) = value.as_array() {
for nested_value in array {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -416,7 +411,7 @@ fn kb_extract_string_by_candidate_keys_inner(
return None;
}
fn kb_value_contains_any_key(
fn value_contains_any_key(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> bool {
@@ -424,10 +419,10 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
return kb_value_contains_any_key_inner(value, candidate_keys);
return value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
fn 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) {
@@ -435,7 +430,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
for nested_value in object.values() {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -443,7 +438,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
if let Some(array) = value.as_array() {
for nested_value in array {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -451,7 +446,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
return false;
}
fn kb_extract_account(
fn extract_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
@@ -461,20 +456,20 @@ fn kb_extract_account(
return Some(accounts[index].clone());
}
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;
fn infer_trade_side(log_messages: &[std::string::String]) -> crate::SwapTradeSide {
if log_messages_contain_keyword(log_messages, "buy") {
return crate::SwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
if log_messages_contain_keyword(log_messages, "sell") {
return crate::SwapTradeSide::SellBase;
}
return crate::KbSwapTradeSide::Unknown;
return crate::SwapTradeSide::Unknown;
}
#[cfg(test)]
mod tests {
fn make_create_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_create_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-meteora-damm-v1-create-1".to_string(),
Some(890001),
Some(1779500001),
@@ -501,19 +496,19 @@ mod tests {
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_create_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
501,
None,
0,
None,
Some(crate::KB_METEORA_DAMM_V1_PROGRAM_ID.to_string()),
Some(crate::METEORA_DAMM_V1_PROGRAM_ID.to_string()),
Some("meteora-damm-v1".to_string()),
Some(1),
serde_json::json!([
"DammV1Pool111",
"DammV1TokenA111",
"So11111111111111111111111111111111111111112",
crate::WSOL_MINT_ID,
"DammV1Config111",
"DammV1Creator111"
])
@@ -526,7 +521,7 @@ mod tests {
"instruction": "initialize_pool_with_config",
"pool": "DammV1Pool111",
"tokenAMint": "DammV1TokenA111",
"tokenBMint": "So11111111111111111111111111111111111111112",
"tokenBMint": crate::WSOL_MINT_ID,
"config": "DammV1Config111",
"creator": "DammV1Creator111"
}
@@ -538,8 +533,8 @@ mod tests {
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_swap_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-meteora-damm-v1-swap-1".to_string(),
Some(890002),
Some(1779500002),
@@ -566,21 +561,17 @@ mod tests {
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_swap_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
503,
None,
0,
None,
Some(crate::KB_METEORA_DAMM_V1_PROGRAM_ID.to_string()),
Some(crate::METEORA_DAMM_V1_PROGRAM_ID.to_string()),
Some("meteora-damm-v1".to_string()),
Some(1),
serde_json::json!([
"DammV1SwapPool111",
"DammV1SwapTokenA111",
"So11111111111111111111111111111111111111112"
])
.to_string(),
serde_json::json!(["DammV1SwapPool111", "DammV1SwapTokenA111", crate::WSOL_MINT_ID])
.to_string(),
None,
None,
Some(
@@ -589,7 +580,7 @@ mod tests {
"instruction": "swap",
"pool": "DammV1SwapPool111",
"tokenAMint": "DammV1SwapTokenA111",
"tokenBMint": "So11111111111111111111111111111111111111112"
"tokenBMint": crate::WSOL_MINT_ID
}
})
.to_string(),
@@ -601,7 +592,7 @@ mod tests {
#[test]
fn meteora_damm_v1_create_pool_is_detected() {
let decoder = crate::KbMeteoraDammV1Decoder::new();
let decoder = crate::MeteoraDammV1Decoder::new();
let transaction = make_create_transaction();
let instructions = vec![make_create_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -611,18 +602,15 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDammV1DecodedEvent::CreatePool(event) => {
crate::MeteoraDammV1DecodedEvent::CreatePool(event) => {
assert_eq!(event.transaction_id, 501);
assert_eq!(event.instruction_id, 502);
assert_eq!(event.pool_account, Some("DammV1Pool111".to_string()));
assert_eq!(event.token_a_mint, Some("DammV1TokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
assert!(event.used_config);
},
crate::KbMeteoraDammV1DecodedEvent::Swap(_) => {
crate::MeteoraDammV1DecodedEvent::Swap(_) => {
panic!("unexpected swap event")
},
}
@@ -630,7 +618,7 @@ mod tests {
#[test]
fn meteora_damm_v1_swap_is_detected() {
let decoder = crate::KbMeteoraDammV1Decoder::new();
let decoder = crate::MeteoraDammV1Decoder::new();
let transaction = make_swap_transaction();
let instructions = vec![make_swap_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -640,17 +628,14 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDammV1DecodedEvent::Swap(event) => {
crate::MeteoraDammV1DecodedEvent::Swap(event) => {
assert_eq!(event.transaction_id, 503);
assert_eq!(event.instruction_id, 504);
assert_eq!(event.pool_account, Some("DammV1SwapPool111".to_string()));
assert_eq!(event.token_a_mint, Some("DammV1SwapTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
},
crate::KbMeteoraDammV1DecodedEvent::CreatePool(_) => {
crate::MeteoraDammV1DecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
},
}

View File

@@ -2,12 +2,9 @@
//! Meteora DAMM v2 transaction decoder.
/// Meteora DAMM v2 program id.
pub const KB_METEORA_DAMM_V2_PROGRAM_ID: &str = "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG";
/// Decoded Meteora DAMM v2 create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbMeteoraDammV2CreatePoolDecoded {
pub struct MeteoraDammV2CreatePoolDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -34,7 +31,7 @@ pub struct KbMeteoraDammV2CreatePoolDecoded {
/// Decoded Meteora DAMM v2 swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbMeteoraDammV2SwapDecoded {
pub struct MeteoraDammV2SwapDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -44,7 +41,7 @@ pub struct KbMeteoraDammV2SwapDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side relative to normalized base.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Optional pool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional token A mint.
@@ -59,19 +56,19 @@ pub struct KbMeteoraDammV2SwapDecoded {
/// Decoded Meteora DAMM v2 event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbMeteoraDammV2DecodedEvent {
pub enum MeteoraDammV2DecodedEvent {
/// Pool creation.
CreatePool(KbMeteoraDammV2CreatePoolDecoded),
CreatePool(MeteoraDammV2CreatePoolDecoded),
/// Swap / swap2.
Swap(KbMeteoraDammV2SwapDecoded),
Swap(MeteoraDammV2SwapDecoded),
}
/// Meteora DAMM v2 decoder.
#[derive(Debug, Clone, Default)]
pub struct KbMeteoraDammV2Decoder;
pub struct MeteoraDammV2Decoder;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KbMeteoraDammV2InstructionKind {
enum MeteoraDammV2InstructionKind {
CreatePoolStatic,
CreatePoolDynamic,
CreatePoolCustomizable,
@@ -79,7 +76,7 @@ enum KbMeteoraDammV2InstructionKind {
Unknown,
}
impl KbMeteoraDammV2Decoder {
impl MeteoraDammV2Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -88,14 +85,14 @@ impl KbMeteoraDammV2Decoder {
/// Decodes one projected transaction into zero or more Meteora DAMM v2 events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbMeteoraDammV2DecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::MeteoraDammV2DecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -106,13 +103,13 @@ impl KbMeteoraDammV2Decoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let log_messages = extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -123,7 +120,7 @@ impl KbMeteoraDammV2Decoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_METEORA_DAMM_V2_PROGRAM_ID {
if program_id.as_str() != crate::METEORA_DAMM_V2_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -131,52 +128,50 @@ impl KbMeteoraDammV2Decoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
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 = 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),
};
let instruction_kind =
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
let instruction_kind = classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["pool", "poolAddress", "poolAccount", "poolState", "cpAmm"],
)
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 0));
let token_a_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 1));
let token_b_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 2));
let config_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["staticConfig", "dynamicConfig", "config", "poolConfig"],
)
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 3));
let creator = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "payer", "user", "owner"],
)
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolStatic
|| instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolDynamic
|| instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolCustomizable
.or_else(|| return extract_account(&accounts, 4));
if instruction_kind == MeteoraDammV2InstructionKind::CreatePoolStatic
|| instruction_kind == MeteoraDammV2InstructionKind::CreatePoolDynamic
|| instruction_kind == MeteoraDammV2InstructionKind::CreatePoolCustomizable
{
let create_kind = match instruction_kind {
KbMeteoraDammV2InstructionKind::CreatePoolStatic => "static".to_string(),
KbMeteoraDammV2InstructionKind::CreatePoolDynamic => "dynamic".to_string(),
KbMeteoraDammV2InstructionKind::CreatePoolCustomizable => {
MeteoraDammV2InstructionKind::CreatePoolStatic => "static".to_string(),
MeteoraDammV2InstructionKind::CreatePoolDynamic => "dynamic".to_string(),
MeteoraDammV2InstructionKind::CreatePoolCustomizable => {
"customizable".to_string()
},
_ => "unknown".to_string(),
@@ -197,8 +192,8 @@ impl KbMeteoraDammV2Decoder {
"configAccount": config_account,
"creator": creator
});
decoded_events.push(crate::KbMeteoraDammV2DecodedEvent::CreatePool(
crate::KbMeteoraDammV2CreatePoolDecoded {
decoded_events.push(crate::MeteoraDammV2DecodedEvent::CreatePool(
crate::MeteoraDammV2CreatePoolDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -214,10 +209,10 @@ impl KbMeteoraDammV2Decoder {
));
continue;
}
if instruction_kind == KbMeteoraDammV2InstructionKind::Swap {
let used_swap2 = kb_log_messages_contain_keyword(&log_messages, "swap2")
|| kb_value_contains_any_key(parsed_json.as_ref(), &["swap2", "isSwap2"]);
let trade_side = kb_infer_trade_side(&log_messages);
if instruction_kind == MeteoraDammV2InstructionKind::Swap {
let used_swap2 = log_messages_contain_keyword(&log_messages, "swap2")
|| value_contains_any_key(parsed_json.as_ref(), &["swap2", "isSwap2"]);
let trade_side = infer_trade_side(&log_messages);
let payload_json = serde_json::json!({
"decoder": "meteora_damm_v2",
"eventKind": "swap",
@@ -233,8 +228,8 @@ impl KbMeteoraDammV2Decoder {
"tokenBMint": token_b_mint,
"tradeSide": format!("{:?}", trade_side)
});
decoded_events.push(crate::KbMeteoraDammV2DecodedEvent::Swap(
crate::KbMeteoraDammV2SwapDecoded {
decoded_events.push(crate::MeteoraDammV2DecodedEvent::Swap(
crate::MeteoraDammV2SwapDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -253,7 +248,7 @@ impl KbMeteoraDammV2Decoder {
}
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -281,65 +276,65 @@ fn kb_extract_log_messages(
return messages;
}
fn kb_classify_instruction_kind(
fn classify_instruction_kind(
parsed_json: std::option::Option<&serde_json::Value>,
log_messages: &[std::string::String],
) -> KbMeteoraDammV2InstructionKind {
let parsed_instruction_name = kb_extract_string_by_candidate_keys(
) -> MeteoraDammV2InstructionKind {
let parsed_instruction_name = extract_string_by_candidate_keys(
parsed_json,
&["instruction", "instructionName", "type", "name"],
);
if let Some(parsed_instruction_name) = parsed_instruction_name {
let normalized = kb_normalize_text(parsed_instruction_name.as_str());
let normalized = normalize_text(parsed_instruction_name.as_str());
if normalized.contains("initializepoolwithdynamicconfig") {
return KbMeteoraDammV2InstructionKind::CreatePoolDynamic;
return MeteoraDammV2InstructionKind::CreatePoolDynamic;
}
if normalized.contains("initializecustomizablepool") {
return KbMeteoraDammV2InstructionKind::CreatePoolCustomizable;
return MeteoraDammV2InstructionKind::CreatePoolCustomizable;
}
if normalized.contains("initializepool") {
return KbMeteoraDammV2InstructionKind::CreatePoolStatic;
return MeteoraDammV2InstructionKind::CreatePoolStatic;
}
if normalized == "swap" || normalized == "swap2" {
return KbMeteoraDammV2InstructionKind::Swap;
return MeteoraDammV2InstructionKind::Swap;
}
}
if kb_value_contains_any_key(parsed_json, &["dynamicConfig"]) {
return KbMeteoraDammV2InstructionKind::CreatePoolDynamic;
if value_contains_any_key(parsed_json, &["dynamicConfig"]) {
return MeteoraDammV2InstructionKind::CreatePoolDynamic;
}
if kb_value_contains_any_key(parsed_json, &["staticConfig"]) {
return KbMeteoraDammV2InstructionKind::CreatePoolStatic;
if value_contains_any_key(parsed_json, &["staticConfig"]) {
return MeteoraDammV2InstructionKind::CreatePoolStatic;
}
if kb_value_contains_any_key(parsed_json, &["poolActivationPoint", "isCustomizablePool"]) {
return KbMeteoraDammV2InstructionKind::CreatePoolCustomizable;
if value_contains_any_key(parsed_json, &["poolActivationPoint", "isCustomizablePool"]) {
return MeteoraDammV2InstructionKind::CreatePoolCustomizable;
}
if kb_log_messages_contain_keyword(log_messages, "initialize_pool_with_dynamic_config")
|| kb_log_messages_contain_keyword(log_messages, "initializepoolwithdynamicconfig")
if log_messages_contain_keyword(log_messages, "initialize_pool_with_dynamic_config")
|| log_messages_contain_keyword(log_messages, "initializepoolwithdynamicconfig")
{
return KbMeteoraDammV2InstructionKind::CreatePoolDynamic;
return MeteoraDammV2InstructionKind::CreatePoolDynamic;
}
if kb_log_messages_contain_keyword(log_messages, "initialize_customizable_pool")
|| kb_log_messages_contain_keyword(log_messages, "initializecustomizablepool")
if log_messages_contain_keyword(log_messages, "initialize_customizable_pool")
|| log_messages_contain_keyword(log_messages, "initializecustomizablepool")
{
return KbMeteoraDammV2InstructionKind::CreatePoolCustomizable;
return MeteoraDammV2InstructionKind::CreatePoolCustomizable;
}
if kb_log_messages_contain_keyword(log_messages, "initialize_pool")
|| kb_log_messages_contain_keyword(log_messages, "initializepool")
if log_messages_contain_keyword(log_messages, "initialize_pool")
|| log_messages_contain_keyword(log_messages, "initializepool")
{
return KbMeteoraDammV2InstructionKind::CreatePoolStatic;
return MeteoraDammV2InstructionKind::CreatePoolStatic;
}
if kb_log_messages_contain_keyword(log_messages, "swap2")
|| kb_log_messages_contain_keyword(log_messages, "swap")
if log_messages_contain_keyword(log_messages, "swap2")
|| log_messages_contain_keyword(log_messages, "swap")
{
return KbMeteoraDammV2InstructionKind::Swap;
return MeteoraDammV2InstructionKind::Swap;
}
return KbMeteoraDammV2InstructionKind::Unknown;
return MeteoraDammV2InstructionKind::Unknown;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_text(keyword);
fn log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = normalize_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_text(log_message.as_str());
let log_normalized = normalize_text(log_message.as_str());
if log_normalized.contains(keyword_normalized.as_str()) {
return true;
}
@@ -347,9 +342,9 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
fn normalize_text(normalize_text: &str) -> std::string::String {
let mut normalized = std::string::String::new();
for character in value.chars() {
for character in normalize_text.chars() {
if character.is_ascii_alphanumeric() {
normalized.push(character.to_ascii_lowercase());
}
@@ -357,14 +352,14 @@ fn kb_normalize_text(value: &str) -> std::string::String {
return normalized;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -380,9 +375,9 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
fn parse_optional_parsed_json(
parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::KbError> {
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
let parsed_json = match parsed_json {
Some(parsed_json) => parsed_json,
None => return Ok(None),
@@ -391,7 +386,7 @@ fn kb_parse_optional_parsed_json(
match value_result {
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
@@ -399,7 +394,7 @@ fn kb_parse_optional_parsed_json(
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -407,10 +402,10 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
return extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
fn extract_string_by_candidate_keys_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -426,7 +421,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
for nested_value in object.values() {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -436,7 +431,7 @@ fn kb_extract_string_by_candidate_keys_inner(
if let Some(array) = value.as_array() {
for nested_value in array {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -445,7 +440,7 @@ fn kb_extract_string_by_candidate_keys_inner(
return None;
}
fn kb_value_contains_any_key(
fn value_contains_any_key(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> bool {
@@ -453,10 +448,10 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
return kb_value_contains_any_key_inner(value, candidate_keys);
return value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
fn 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) {
@@ -464,7 +459,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
for nested_value in object.values() {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -472,7 +467,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
if let Some(array) = value.as_array() {
for nested_value in array {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -480,7 +475,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
return false;
}
fn kb_extract_account(
fn extract_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
@@ -490,20 +485,20 @@ fn kb_extract_account(
return Some(accounts[index].clone());
}
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;
fn infer_trade_side(log_messages: &[std::string::String]) -> crate::SwapTradeSide {
if log_messages_contain_keyword(log_messages, "buy") {
return crate::SwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
if log_messages_contain_keyword(log_messages, "sell") {
return crate::SwapTradeSide::SellBase;
}
return crate::KbSwapTradeSide::Unknown;
return crate::SwapTradeSide::Unknown;
}
#[cfg(test)]
mod tests {
fn make_create_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_create_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-meteora-damm-v2-create-1".to_string(),
Some(889001),
Some(1779400001),
@@ -530,19 +525,19 @@ mod tests {
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_create_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
401,
None,
0,
None,
Some(crate::KB_METEORA_DAMM_V2_PROGRAM_ID.to_string()),
Some(crate::METEORA_DAMM_V2_PROGRAM_ID.to_string()),
Some("meteora-damm-v2".to_string()),
Some(1),
serde_json::json!([
"DammV2Pool111",
"DammV2TokenA111",
"So11111111111111111111111111111111111111112",
crate::WSOL_MINT_ID,
"DammV2Config111",
"DammV2Creator111"
])
@@ -555,7 +550,7 @@ mod tests {
"instruction": "initialize_customizable_pool",
"pool": "DammV2Pool111",
"tokenAMint": "DammV2TokenA111",
"tokenBMint": "So11111111111111111111111111111111111111112",
"tokenBMint": crate::WSOL_MINT_ID,
"creator": "DammV2Creator111",
"isCustomizablePool": true
}
@@ -567,8 +562,8 @@ mod tests {
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_swap_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-meteora-damm-v2-swap-1".to_string(),
Some(889002),
Some(1779400002),
@@ -595,21 +590,17 @@ mod tests {
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_swap_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
403,
None,
0,
None,
Some(crate::KB_METEORA_DAMM_V2_PROGRAM_ID.to_string()),
Some(crate::METEORA_DAMM_V2_PROGRAM_ID.to_string()),
Some("meteora-damm-v2".to_string()),
Some(1),
serde_json::json!([
"DammV2SwapPool111",
"DammV2SwapTokenA111",
"So11111111111111111111111111111111111111112"
])
.to_string(),
serde_json::json!(["DammV2SwapPool111", "DammV2SwapTokenA111", crate::WSOL_MINT_ID])
.to_string(),
None,
None,
Some(
@@ -618,7 +609,7 @@ mod tests {
"instruction": "swap2",
"pool": "DammV2SwapPool111",
"tokenAMint": "DammV2SwapTokenA111",
"tokenBMint": "So11111111111111111111111111111111111111112"
"tokenBMint": crate::WSOL_MINT_ID
}
})
.to_string(),
@@ -630,7 +621,7 @@ mod tests {
#[test]
fn meteora_damm_v2_create_pool_is_detected() {
let decoder = crate::KbMeteoraDammV2Decoder::new();
let decoder = crate::MeteoraDammV2Decoder::new();
let transaction = make_create_transaction();
let instructions = vec![make_create_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -640,18 +631,15 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDammV2DecodedEvent::CreatePool(event) => {
crate::MeteoraDammV2DecodedEvent::CreatePool(event) => {
assert_eq!(event.transaction_id, 401);
assert_eq!(event.instruction_id, 402);
assert_eq!(event.pool_account, Some("DammV2Pool111".to_string()));
assert_eq!(event.token_a_mint, Some("DammV2TokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
assert_eq!(event.create_kind, "customizable".to_string());
},
crate::KbMeteoraDammV2DecodedEvent::Swap(_) => {
crate::MeteoraDammV2DecodedEvent::Swap(_) => {
panic!("unexpected swap event")
},
}
@@ -659,7 +647,7 @@ mod tests {
#[test]
fn meteora_damm_v2_swap_is_detected() {
let decoder = crate::KbMeteoraDammV2Decoder::new();
let decoder = crate::MeteoraDammV2Decoder::new();
let transaction = make_swap_transaction();
let instructions = vec![make_swap_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -669,18 +657,15 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDammV2DecodedEvent::Swap(event) => {
crate::MeteoraDammV2DecodedEvent::Swap(event) => {
assert_eq!(event.transaction_id, 403);
assert_eq!(event.instruction_id, 404);
assert_eq!(event.pool_account, Some("DammV2SwapPool111".to_string()));
assert_eq!(event.token_a_mint, Some("DammV2SwapTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
assert!(event.used_swap2);
},
crate::KbMeteoraDammV2DecodedEvent::CreatePool(_) => {
crate::MeteoraDammV2DecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
},
}

View File

@@ -2,12 +2,9 @@
//! Meteora Dynamic Bonding Curve (DBC) transaction decoder.
/// Meteora DBC program id.
pub const KB_METEORA_DBC_PROGRAM_ID: &str = "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN";
/// Decoded Meteora DBC create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbMeteoraDbcCreatePoolDecoded {
pub struct MeteoraDbcCreatePoolDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -32,7 +29,7 @@ pub struct KbMeteoraDbcCreatePoolDecoded {
/// Decoded Meteora DBC swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbMeteoraDbcSwapDecoded {
pub struct MeteoraDbcSwapDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -42,7 +39,7 @@ pub struct KbMeteoraDbcSwapDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side relative to normalized base.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Optional pool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional base mint.
@@ -55,15 +52,15 @@ pub struct KbMeteoraDbcSwapDecoded {
/// Decoded Meteora DBC event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbMeteoraDbcDecodedEvent {
pub enum MeteoraDbcDecodedEvent {
/// Create pool / launch pool.
CreatePool(KbMeteoraDbcCreatePoolDecoded),
CreatePool(MeteoraDbcCreatePoolDecoded),
/// Swap / swap2.
Swap(KbMeteoraDbcSwapDecoded),
Swap(MeteoraDbcSwapDecoded),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KbMeteoraDbcInstructionKind {
enum MeteoraDbcInstructionKind {
CreatePool,
Swap,
Unknown,
@@ -71,9 +68,9 @@ enum KbMeteoraDbcInstructionKind {
/// Meteora DBC decoder.
#[derive(Debug, Clone, Default)]
pub struct KbMeteoraDbcDecoder;
pub struct MeteoraDbcDecoder;
impl KbMeteoraDbcDecoder {
impl MeteoraDbcDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -82,14 +79,14 @@ impl KbMeteoraDbcDecoder {
/// Decodes one projected transaction into zero or more Meteora DBC events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbMeteoraDbcDecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::MeteoraDbcDecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -100,13 +97,13 @@ impl KbMeteoraDbcDecoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let log_messages = extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -117,7 +114,7 @@ impl KbMeteoraDbcDecoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_METEORA_DBC_PROGRAM_ID {
if program_id.as_str() != crate::METEORA_DBC_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -125,45 +122,43 @@ impl KbMeteoraDbcDecoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
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 = 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),
};
let instruction_kind =
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
let instruction_kind = classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["pool", "poolAccount", "poolState", "virtualPool", "poolKey"],
)
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 0));
let token_a_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["baseMint", "tokenAMint", "mintA", "token0Mint", "mint0"],
)
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 1));
let token_b_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["quoteMint", "tokenBMint", "mintB", "token1Mint", "mint1"],
)
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 2));
let config_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["poolConfig", "config", "dbcConfig", "curveConfig"],
)
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 3));
let creator = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "poolCreator", "owner", "user"],
)
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDbcInstructionKind::CreatePool {
.or_else(|| return extract_account(&accounts, 4));
if instruction_kind == MeteoraDbcInstructionKind::CreatePool {
let payload_json = serde_json::json!({
"decoder": "meteora_dbc",
"eventKind": "create_pool",
@@ -180,8 +175,8 @@ impl KbMeteoraDbcDecoder {
"configAccount": config_account,
"creator": creator
});
decoded_events.push(crate::KbMeteoraDbcDecodedEvent::CreatePool(
crate::KbMeteoraDbcCreatePoolDecoded {
decoded_events.push(crate::MeteoraDbcDecodedEvent::CreatePool(
crate::MeteoraDbcCreatePoolDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -196,8 +191,8 @@ impl KbMeteoraDbcDecoder {
));
continue;
}
if instruction_kind == KbMeteoraDbcInstructionKind::Swap {
let trade_side = kb_infer_trade_side(&log_messages);
if instruction_kind == MeteoraDbcInstructionKind::Swap {
let trade_side = infer_trade_side(&log_messages);
let payload_json = serde_json::json!({
"decoder": "meteora_dbc",
"eventKind": "swap",
@@ -213,8 +208,8 @@ impl KbMeteoraDbcDecoder {
"tokenBMint": token_b_mint,
"tradeSide": format!("{:?}", trade_side)
});
decoded_events.push(crate::KbMeteoraDbcDecodedEvent::Swap(
crate::KbMeteoraDbcSwapDecoded {
decoded_events.push(crate::MeteoraDbcDecodedEvent::Swap(
crate::MeteoraDbcSwapDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -232,7 +227,7 @@ impl KbMeteoraDbcDecoder {
}
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -260,22 +255,22 @@ fn kb_extract_log_messages(
return messages;
}
fn kb_log_messages_contain_any_keyword(
fn log_messages_contain_any_keyword(
log_messages: &[std::string::String],
keywords: &[&str],
) -> bool {
for keyword in keywords {
if kb_log_messages_contain_keyword(log_messages, keyword) {
if log_messages_contain_keyword(log_messages, keyword) {
return true;
}
}
return false;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_log_text(keyword);
fn log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = normalize_log_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_log_text(log_message.as_str());
let log_normalized = normalize_log_text(log_message.as_str());
if log_normalized.contains(keyword_normalized.as_str()) {
return true;
}
@@ -283,7 +278,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return false;
}
fn kb_normalize_log_text(value: &str) -> std::string::String {
fn normalize_log_text(value: &str) -> std::string::String {
let mut normalized = std::string::String::new();
for character in value.chars() {
if character.is_ascii_alphanumeric() {
@@ -293,14 +288,14 @@ fn kb_normalize_log_text(value: &str) -> std::string::String {
return normalized;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -316,9 +311,9 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
fn parse_optional_parsed_json(
parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::KbError> {
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
let parsed_json = match parsed_json {
Some(parsed_json) => parsed_json,
None => return Ok(None),
@@ -327,7 +322,7 @@ fn kb_parse_optional_parsed_json(
match value_result {
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
@@ -335,7 +330,7 @@ fn kb_parse_optional_parsed_json(
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -343,10 +338,10 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
return extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
fn extract_string_by_candidate_keys_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -362,7 +357,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
for nested_value in object.values() {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -372,7 +367,7 @@ fn kb_extract_string_by_candidate_keys_inner(
if let Some(array) = value.as_array() {
for nested_value in array {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -381,7 +376,7 @@ fn kb_extract_string_by_candidate_keys_inner(
return None;
}
fn kb_value_contains_any_key(
fn value_contains_any_key(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> bool {
@@ -389,10 +384,10 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
return kb_value_contains_any_key_inner(value, candidate_keys);
return value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
fn 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) {
@@ -400,7 +395,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
for nested_value in object.values() {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -408,7 +403,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
if let Some(array) = value.as_array() {
for nested_value in array {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -416,7 +411,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
return false;
}
fn kb_extract_account(
fn extract_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
@@ -426,59 +421,59 @@ fn kb_extract_account(
return Some(accounts[index].clone());
}
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;
fn infer_trade_side(log_messages: &[std::string::String]) -> crate::SwapTradeSide {
if log_messages_contain_keyword(log_messages, "buy") {
return crate::SwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
if log_messages_contain_keyword(log_messages, "sell") {
return crate::SwapTradeSide::SellBase;
}
return crate::KbSwapTradeSide::Unknown;
return crate::SwapTradeSide::Unknown;
}
fn kb_classify_instruction_kind(
fn classify_instruction_kind(
parsed_json: std::option::Option<&serde_json::Value>,
log_messages: &[std::string::String],
) -> KbMeteoraDbcInstructionKind {
let parsed_instruction_name = kb_extract_string_by_candidate_keys(
) -> MeteoraDbcInstructionKind {
let parsed_instruction_name = extract_string_by_candidate_keys(
parsed_json,
&["instruction", "instructionName", "type", "name"],
);
if let Some(parsed_instruction_name) = parsed_instruction_name {
let normalized = kb_normalize_log_text(parsed_instruction_name.as_str());
let normalized = normalize_log_text(parsed_instruction_name.as_str());
if normalized.contains("createpool")
|| normalized.contains("initializepool")
|| normalized.contains("launchpool")
{
return KbMeteoraDbcInstructionKind::CreatePool;
return MeteoraDbcInstructionKind::CreatePool;
}
if normalized == "swap" || normalized == "swap2" {
return KbMeteoraDbcInstructionKind::Swap;
return MeteoraDbcInstructionKind::Swap;
}
}
let has_create_config = kb_value_contains_any_key(
let has_create_config = value_contains_any_key(
parsed_json,
&["poolConfig", "migrationQuoteThreshold", "curveConfig", "dbcConfig"],
);
if has_create_config {
return KbMeteoraDbcInstructionKind::CreatePool;
return MeteoraDbcInstructionKind::CreatePool;
}
if kb_log_messages_contain_any_keyword(
if log_messages_contain_any_keyword(
log_messages,
&["create_pool", "createpool", "initialize_pool", "initializepool", "launch_pool"],
) {
return KbMeteoraDbcInstructionKind::CreatePool;
return MeteoraDbcInstructionKind::CreatePool;
}
if kb_log_messages_contain_any_keyword(log_messages, &["swap2", "swap"]) {
return KbMeteoraDbcInstructionKind::Swap;
if log_messages_contain_any_keyword(log_messages, &["swap2", "swap"]) {
return MeteoraDbcInstructionKind::Swap;
}
return KbMeteoraDbcInstructionKind::Unknown;
return MeteoraDbcInstructionKind::Unknown;
}
#[cfg(test)]
mod tests {
fn make_create_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_create_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-meteora-dbc-create-1".to_string(),
Some(888001),
Some(1779300001),
@@ -505,19 +500,19 @@ mod tests {
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_create_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
301,
None,
0,
None,
Some(crate::KB_METEORA_DBC_PROGRAM_ID.to_string()),
Some(crate::METEORA_DBC_PROGRAM_ID.to_string()),
Some("meteora-dbc".to_string()),
Some(1),
serde_json::json!([
"DbcPool111",
"DbcTokenA111",
"So11111111111111111111111111111111111111112",
crate::WSOL_MINT_ID,
"DbcConfig111",
"DbcCreator111"
])
@@ -529,7 +524,7 @@ mod tests {
"info": {
"pool": "DbcPool111",
"baseMint": "DbcTokenA111",
"quoteMint": "So11111111111111111111111111111111111111112",
"quoteMint": crate::WSOL_MINT_ID,
"poolConfig": "DbcConfig111",
"creator": "DbcCreator111"
}
@@ -541,8 +536,8 @@ mod tests {
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_swap_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-meteora-dbc-swap-1".to_string(),
Some(888002),
Some(1779300002),
@@ -569,21 +564,17 @@ mod tests {
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_swap_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
303,
None,
0,
None,
Some(crate::KB_METEORA_DBC_PROGRAM_ID.to_string()),
Some(crate::METEORA_DBC_PROGRAM_ID.to_string()),
Some("meteora-dbc".to_string()),
Some(1),
serde_json::json!([
"DbcPoolSwap111",
"DbcSwapTokenA111",
"So11111111111111111111111111111111111111112"
])
.to_string(),
serde_json::json!(["DbcPoolSwap111", "DbcSwapTokenA111", crate::WSOL_MINT_ID])
.to_string(),
None,
None,
Some(
@@ -591,7 +582,7 @@ mod tests {
"info": {
"pool": "DbcPoolSwap111",
"baseMint": "DbcSwapTokenA111",
"quoteMint": "So11111111111111111111111111111111111111112"
"quoteMint": crate::WSOL_MINT_ID
}
})
.to_string(),
@@ -603,7 +594,7 @@ mod tests {
#[test]
fn meteora_dbc_create_pool_is_detected() {
let decoder = crate::KbMeteoraDbcDecoder::new();
let decoder = crate::MeteoraDbcDecoder::new();
let transaction = make_create_transaction();
let instructions = vec![make_create_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -613,18 +604,15 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::CreatePool(event) => {
crate::MeteoraDbcDecodedEvent::CreatePool(event) => {
assert_eq!(event.transaction_id, 301);
assert_eq!(event.instruction_id, 302);
assert_eq!(event.pool_account, Some("DbcPool111".to_string()));
assert_eq!(event.token_a_mint, Some("DbcTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
assert_eq!(event.config_account, Some("DbcConfig111".to_string()));
},
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {
crate::MeteoraDbcDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
},
}
@@ -632,7 +620,7 @@ mod tests {
#[test]
fn meteora_dbc_swap_is_detected() {
let decoder = crate::KbMeteoraDbcDecoder::new();
let decoder = crate::MeteoraDbcDecoder::new();
let transaction = make_swap_transaction();
let instructions = vec![make_swap_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -642,17 +630,14 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::Swap(event) => {
crate::MeteoraDbcDecodedEvent::Swap(event) => {
assert_eq!(event.transaction_id, 303);
assert_eq!(event.instruction_id, 304);
assert_eq!(event.pool_account, Some("DbcPoolSwap111".to_string()));
assert_eq!(event.token_a_mint, Some("DbcSwapTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
},
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {
crate::MeteoraDbcDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
},
}
@@ -660,13 +645,13 @@ mod tests {
#[test]
fn meteora_dbc_quote_mint_alone_does_not_trigger_create_pool() {
let decoder = crate::KbMeteoraDbcDecoder::new();
let decoder = crate::MeteoraDbcDecoder::new();
let transaction = make_swap_transaction();
let mut instruction = make_swap_instruction();
instruction.parsed_json = Some(
serde_json::json!({
"info": {
"quoteMint": "So11111111111111111111111111111111111111112"
"quoteMint": crate::WSOL_MINT_ID
}
})
.to_string(),
@@ -678,8 +663,8 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {},
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {
crate::MeteoraDbcDecodedEvent::Swap(_) => {},
crate::MeteoraDbcDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
},
}
@@ -687,7 +672,7 @@ mod tests {
#[test]
fn meteora_dbc_pool_config_triggers_create_pool() {
let decoder = crate::KbMeteoraDbcDecoder::new();
let decoder = crate::MeteoraDbcDecoder::new();
let transaction = make_create_transaction();
let instruction = make_create_instruction();
let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
@@ -697,8 +682,8 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {},
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {
crate::MeteoraDbcDecodedEvent::CreatePool(_) => {},
crate::MeteoraDbcDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
},
}

View File

@@ -2,12 +2,9 @@
//! Orca Whirlpools transaction decoder.
/// Orca Whirlpools program id.
pub const KB_ORCA_WHIRLPOOLS_PROGRAM_ID: &str = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
/// Decoded Orca Whirlpools create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbOrcaWhirlpoolsCreatePoolDecoded {
pub struct OrcaWhirlpoolsCreatePoolDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -34,7 +31,7 @@ pub struct KbOrcaWhirlpoolsCreatePoolDecoded {
/// Decoded Orca Whirlpools swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbOrcaWhirlpoolsSwapDecoded {
pub struct OrcaWhirlpoolsSwapDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -44,7 +41,7 @@ pub struct KbOrcaWhirlpoolsSwapDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side relative to normalized base.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Optional whirlpool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional token A mint.
@@ -59,19 +56,19 @@ pub struct KbOrcaWhirlpoolsSwapDecoded {
/// Decoded Orca Whirlpools event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbOrcaWhirlpoolsDecodedEvent {
pub enum OrcaWhirlpoolsDecodedEvent {
/// Pool creation.
CreatePool(KbOrcaWhirlpoolsCreatePoolDecoded),
CreatePool(OrcaWhirlpoolsCreatePoolDecoded),
/// Swap / swap_v2.
Swap(KbOrcaWhirlpoolsSwapDecoded),
Swap(OrcaWhirlpoolsSwapDecoded),
}
/// Orca Whirlpools decoder.
#[derive(Debug, Clone, Default)]
pub struct KbOrcaWhirlpoolsDecoder;
pub struct OrcaWhirlpoolsDecoder;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KbOrcaWhirlpoolsInstructionKind {
enum OrcaWhirlpoolsInstructionKind {
InitializePool,
InitializePoolV2,
Swap,
@@ -79,7 +76,7 @@ enum KbOrcaWhirlpoolsInstructionKind {
Unknown,
}
impl KbOrcaWhirlpoolsDecoder {
impl OrcaWhirlpoolsDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -88,14 +85,14 @@ impl KbOrcaWhirlpoolsDecoder {
/// Decodes one projected transaction into zero or more Orca Whirlpools events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbOrcaWhirlpoolsDecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::OrcaWhirlpoolsDecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -106,13 +103,13 @@ impl KbOrcaWhirlpoolsDecoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let log_messages = extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -123,7 +120,7 @@ impl KbOrcaWhirlpoolsDecoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_ORCA_WHIRLPOOLS_PROGRAM_ID {
if program_id.as_str() != crate::ORCA_WHIRLPOOLS_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -131,48 +128,46 @@ impl KbOrcaWhirlpoolsDecoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
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 = 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),
};
let instruction_kind =
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
let instruction_kind = classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["whirlpool", "pool", "poolAddress", "poolAccount", "whirlpoolAddress"],
)
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 0));
let token_a_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenMintA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 1));
let token_b_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenMintB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 2));
let config_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["whirlpoolsConfig", "config", "configAccount", "whirlpoolConfig"],
)
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 3));
let creator = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["funder", "creator", "payer", "user", "owner"],
)
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePool
|| instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2
.or_else(|| return extract_account(&accounts, 4));
if instruction_kind == OrcaWhirlpoolsInstructionKind::InitializePool
|| instruction_kind == OrcaWhirlpoolsInstructionKind::InitializePoolV2
{
let used_v2 = instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
let used_v2 = instruction_kind == OrcaWhirlpoolsInstructionKind::InitializePoolV2;
let payload_json = serde_json::json!({
"decoder": "orca_whirlpools",
"eventKind": "create_pool",
@@ -189,8 +184,8 @@ impl KbOrcaWhirlpoolsDecoder {
"configAccount": config_account,
"creator": creator
});
decoded_events.push(crate::KbOrcaWhirlpoolsDecodedEvent::CreatePool(
crate::KbOrcaWhirlpoolsCreatePoolDecoded {
decoded_events.push(crate::OrcaWhirlpoolsDecodedEvent::CreatePool(
crate::OrcaWhirlpoolsCreatePoolDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -206,11 +201,11 @@ impl KbOrcaWhirlpoolsDecoder {
));
continue;
}
if instruction_kind == KbOrcaWhirlpoolsInstructionKind::Swap
|| instruction_kind == KbOrcaWhirlpoolsInstructionKind::SwapV2
if instruction_kind == OrcaWhirlpoolsInstructionKind::Swap
|| instruction_kind == OrcaWhirlpoolsInstructionKind::SwapV2
{
let used_v2 = instruction_kind == KbOrcaWhirlpoolsInstructionKind::SwapV2;
let trade_side = kb_infer_trade_side(&log_messages);
let used_v2 = instruction_kind == OrcaWhirlpoolsInstructionKind::SwapV2;
let trade_side = infer_trade_side(&log_messages);
let payload_json = serde_json::json!({
"decoder": "orca_whirlpools",
"eventKind": "swap",
@@ -226,8 +221,8 @@ impl KbOrcaWhirlpoolsDecoder {
"tokenBMint": token_b_mint,
"tradeSide": format!("{:?}", trade_side)
});
decoded_events.push(crate::KbOrcaWhirlpoolsDecodedEvent::Swap(
crate::KbOrcaWhirlpoolsSwapDecoded {
decoded_events.push(crate::OrcaWhirlpoolsDecodedEvent::Swap(
crate::OrcaWhirlpoolsSwapDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -246,61 +241,61 @@ impl KbOrcaWhirlpoolsDecoder {
}
}
fn kb_classify_instruction_kind(
fn classify_instruction_kind(
parsed_json: std::option::Option<&serde_json::Value>,
log_messages: &[std::string::String],
) -> KbOrcaWhirlpoolsInstructionKind {
let parsed_instruction_name = kb_extract_string_by_candidate_keys(
) -> OrcaWhirlpoolsInstructionKind {
let parsed_instruction_name = extract_string_by_candidate_keys(
parsed_json,
&["instruction", "instructionName", "type", "name"],
);
if let Some(parsed_instruction_name) = parsed_instruction_name {
let normalized = kb_normalize_text(parsed_instruction_name.as_str());
let normalized = normalize_text(parsed_instruction_name.as_str());
if normalized.contains("initializepoolv2") {
return KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
return OrcaWhirlpoolsInstructionKind::InitializePoolV2;
}
if normalized.contains("initializepool") {
return KbOrcaWhirlpoolsInstructionKind::InitializePool;
return OrcaWhirlpoolsInstructionKind::InitializePool;
}
if normalized == "swapv2" {
return KbOrcaWhirlpoolsInstructionKind::SwapV2;
return OrcaWhirlpoolsInstructionKind::SwapV2;
}
if normalized == "swap" {
return KbOrcaWhirlpoolsInstructionKind::Swap;
return OrcaWhirlpoolsInstructionKind::Swap;
}
}
if kb_value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& kb_log_messages_contain_keyword(log_messages, "initialize_pool")
if value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& log_messages_contain_keyword(log_messages, "initialize_pool")
{
return KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
return OrcaWhirlpoolsInstructionKind::InitializePoolV2;
}
if kb_value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& kb_log_messages_contain_keyword(log_messages, "swap")
if value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& log_messages_contain_keyword(log_messages, "swap")
{
return KbOrcaWhirlpoolsInstructionKind::SwapV2;
return OrcaWhirlpoolsInstructionKind::SwapV2;
}
if kb_log_messages_contain_keyword(log_messages, "initialize_pool_v2")
|| kb_log_messages_contain_keyword(log_messages, "initializepoolv2")
if log_messages_contain_keyword(log_messages, "initialize_pool_v2")
|| log_messages_contain_keyword(log_messages, "initializepoolv2")
{
return KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
return OrcaWhirlpoolsInstructionKind::InitializePoolV2;
}
if kb_log_messages_contain_keyword(log_messages, "initialize_pool")
|| kb_log_messages_contain_keyword(log_messages, "initializepool")
if log_messages_contain_keyword(log_messages, "initialize_pool")
|| log_messages_contain_keyword(log_messages, "initializepool")
{
return KbOrcaWhirlpoolsInstructionKind::InitializePool;
return OrcaWhirlpoolsInstructionKind::InitializePool;
}
if kb_log_messages_contain_keyword(log_messages, "swap_v2")
|| kb_log_messages_contain_keyword(log_messages, "swapv2")
if log_messages_contain_keyword(log_messages, "swap_v2")
|| log_messages_contain_keyword(log_messages, "swapv2")
{
return KbOrcaWhirlpoolsInstructionKind::SwapV2;
return OrcaWhirlpoolsInstructionKind::SwapV2;
}
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbOrcaWhirlpoolsInstructionKind::Swap;
if log_messages_contain_keyword(log_messages, "swap") {
return OrcaWhirlpoolsInstructionKind::Swap;
}
return KbOrcaWhirlpoolsInstructionKind::Unknown;
return OrcaWhirlpoolsInstructionKind::Unknown;
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -328,10 +323,13 @@ fn kb_extract_log_messages(
return messages;
}
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());
fn log_messages_contain_keyword(
log_messages_contain_keyword: &[std::string::String],
keyword: &str,
) -> bool {
let keyword_normalized = normalize_text(keyword);
for log_message in log_messages_contain_keyword {
let log_normalized = normalize_text(log_message.as_str());
if log_normalized.contains(keyword_normalized.as_str()) {
return true;
}
@@ -339,9 +337,9 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
fn normalize_text(normalize_text: &str) -> std::string::String {
let mut normalized = std::string::String::new();
for character in value.chars() {
for character in normalize_text.chars() {
if character.is_ascii_alphanumeric() {
normalized.push(character.to_ascii_lowercase());
}
@@ -349,14 +347,14 @@ fn kb_normalize_text(value: &str) -> std::string::String {
return normalized;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -372,9 +370,9 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
fn parse_optional_parsed_json(
parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::KbError> {
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
let parsed_json = match parsed_json {
Some(parsed_json) => parsed_json,
None => return Ok(None),
@@ -383,7 +381,7 @@ fn kb_parse_optional_parsed_json(
match value_result {
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
@@ -391,7 +389,7 @@ fn kb_parse_optional_parsed_json(
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -399,10 +397,10 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
return extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
fn extract_string_by_candidate_keys_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -418,7 +416,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
for nested_value in object.values() {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -429,7 +427,7 @@ fn kb_extract_string_by_candidate_keys_inner(
if let Some(array) = value.as_array() {
for nested_value in array {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -438,7 +436,7 @@ fn kb_extract_string_by_candidate_keys_inner(
return None;
}
fn kb_value_contains_any_key(
fn value_contains_any_key(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> bool {
@@ -446,10 +444,10 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
return kb_value_contains_any_key_inner(value, candidate_keys);
return value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
fn 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) {
@@ -457,7 +455,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
for nested_value in object.values() {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -465,7 +463,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
if let Some(array) = value.as_array() {
for nested_value in array {
if kb_value_contains_any_key_inner(nested_value, candidate_keys) {
if value_contains_any_key_inner(nested_value, candidate_keys) {
return true;
}
}
@@ -473,7 +471,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
return false;
}
fn kb_extract_account(
fn extract_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
@@ -483,20 +481,20 @@ fn kb_extract_account(
return Some(accounts[index].clone());
}
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;
fn infer_trade_side(log_messages: &[std::string::String]) -> crate::SwapTradeSide {
if log_messages_contain_keyword(log_messages, "buy") {
return crate::SwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
if log_messages_contain_keyword(log_messages, "sell") {
return crate::SwapTradeSide::SellBase;
}
return crate::KbSwapTradeSide::Unknown;
return crate::SwapTradeSide::Unknown;
}
#[cfg(test)]
mod tests {
fn make_create_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_create_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-orca-create-1".to_string(),
Some(891001),
Some(1779600001),
@@ -523,19 +521,19 @@ mod tests {
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_create_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
601,
None,
0,
None,
Some(crate::KB_ORCA_WHIRLPOOLS_PROGRAM_ID.to_string()),
Some(crate::ORCA_WHIRLPOOLS_PROGRAM_ID.to_string()),
Some("orca-whirlpools".to_string()),
Some(1),
serde_json::json!([
"OrcaPool111",
"OrcaTokenA111",
"So11111111111111111111111111111111111111112",
crate::WSOL_MINT_ID,
"OrcaConfig111",
"OrcaCreator111"
])
@@ -548,11 +546,11 @@ mod tests {
"instruction": "initialize_pool_v2",
"whirlpool": "OrcaPool111",
"tokenMintA": "OrcaTokenA111",
"tokenMintB": "So11111111111111111111111111111111111111112",
"tokenMintB": crate::WSOL_MINT_ID,
"whirlpoolsConfig": "OrcaConfig111",
"funder": "OrcaCreator111",
"tokenProgramA": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"tokenProgramB": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
"tokenProgramA": crate::SPL_TOKEN_PROGRAM_ID,
"tokenProgramB": crate::SPL_TOKEN_PROGRAM_ID
}
})
.to_string(),
@@ -562,8 +560,8 @@ mod tests {
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_swap_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-orca-swap-1".to_string(),
Some(891002),
Some(1779600002),
@@ -590,21 +588,17 @@ mod tests {
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_swap_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
603,
None,
0,
None,
Some(crate::KB_ORCA_WHIRLPOOLS_PROGRAM_ID.to_string()),
Some(crate::ORCA_WHIRLPOOLS_PROGRAM_ID.to_string()),
Some("orca-whirlpools".to_string()),
Some(1),
serde_json::json!([
"OrcaSwapPool111",
"OrcaSwapTokenA111",
"So11111111111111111111111111111111111111112"
])
.to_string(),
serde_json::json!(["OrcaSwapPool111", "OrcaSwapTokenA111", crate::WSOL_MINT_ID])
.to_string(),
None,
None,
Some(
@@ -613,7 +607,7 @@ mod tests {
"instruction": "swap_v2",
"whirlpool": "OrcaSwapPool111",
"tokenMintA": "OrcaSwapTokenA111",
"tokenMintB": "So11111111111111111111111111111111111111112"
"tokenMintB": crate::WSOL_MINT_ID
}
})
.to_string(),
@@ -625,7 +619,7 @@ mod tests {
#[test]
fn orca_whirlpools_create_pool_is_detected() {
let decoder = crate::KbOrcaWhirlpoolsDecoder::new();
let decoder = crate::OrcaWhirlpoolsDecoder::new();
let transaction = make_create_transaction();
let instructions = vec![make_create_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -635,19 +629,16 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbOrcaWhirlpoolsDecodedEvent::CreatePool(event) => {
crate::OrcaWhirlpoolsDecodedEvent::CreatePool(event) => {
assert_eq!(event.transaction_id, 601);
assert_eq!(event.instruction_id, 602);
assert_eq!(event.pool_account, Some("OrcaPool111".to_string()));
assert_eq!(event.token_a_mint, Some("OrcaTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
assert_eq!(event.config_account, Some("OrcaConfig111".to_string()));
assert!(event.used_v2);
},
crate::KbOrcaWhirlpoolsDecodedEvent::Swap(_) => {
crate::OrcaWhirlpoolsDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
},
}
@@ -655,7 +646,7 @@ mod tests {
#[test]
fn orca_whirlpools_swap_is_detected() {
let decoder = crate::KbOrcaWhirlpoolsDecoder::new();
let decoder = crate::OrcaWhirlpoolsDecoder::new();
let transaction = make_swap_transaction();
let instructions = vec![make_swap_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -665,18 +656,15 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbOrcaWhirlpoolsDecodedEvent::Swap(event) => {
crate::OrcaWhirlpoolsDecodedEvent::Swap(event) => {
assert_eq!(event.transaction_id, 603);
assert_eq!(event.instruction_id, 604);
assert_eq!(event.pool_account, Some("OrcaSwapPool111".to_string()));
assert_eq!(event.token_a_mint, Some("OrcaSwapTokenA111".to_string()));
assert_eq!(
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
assert!(event.used_v2);
},
crate::KbOrcaWhirlpoolsDecodedEvent::CreatePool(_) => {
crate::OrcaWhirlpoolsDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
},
}

View File

@@ -2,15 +2,12 @@
//! Pump.fun bonding-curve transaction decoder.
/// Pump.fun program id.
pub const KB_PUMP_FUN_PROGRAM_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
const KB_PUMP_FUN_BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
const KB_PUMP_FUN_SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
const PUMP_FUN_BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
const PUMP_FUN_SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
/// Decoded Pump.fun `create_v2` token event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbPumpFunCreateV2TokenDecoded {
pub struct PumpFunCreateV2TokenDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -33,7 +30,7 @@ pub struct KbPumpFunCreateV2TokenDecoded {
/// Decoded Pump.fun bonding-curve trade event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbPumpFunTradeDecoded {
pub struct PumpFunTradeDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -43,7 +40,7 @@ pub struct KbPumpFunTradeDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Token mint.
pub mint: std::option::Option<std::string::String>,
/// Bonding curve account.
@@ -64,20 +61,20 @@ pub struct KbPumpFunTradeDecoded {
/// Decoded Pump.fun event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbPumpFunDecodedEvent {
pub enum PumpFunDecodedEvent {
/// `create_v2` token creation.
CreateV2Token(KbPumpFunCreateV2TokenDecoded),
CreateV2Token(PumpFunCreateV2TokenDecoded),
/// Buy trade.
BuyTrade(KbPumpFunTradeDecoded),
BuyTrade(PumpFunTradeDecoded),
/// Sell trade.
SellTrade(KbPumpFunTradeDecoded),
SellTrade(PumpFunTradeDecoded),
}
/// Pump.fun decoder.
#[derive(Debug, Clone, Default)]
pub struct KbPumpFunDecoder;
pub struct PumpFunDecoder;
impl KbPumpFunDecoder {
impl PumpFunDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -86,14 +83,14 @@ impl KbPumpFunDecoder {
/// Decodes one projected transaction into zero or more Pump.fun events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbPumpFunDecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::PumpFunDecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -107,15 +104,15 @@ impl KbPumpFunDecoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"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")
|| kb_log_messages_contain_keyword(&log_messages, "createv2");
let log_messages = extract_log_messages(&transaction_json);
let has_create_v2_log = log_messages_contain_keyword(&log_messages, "create_v2")
|| log_messages_contain_keyword(&log_messages, "createv2");
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -126,7 +123,7 @@ impl KbPumpFunDecoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_PUMP_FUN_PROGRAM_ID {
if program_id.as_str() != crate::PUMP_FUN_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -134,35 +131,34 @@ impl KbPumpFunDecoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
Ok(accounts) => accounts,
Err(error) => return Err(error),
};
let instruction_data =
kb_decode_optional_instruction_data(instruction.data_json.as_ref());
let is_buy = kb_instruction_data_starts_with(
let instruction_data = decode_optional_instruction_data(instruction.data_json.as_ref());
let is_buy = instruction_data_starts_with(
instruction_data.as_deref(),
&KB_PUMP_FUN_BUY_DISCRIMINATOR,
&PUMP_FUN_BUY_DISCRIMINATOR,
);
let is_sell = kb_instruction_data_starts_with(
let is_sell = instruction_data_starts_with(
instruction_data.as_deref(),
&KB_PUMP_FUN_SELL_DISCRIMINATOR,
&PUMP_FUN_SELL_DISCRIMINATOR,
);
if is_buy || is_sell {
if accounts.len() < 7 {
continue;
}
let amount_raw = kb_extract_u64_argument(instruction_data.as_deref(), 8);
let sol_limit_raw = kb_extract_u64_argument(instruction_data.as_deref(), 16);
let mint = kb_extract_account(&accounts, 2);
let bonding_curve = kb_extract_account(&accounts, 3);
let associated_bonding_curve = kb_extract_account(&accounts, 4);
let associated_user = kb_extract_account(&accounts, 5);
let user = kb_extract_account(&accounts, 6);
let fee_recipient = kb_extract_account(&accounts, 1);
let token_program = kb_extract_account(&accounts, 8);
let creator_vault = kb_extract_account(&accounts, 9);
let amount_raw = extract_u64_argument(instruction_data.as_deref(), 8);
let sol_limit_raw = extract_u64_argument(instruction_data.as_deref(), 16);
let mint = extract_account(&accounts, 2);
let bonding_curve = extract_account(&accounts, 3);
let associated_bonding_curve = extract_account(&accounts, 4);
let associated_user = extract_account(&accounts, 5);
let user = extract_account(&accounts, 6);
let fee_recipient = extract_account(&accounts, 1);
let token_program = extract_account(&accounts, 8);
let creator_vault = extract_account(&accounts, 9);
let payload_json = serde_json::json!({
"decoder": "pump_fun",
"signature": transaction.signature,
@@ -187,15 +183,15 @@ impl KbPumpFunDecoder {
"solLimitRaw": sol_limit_raw,
"tradeSide": if is_buy { "BuyBase" } else { "SellBase" }
});
let event = crate::KbPumpFunTradeDecoded {
let event = crate::PumpFunTradeDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.clone(),
trade_side: if is_buy {
crate::KbSwapTradeSide::BuyBase
crate::SwapTradeSide::BuyBase
} else {
crate::KbSwapTradeSide::SellBase
crate::SwapTradeSide::SellBase
},
mint,
bonding_curve,
@@ -207,9 +203,9 @@ impl KbPumpFunDecoder {
payload_json,
};
if is_buy {
decoded_events.push(crate::KbPumpFunDecodedEvent::BuyTrade(event));
decoded_events.push(crate::PumpFunDecodedEvent::BuyTrade(event));
} else {
decoded_events.push(crate::KbPumpFunDecodedEvent::SellTrade(event));
decoded_events.push(crate::PumpFunDecodedEvent::SellTrade(event));
}
continue;
}
@@ -219,10 +215,10 @@ impl KbPumpFunDecoder {
if accounts.len() < 6 {
continue;
}
let mint = kb_extract_account(&accounts, 0);
let bonding_curve = kb_extract_account(&accounts, 2);
let associated_bonding_curve = kb_extract_account(&accounts, 3);
let creator = kb_extract_account(&accounts, 5);
let mint = extract_account(&accounts, 0);
let bonding_curve = extract_account(&accounts, 2);
let associated_bonding_curve = extract_account(&accounts, 3);
let creator = extract_account(&accounts, 5);
let payload_json = serde_json::json!({
"decoder": "pump_fun",
"eventKind": "create_v2_token",
@@ -236,8 +232,8 @@ impl KbPumpFunDecoder {
"associatedBondingCurve": associated_bonding_curve,
"creator": creator
});
decoded_events.push(crate::KbPumpFunDecodedEvent::CreateV2Token(
crate::KbPumpFunCreateV2TokenDecoded {
decoded_events.push(crate::PumpFunDecodedEvent::CreateV2Token(
crate::PumpFunCreateV2TokenDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -254,7 +250,7 @@ impl KbPumpFunDecoder {
}
}
fn kb_decode_optional_instruction_data(
fn decode_optional_instruction_data(
data_json: std::option::Option<&std::string::String>,
) -> std::option::Option<std::vec::Vec<u8>> {
let data_json = match data_json {
@@ -276,7 +272,7 @@ fn kb_decode_optional_instruction_data(
}
}
fn kb_instruction_data_starts_with(
fn instruction_data_starts_with(
instruction_data: std::option::Option<&[u8]>,
discriminator: &[u8; 8],
) -> bool {
@@ -290,7 +286,7 @@ fn kb_instruction_data_starts_with(
return &instruction_data[0..discriminator.len()] == discriminator;
}
fn kb_extract_u64_argument(
fn extract_u64_argument(
instruction_data: std::option::Option<&[u8]>,
offset: usize,
) -> std::option::Option<std::string::String> {
@@ -307,7 +303,7 @@ fn kb_extract_u64_argument(
return Some(u64::from_le_bytes(bytes).to_string());
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -335,10 +331,10 @@ fn kb_extract_log_messages(
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_log_text(keyword);
fn log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = normalize_log_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_log_text(log_message.as_str());
let log_normalized = normalize_log_text(log_message.as_str());
if log_normalized.contains(keyword_normalized.as_str()) {
return true;
}
@@ -346,14 +342,14 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return false;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -369,7 +365,7 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_extract_account(
fn extract_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
@@ -379,7 +375,7 @@ fn kb_extract_account(
return Some(accounts[index].clone());
}
fn kb_normalize_log_text(value: &str) -> std::string::String {
fn normalize_log_text(value: &str) -> std::string::String {
let mut normalized = std::string::String::new();
for character in value.chars() {
if character.is_ascii_alphanumeric() {
@@ -391,8 +387,8 @@ fn kb_normalize_log_text(value: &str) -> std::string::String {
#[cfg(test)]
mod tests {
fn make_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-pump-fun-test-1".to_string(),
Some(777001),
Some(1779200001),
@@ -419,13 +415,13 @@ mod tests {
return dto;
}
fn make_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
91,
None,
0,
None,
Some(crate::KB_PUMP_FUN_PROGRAM_ID.to_string()),
Some(crate::PUMP_FUN_PROGRAM_ID.to_string()),
Some("pump".to_string()),
Some(1),
serde_json::json!([
@@ -450,7 +446,7 @@ mod tests {
#[test]
fn pump_fun_create_v2_is_detected() {
let decoder = crate::KbPumpFunDecoder::new();
let decoder = crate::PumpFunDecoder::new();
let transaction = make_transaction();
let instructions = vec![make_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -460,7 +456,7 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbPumpFunDecodedEvent::CreateV2Token(event) => {
crate::PumpFunDecodedEvent::CreateV2Token(event) => {
assert_eq!(event.transaction_id, 91);
assert_eq!(event.instruction_id, 17);
assert_eq!(event.mint, Some("MintCreate111".to_string()));
@@ -471,10 +467,10 @@ mod tests {
);
assert_eq!(event.creator, Some("Creator111".to_string()));
},
crate::KbPumpFunDecodedEvent::BuyTrade(_) => {
crate::PumpFunDecodedEvent::BuyTrade(_) => {
panic!("unexpected pump_fun buy trade event");
},
crate::KbPumpFunDecodedEvent::SellTrade(_) => {
crate::PumpFunDecodedEvent::SellTrade(_) => {
panic!("unexpected pump_fun sell trade event");
},
}
@@ -482,7 +478,7 @@ mod tests {
#[test]
fn pump_fun_create_v2_returns_none_without_expected_log() {
let decoder = crate::KbPumpFunDecoder::new();
let decoder = crate::PumpFunDecoder::new();
let mut transaction = make_transaction();
transaction.transaction_json = serde_json::json!({
"slot": 777001,

View File

@@ -2,12 +2,25 @@
//! PumpSwap AMM transaction decoder.
/// PumpSwap / PumpAMM program id.
pub const KB_PUMP_SWAP_PROGRAM_ID: &str = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA";
const PUMP_SWAP_BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
const PUMP_SWAP_SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PumpSwapInstructionKind {
Buy,
Sell,
}
#[derive(Debug, Clone)]
struct PumpSwapInstructionData {
instruction_kind: PumpSwapInstructionKind,
base_amount_raw: std::option::Option<std::string::String>,
quote_amount_bound_raw: std::option::Option<std::string::String>,
}
/// Decoded PumpSwap trade event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbPumpSwapTradeDecoded {
pub struct PumpSwapTradeDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -17,7 +30,7 @@ pub struct KbPumpSwapTradeDecoded {
/// Program id.
pub program_id: std::string::String,
/// Trade side.
pub trade_side: crate::KbSwapTradeSide,
pub trade_side: crate::SwapTradeSide,
/// Optional heuristic pool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional token A mint.
@@ -32,18 +45,18 @@ pub struct KbPumpSwapTradeDecoded {
/// Decoded PumpSwap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbPumpSwapDecodedEvent {
pub enum PumpSwapDecodedEvent {
/// Buy trade.
BuyTrade(KbPumpSwapTradeDecoded),
BuyTrade(PumpSwapTradeDecoded),
/// Sell trade.
SellTrade(KbPumpSwapTradeDecoded),
SellTrade(PumpSwapTradeDecoded),
}
/// PumpSwap decoder.
#[derive(Debug, Clone, Default)]
pub struct KbPumpSwapDecoder;
pub struct PumpSwapDecoder;
impl KbPumpSwapDecoder {
impl PumpSwapDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -52,14 +65,14 @@ impl KbPumpSwapDecoder {
/// Decodes one projected transaction into zero or more PumpSwap events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbPumpSwapDecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::PumpSwapDecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -70,24 +83,21 @@ impl KbPumpSwapDecoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let log_messages = extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
continue;
}
let program_id_option = &instruction.program_id;
let program_id = match program_id_option {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_PUMP_SWAP_PROGRAM_ID {
if program_id.as_str() != crate::PUMP_SWAP_PROGRAM_ID {
continue;
}
let instruction_id_option = instruction.id;
@@ -95,48 +105,75 @@ impl KbPumpSwapDecoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
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 = 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),
};
let pool_account = kb_extract_string_by_candidate_keys(
let pool_account = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["pool", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 0));
let token_a_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenAMint", "baseMint", "mintA", "coinMint", "token0Mint", "mint0"],
)
.or_else(|| return kb_extract_account(&accounts, 3));
let token_b_mint = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 3));
let token_b_mint = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenBMint", "quoteMint", "mintB", "pcMint", "token1Mint", "mint1"],
)
.or_else(|| return kb_extract_account(&accounts, 4));
let pool_v2 = kb_extract_string_by_candidate_keys(
.or_else(|| return extract_account(&accounts, 4));
let pool_v2 = extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["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);
let is_buy = kb_log_messages_contain_keyword(&log_messages, "buy");
let is_sell = kb_log_messages_contain_keyword(&log_messages, "sell");
if !is_buy && !is_sell {
continue;
}
let user_base_token_account = extract_account(&accounts, 5);
let user_quote_token_account = extract_account(&accounts, 6);
let pool_base_token_account = extract_account(&accounts, 7);
let pool_quote_token_account = extract_account(&accounts, 8);
let instruction_data_result =
parse_pump_swap_instruction_data(instruction.data_json.as_ref());
let instruction_data_option = match instruction_data_result {
Ok(instruction_data_option) => instruction_data_option,
Err(error) => return Err(error),
};
let instruction_data = match instruction_data_option {
Some(instruction_data) => instruction_data,
None => continue,
};
let instruction_kind = match instruction_data.instruction_kind {
PumpSwapInstructionKind::Buy => "buy",
PumpSwapInstructionKind::Sell => "sell",
};
let max_quote_amount_in_raw = match instruction_data.instruction_kind {
PumpSwapInstructionKind::Buy => instruction_data.quote_amount_bound_raw.clone(),
PumpSwapInstructionKind::Sell => None,
};
let min_quote_amount_out_raw = match instruction_data.instruction_kind {
PumpSwapInstructionKind::Buy => None,
PumpSwapInstructionKind::Sell => instruction_data.quote_amount_bound_raw.clone(),
};
let payload_json = serde_json::json!({
"decoder": "pump_swap",
"signature": transaction.signature,
"instructionId": instruction_id,
"parentInstructionId": instruction.parent_instruction_id,
"instructionIndex": instruction.instruction_index,
"innerInstructionIndex": instruction.inner_instruction_index,
"instructionKind": instruction_kind,
"instructionBaseAmountRaw": instruction_data.base_amount_raw,
"instructionQuoteAmountBoundRaw": instruction_data.quote_amount_bound_raw,
"maxQuoteAmountInRaw": max_quote_amount_in_raw,
"minQuoteAmountOutRaw": min_quote_amount_out_raw,
"userBaseTokenAccount": user_base_token_account,
"userQuoteTokenAccount": user_quote_token_account,
"accounts": accounts,
"parsed": parsed_json,
"logMessages": log_messages,
@@ -147,14 +184,14 @@ impl KbPumpSwapDecoder {
"poolBaseTokenAccount": pool_base_token_account,
"poolQuoteTokenAccount": pool_quote_token_account
});
if is_buy {
decoded_events.push(crate::KbPumpSwapDecodedEvent::BuyTrade(
crate::KbPumpSwapTradeDecoded {
if instruction_data.instruction_kind == PumpSwapInstructionKind::Buy {
decoded_events.push(crate::PumpSwapDecodedEvent::BuyTrade(
crate::PumpSwapTradeDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.clone(),
trade_side: crate::KbSwapTradeSide::BuyBase,
trade_side: crate::SwapTradeSide::BuyBase,
pool_account: pool_account.clone(),
token_a_mint: token_a_mint.clone(),
token_b_mint: token_b_mint.clone(),
@@ -163,14 +200,14 @@ impl KbPumpSwapDecoder {
},
));
}
if is_sell {
decoded_events.push(crate::KbPumpSwapDecodedEvent::SellTrade(
crate::KbPumpSwapTradeDecoded {
if instruction_data.instruction_kind == PumpSwapInstructionKind::Sell {
decoded_events.push(crate::PumpSwapDecodedEvent::SellTrade(
crate::PumpSwapTradeDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.clone(),
trade_side: crate::KbSwapTradeSide::SellBase,
trade_side: crate::SwapTradeSide::SellBase,
pool_account,
token_a_mint,
token_b_mint,
@@ -184,7 +221,7 @@ impl KbPumpSwapDecoder {
}
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -213,25 +250,14 @@ fn kb_extract_log_messages(
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_lower = keyword.to_ascii_lowercase();
for log_message in log_messages {
let log_lower = log_message.to_ascii_lowercase();
if log_lower.contains(keyword_lower.as_str()) {
return true;
}
}
return false;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -247,19 +273,19 @@ fn kb_parse_accounts_json(
return Ok(accounts);
}
fn kb_extract_account(
accounts: &[std::string::String],
fn extract_account(
extract_account: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
if index >= accounts.len() {
if index >= extract_account.len() {
return None;
}
return Some(accounts[index].clone());
return Some(extract_account[index].clone());
}
fn kb_parse_optional_parsed_json(
fn parse_optional_parsed_json(
parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::KbError> {
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
let parsed_json = match parsed_json {
Some(parsed_json) => parsed_json,
None => return Ok(None),
@@ -268,7 +294,7 @@ fn kb_parse_optional_parsed_json(
match value_result {
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
)));
@@ -276,7 +302,7 @@ fn kb_parse_optional_parsed_json(
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
value: std::option::Option<&serde_json::Value>,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -284,10 +310,10 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
return extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
fn extract_string_by_candidate_keys_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -303,7 +329,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
for nested_value in object.values() {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -313,7 +339,7 @@ fn kb_extract_string_by_candidate_keys_inner(
if let Some(array) = value.as_array() {
for nested_value in array {
let nested_result =
kb_extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
if nested_result.is_some() {
return nested_result;
}
@@ -322,10 +348,94 @@ fn kb_extract_string_by_candidate_keys_inner(
return None;
}
fn parse_pump_swap_instruction_data(
data_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<PumpSwapInstructionData>, crate::Error> {
let data_json = match data_json {
Some(data_json) => data_json,
None => return Ok(None),
};
let data_text_result = parse_instruction_data_text(data_json.as_str());
let data_text = match data_text_result {
Ok(data_text) => data_text,
Err(error) => return Err(error),
};
if data_text.trim().is_empty() {
return Ok(None);
}
let decoded_result = bs58::decode(data_text.trim()).into_vec();
let decoded = match decoded_result {
Ok(decoded) => decoded,
Err(error) => {
return Err(crate::Error::Json(format!(
"cannot base58-decode PumpSwap instruction data '{}': {}",
data_text, error
)));
},
};
if decoded.len() < 8 {
return Ok(None);
}
let discriminator = [
decoded[0], decoded[1], decoded[2], decoded[3], decoded[4], decoded[5], decoded[6],
decoded[7],
];
let instruction_kind = if discriminator == PUMP_SWAP_BUY_DISCRIMINATOR {
PumpSwapInstructionKind::Buy
} else if discriminator == PUMP_SWAP_SELL_DISCRIMINATOR {
PumpSwapInstructionKind::Sell
} else {
return Ok(None);
};
let base_amount_raw = decode_u64_le_as_string(&decoded, 8);
let quote_amount_bound_raw = decode_u64_le_as_string(&decoded, 16);
return Ok(Some(PumpSwapInstructionData {
instruction_kind,
base_amount_raw,
quote_amount_bound_raw,
}));
}
fn parse_instruction_data_text(data_json: &str) -> Result<std::string::String, crate::Error> {
let trimmed = data_json.trim();
if trimmed.starts_with('"') {
let parsed_result = serde_json::from_str::<std::string::String>(trimmed);
return match parsed_result {
Ok(parsed) => Ok(parsed),
Err(error) => Err(crate::Error::Json(format!(
"cannot parse instruction data JSON string '{}': {}",
data_json, error
))),
};
}
return Ok(trimmed.to_string());
}
fn decode_u64_le_as_string(data: &[u8], offset: usize) -> std::option::Option<std::string::String> {
if data.len() < offset + 8 {
return None;
}
let value = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
return Some(value.to_string());
}
#[cfg(test)]
mod tests {
fn make_transaction_with_buy_log() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_pump_swap_buy_instruction_data_json() -> std::string::String {
return serde_json::json!("AJTQ2h9DXrBfqJi53PDQG2Fvki5tkaTU3").to_string();
}
fn make_transaction_with_buy_log() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-pump-swap-test-1".to_string(),
Some(777002),
Some(1779200002),
@@ -352,13 +462,13 @@ mod tests {
return dto;
}
fn make_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
92,
None,
0,
None,
Some(crate::KB_PUMP_SWAP_PROGRAM_ID.to_string()),
Some(crate::PUMP_SWAP_PROGRAM_ID.to_string()),
Some("pump-amm".to_string()),
Some(1),
serde_json::json!([
@@ -373,7 +483,7 @@ mod tests {
"PoolQuoteVault111"
])
.to_string(),
None,
Some(make_pump_swap_buy_instruction_data_json()),
None,
Some(
serde_json::json!({
@@ -393,7 +503,7 @@ mod tests {
#[test]
fn pump_swap_buy_is_detected() {
let decoder = crate::KbPumpSwapDecoder::new();
let decoder = crate::PumpSwapDecoder::new();
let transaction = make_transaction_with_buy_log();
let instructions = vec![make_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -403,7 +513,7 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbPumpSwapDecodedEvent::BuyTrade(event) => {
crate::PumpSwapDecodedEvent::BuyTrade(event) => {
assert_eq!(event.transaction_id, 92);
assert_eq!(event.instruction_id, 18);
assert_eq!(event.pool_account, Some("PumpPool111".to_string()));
@@ -418,9 +528,9 @@ mod tests {
event.payload_json.get("poolQuoteTokenAccount"),
Some(&serde_json::Value::String("PoolQuoteVault111".to_string()))
);
assert_eq!(event.trade_side, crate::KbSwapTradeSide::BuyBase);
assert_eq!(event.trade_side, crate::SwapTradeSide::BuyBase);
},
crate::KbPumpSwapDecodedEvent::SellTrade(_) => {
crate::PumpSwapDecodedEvent::SellTrade(_) => {
panic!("unexpected sell event")
},
}
@@ -428,7 +538,7 @@ mod tests {
#[test]
fn pump_swap_returns_none_without_buy_or_sell_log() {
let decoder = crate::KbPumpSwapDecoder::new();
let decoder = crate::PumpSwapDecoder::new();
let mut transaction = make_transaction_with_buy_log();
transaction.transaction_json = serde_json::json!({
"slot": 777002,
@@ -444,7 +554,9 @@ mod tests {
}
})
.to_string();
let instructions = vec![make_instruction()];
let mut instruction = make_instruction();
instruction.data_json = None;
let instructions = vec![instruction];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
let decoded = match decoded_result {
Ok(decoded) => decoded,
@@ -452,4 +564,40 @@ mod tests {
};
assert_eq!(decoded.len(), 0);
}
#[test]
fn pump_swap_inner_buy_is_detected() {
let decoder = crate::PumpSwapDecoder::new();
let transaction = make_transaction_with_buy_log();
let mut instruction = make_instruction();
instruction.parent_instruction_id = Some(17);
instruction.inner_instruction_index = Some(0);
let instructions = vec![instruction];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
let decoded = match decoded_result {
Ok(decoded) => decoded,
Err(error) => panic!("decode must succeed: {}", error),
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::PumpSwapDecodedEvent::BuyTrade(event) => {
assert_eq!(event.transaction_id, 92);
assert_eq!(event.instruction_id, 18);
assert_eq!(event.pool_account, Some("PumpPool111".to_string()));
assert_eq!(event.token_a_mint, Some("TokenA111".to_string()));
assert_eq!(event.token_b_mint, Some("TokenB111".to_string()));
assert_eq!(
event.payload_json.get("parentInstructionId"),
Some(&serde_json::Value::Number(serde_json::Number::from(17)))
);
assert_eq!(
event.payload_json.get("innerInstructionIndex"),
Some(&serde_json::Value::Number(serde_json::Number::from(0)))
);
},
crate::PumpSwapDecodedEvent::SellTrade(_) => {
panic!("unexpected sell event")
},
}
}
}

View File

@@ -2,12 +2,9 @@
//! Raydium AmmV4 transaction decoder.
/// Raydium AmmV4 program id.
pub const KB_RAYDIUM_AMM_V4_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
/// Decoded Raydium AmmV4 initialize2 pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbRaydiumAmmV4Initialize2PoolDecoded {
pub struct RaydiumAmmV4Initialize2PoolDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
@@ -32,16 +29,16 @@ pub struct KbRaydiumAmmV4Initialize2PoolDecoded {
/// Decoded Raydium AmmV4 event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KbRaydiumAmmV4DecodedEvent {
pub enum RaydiumAmmV4DecodedEvent {
/// `initialize2` pool creation-like event.
Initialize2Pool(KbRaydiumAmmV4Initialize2PoolDecoded),
Initialize2Pool(RaydiumAmmV4Initialize2PoolDecoded),
}
/// Raydium AmmV4 decoder.
#[derive(Debug, Clone, Default)]
pub struct KbRaydiumAmmV4Decoder;
pub struct RaydiumAmmV4Decoder;
impl KbRaydiumAmmV4Decoder {
impl RaydiumAmmV4Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
return Self;
@@ -50,14 +47,14 @@ impl KbRaydiumAmmV4Decoder {
/// Decodes one projected transaction into zero or more Raydium AmmV4 events.
pub fn decode_transaction(
&self,
transaction: &crate::KbChainTransactionDto,
instructions: &[crate::KbChainInstructionDto],
) -> Result<std::vec::Vec<crate::KbRaydiumAmmV4DecodedEvent>, crate::KbError> {
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::RaydiumAmmV4DecodedEvent>, crate::Error> {
let transaction_id_option = transaction.id;
let transaction_id = match transaction_id_option {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::KbError::InvalidState(format!(
return Err(crate::Error::InvalidState(format!(
"chain transaction '{}' has no internal id",
transaction.signature
)));
@@ -68,14 +65,14 @@ impl KbRaydiumAmmV4Decoder {
let transaction_json = match transaction_json_result {
Ok(transaction_json) => transaction_json,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"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);
let log_messages = extract_log_messages(&transaction_json);
let has_initialize2_log = log_messages_contain_initialize2(&log_messages);
let mut decoded_events = std::vec::Vec::new();
for instruction in instructions {
if instruction.parent_instruction_id.is_some() {
@@ -86,7 +83,7 @@ impl KbRaydiumAmmV4Decoder {
Some(program_id) => program_id,
None => continue,
};
if program_id.as_str() != crate::KB_RAYDIUM_AMM_V4_PROGRAM_ID {
if program_id.as_str() != crate::RAYDIUM_AMM_V4_PROGRAM_ID {
continue;
}
if !has_initialize2_log {
@@ -97,7 +94,7 @@ impl KbRaydiumAmmV4Decoder {
Some(instruction_id) => instruction_id,
None => continue,
};
let accounts_result = kb_parse_accounts_json(instruction.accounts_json.as_str());
let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
let accounts = match accounts_result {
Ok(accounts) => accounts,
Err(error) => return Err(error),
@@ -105,11 +102,11 @@ impl KbRaydiumAmmV4Decoder {
if accounts.len() < 10 {
continue;
}
let pool_account = kb_extract_account(&accounts, 4);
let lp_mint = kb_extract_account(&accounts, 7);
let token_a_mint = kb_extract_account(&accounts, 8);
let token_b_mint = kb_extract_account(&accounts, 9);
let market_account = kb_extract_account(&accounts, 16);
let pool_account = extract_account(&accounts, 4);
let lp_mint = extract_account(&accounts, 7);
let token_a_mint = extract_account(&accounts, 8);
let token_b_mint = extract_account(&accounts, 9);
let market_account = extract_account(&accounts, 16);
let payload_json = serde_json::json!({
"decoder": "raydium_amm_v4",
"eventKind": "initialize2_pool",
@@ -124,8 +121,8 @@ impl KbRaydiumAmmV4Decoder {
"tokenBMint": token_b_mint,
"marketAccount": market_account
});
decoded_events.push(crate::KbRaydiumAmmV4DecodedEvent::Initialize2Pool(
crate::KbRaydiumAmmV4Initialize2PoolDecoded {
decoded_events.push(crate::RaydiumAmmV4DecodedEvent::Initialize2Pool(
crate::RaydiumAmmV4Initialize2PoolDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
@@ -139,11 +136,11 @@ impl KbRaydiumAmmV4Decoder {
},
));
}
return Ok(decoded_events)
return Ok(decoded_events);
}
}
fn kb_extract_log_messages(
fn extract_log_messages(
transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
let mut messages = std::vec::Vec::new();
@@ -168,26 +165,26 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
return messages
return messages;
}
fn kb_log_messages_contain_initialize2(log_messages: &[std::string::String]) -> bool {
fn log_messages_contain_initialize2(log_messages: &[std::string::String]) -> bool {
for log_message in log_messages {
if log_message.contains("initialize2") {
return true;
}
}
return false
return false;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
let values = match values_result {
Ok(values) => values,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
@@ -200,23 +197,23 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
return Ok(accounts)
return Ok(accounts);
}
fn kb_extract_account(
fn extract_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
if index >= accounts.len() {
return None;
}
return Some(accounts[index].clone())
return Some(accounts[index].clone());
}
#[cfg(test)]
mod tests {
fn make_transaction() -> crate::KbChainTransactionDto {
let mut dto = crate::KbChainTransactionDto::new(
fn make_transaction() -> crate::ChainTransactionDto {
let mut dto = crate::ChainTransactionDto::new(
"sig-raydium-test-1".to_string(),
Some(888888),
Some(1778000000),
@@ -240,16 +237,16 @@ mod tests {
.to_string(),
);
dto.id = Some(42);
return dto
return dto;
}
fn make_instruction() -> crate::KbChainInstructionDto {
let mut dto = crate::KbChainInstructionDto::new(
fn make_instruction() -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
42,
None,
0,
None,
Some(crate::KB_RAYDIUM_AMM_V4_PROGRAM_ID.to_string()),
Some(crate::RAYDIUM_AMM_V4_PROGRAM_ID.to_string()),
Some("raydium-amm-v4".to_string()),
Some(1),
serde_json::json!([
@@ -277,12 +274,12 @@ mod tests {
None,
);
dto.id = Some(7);
return dto
return dto;
}
#[test]
fn raydium_amm_v4_initialize2_logs_are_detected() {
let decoder = crate::KbRaydiumAmmV4Decoder::new();
let decoder = crate::RaydiumAmmV4Decoder::new();
let transaction = make_transaction();
let instructions = vec![make_instruction()];
let decoded_result = decoder.decode_transaction(&transaction, &instructions);
@@ -292,7 +289,7 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbRaydiumAmmV4DecodedEvent::Initialize2Pool(event) => {
crate::RaydiumAmmV4DecodedEvent::Initialize2Pool(event) => {
assert_eq!(event.transaction_id, 42);
assert_eq!(event.instruction_id, 7);
assert_eq!(event.pool_account, Some("Pool111".to_string()));
@@ -306,7 +303,7 @@ mod tests {
#[test]
fn raydium_amm_v4_initialize2_returns_none_without_expected_log() {
let decoder = crate::KbRaydiumAmmV4Decoder::new();
let decoder = crate::RaydiumAmmV4Decoder::new();
let mut transaction = make_transaction();
transaction.transaction_json = serde_json::json!({
"slot": 888888,

View File

@@ -2,53 +2,50 @@
//! Raydium CLMM instruction decoder.
/// Raydium CLMM program id.
pub const KB_RAYDIUM_CLMM_PROGRAM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
const RAYDIUM_CLMM_SWAP_V2_DISCRIMINATOR: [u8; 8] = [43, 4, 237, 11, 26, 201, 30, 98];
const KB_RAYDIUM_CLMM_SWAP_V2_DISCRIMINATOR: [u8; 8] = [43, 4, 237, 11, 26, 201, 30, 98];
const KB_RAYDIUM_CLMM_SWAP_LEGACY_DISCRIMINATOR: [u8; 8] = [248, 198, 158, 145, 225, 117, 135, 200];
const RAYDIUM_CLMM_SWAP_LEGACY_DISCRIMINATOR: [u8; 8] = [248, 198, 158, 145, 225, 117, 135, 200];
/// Decoded Raydium CLMM event.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub enum KbRaydiumClmmDecodedEvent {
pub enum RaydiumClmmDecodedEvent {
/// Raydium CLMM swap_v2 event.
SwapV2(crate::KbRaydiumClmmSwapV2Decoded),
SwapV2(crate::RaydiumClmmSwapV2Decoded),
}
impl KbRaydiumClmmDecodedEvent {
impl RaydiumClmmDecodedEvent {
/// Returns the normalized event kind.
pub fn event_kind(&self) -> &'static str {
match self {
crate::KbRaydiumClmmDecodedEvent::SwapV2(_) => return "raydium_clmm.swap_v2",
crate::RaydiumClmmDecodedEvent::SwapV2(_) => return "raydium_clmm.swap_v2",
}
}
/// Returns the pool account.
pub fn pool_account(&self) -> &str {
match self {
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => return event.pool_state.as_str(),
crate::RaydiumClmmDecodedEvent::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) => return event.base_mint.as_str(),
crate::RaydiumClmmDecodedEvent::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) => return event.quote_mint.as_str(),
crate::RaydiumClmmDecodedEvent::SwapV2(event) => return event.quote_mint.as_str(),
}
}
/// Converts the decoded event to JSON payload.
pub fn to_payload_json(&self) -> std::option::Option<std::string::String> {
match self {
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => {
crate::RaydiumClmmDecodedEvent::SwapV2(event) => {
let result = serde_json::to_string(event);
match result {
Ok(payload_json) => return Some(payload_json),
@@ -61,7 +58,7 @@ impl KbRaydiumClmmDecodedEvent {
/// Decoded Raydium CLMM swap_v2 instruction.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct KbRaydiumClmmSwapV2Decoded {
pub struct RaydiumClmmSwapV2Decoded {
/// User performing the swap.
pub payer: std::string::String,
/// AMM config account.
@@ -104,10 +101,10 @@ pub struct KbRaydiumClmmSwapV2Decoded {
}
/// Decodes a Raydium CLMM instruction.
pub fn kb_decode_raydium_clmm_instruction(
pub fn decode_raydium_clmm_instruction(
accounts_json: &str,
data_json: &str,
) -> std::vec::Vec<crate::KbRaydiumClmmDecodedEvent> {
) -> std::vec::Vec<crate::RaydiumClmmDecodedEvent> {
let mut decoded = std::vec::Vec::new();
let accounts_result = serde_json::from_str::<std::vec::Vec<std::string::String>>(accounts_json);
let accounts = match accounts_result {
@@ -119,7 +116,7 @@ pub fn kb_decode_raydium_clmm_instruction(
Ok(data_base58) => data_base58,
Err(_) => data_json.to_string(),
};
let data_option = kb_decode_base58(data_base58.as_str());
let data_option = decode_base58(data_base58.as_str());
let data = match data_option {
Some(data) => data,
None => return decoded,
@@ -127,83 +124,83 @@ pub fn kb_decode_raydium_clmm_instruction(
if data.len() < 41 {
return decoded;
}
let discriminator_option = kb_read_discriminator(data.as_slice());
let discriminator_option = read_discriminator(data.as_slice());
let discriminator = match discriminator_option {
Some(discriminator) => discriminator,
None => return decoded,
};
if discriminator == KB_RAYDIUM_CLMM_SWAP_LEGACY_DISCRIMINATOR {
if discriminator == RAYDIUM_CLMM_SWAP_LEGACY_DISCRIMINATOR {
return decoded;
}
if discriminator != KB_RAYDIUM_CLMM_SWAP_V2_DISCRIMINATOR {
if discriminator != RAYDIUM_CLMM_SWAP_V2_DISCRIMINATOR {
return decoded;
}
let event_option = kb_decode_swap_v2(accounts.as_slice(), data.as_slice());
let event_option = decode_swap_v2(accounts.as_slice(), data.as_slice());
let event = match event_option {
Some(event) => event,
None => return decoded,
};
decoded.push(crate::KbRaydiumClmmDecodedEvent::SwapV2(event));
decoded.push(crate::RaydiumClmmDecodedEvent::SwapV2(event));
return decoded;
}
fn kb_decode_swap_v2(
fn decode_swap_v2(
accounts: &[std::string::String],
data: &[u8],
) -> std::option::Option<crate::KbRaydiumClmmSwapV2Decoded> {
let payer = match kb_clone_account(accounts, 0) {
) -> std::option::Option<crate::RaydiumClmmSwapV2Decoded> {
let payer = match clone_account(accounts, 0) {
Some(value) => value,
None => return None,
};
let amm_config = match kb_clone_account(accounts, 1) {
let amm_config = match clone_account(accounts, 1) {
Some(value) => value,
None => return None,
};
let pool_state = match kb_clone_account(accounts, 2) {
let pool_state = match clone_account(accounts, 2) {
Some(value) => value,
None => return None,
};
let input_token_account = match kb_clone_account(accounts, 3) {
let input_token_account = match clone_account(accounts, 3) {
Some(value) => value,
None => return None,
};
let output_token_account = match kb_clone_account(accounts, 4) {
let output_token_account = match clone_account(accounts, 4) {
Some(value) => value,
None => return None,
};
let input_vault = match kb_clone_account(accounts, 5) {
let input_vault = match clone_account(accounts, 5) {
Some(value) => value,
None => return None,
};
let output_vault = match kb_clone_account(accounts, 6) {
let output_vault = match clone_account(accounts, 6) {
Some(value) => value,
None => return None,
};
let observation_state = match kb_clone_account(accounts, 7) {
let observation_state = match clone_account(accounts, 7) {
Some(value) => value,
None => return None,
};
let input_vault_mint = match kb_clone_account(accounts, 11) {
let input_vault_mint = match clone_account(accounts, 11) {
Some(value) => value,
None => return None,
};
let output_vault_mint = match kb_clone_account(accounts, 12) {
let output_vault_mint = match clone_account(accounts, 12) {
Some(value) => value,
None => return None,
};
let amount = match kb_read_u64_le(data, 8) {
let amount = match read_u64_le(data, 8) {
Some(value) => value,
None => return None,
};
let other_amount_threshold = match kb_read_u64_le(data, 16) {
let other_amount_threshold = match read_u64_le(data, 16) {
Some(value) => value,
None => return None,
};
let sqrt_price_limit_x64 = match kb_read_u128_le(data, 24) {
let sqrt_price_limit_x64 = match read_u128_le(data, 24) {
Some(value) => value,
None => return None,
};
let is_base_input = match kb_read_bool(data, 40) {
let is_base_input = match read_bool(data, 40) {
Some(value) => value,
None => return None,
};
@@ -219,7 +216,7 @@ fn kb_decode_swap_v2(
quote_vault = input_vault.clone();
trade_side = "BuyBase".to_string();
}
return Some(crate::KbRaydiumClmmSwapV2Decoded {
return Some(crate::RaydiumClmmSwapV2Decoded {
payer,
amm_config,
pool_state,
@@ -242,7 +239,7 @@ fn kb_decode_swap_v2(
});
}
fn kb_clone_account(
fn clone_account(
accounts: &[std::string::String],
index: usize,
) -> std::option::Option<std::string::String> {
@@ -253,7 +250,7 @@ fn kb_clone_account(
}
}
fn kb_read_discriminator(data: &[u8]) -> std::option::Option<[u8; 8]> {
fn read_discriminator(data: &[u8]) -> std::option::Option<[u8; 8]> {
if data.len() < 8 {
return None;
}
@@ -266,7 +263,7 @@ fn kb_read_discriminator(data: &[u8]) -> std::option::Option<[u8; 8]> {
return Some(bytes);
}
fn kb_read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
fn read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
if data.len() < offset + 8 {
return None;
}
@@ -279,7 +276,7 @@ fn kb_read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
return Some(u64::from_le_bytes(bytes));
}
fn kb_read_u128_le(data: &[u8], offset: usize) -> std::option::Option<u128> {
fn read_u128_le(data: &[u8], offset: usize) -> std::option::Option<u128> {
if data.len() < offset + 16 {
return None;
}
@@ -292,7 +289,7 @@ fn kb_read_u128_le(data: &[u8], offset: usize) -> std::option::Option<u128> {
return Some(u128::from_le_bytes(bytes));
}
fn kb_read_bool(data: &[u8], offset: usize) -> std::option::Option<bool> {
fn read_bool(data: &[u8], offset: usize) -> std::option::Option<bool> {
if data.len() <= offset {
return None;
}
@@ -303,7 +300,7 @@ fn kb_read_bool(data: &[u8], offset: usize) -> std::option::Option<bool> {
}
}
fn kb_decode_base58(input: &str) -> std::option::Option<std::vec::Vec<u8>> {
fn decode_base58(input: &str) -> std::option::Option<std::vec::Vec<u8>> {
let alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".as_bytes();
let mut bytes: std::vec::Vec<u8> = std::vec::Vec::new();
for input_byte in input.bytes() {
@@ -376,13 +373,13 @@ mod tests {
#[test]
fn decodes_swap_v2() {
let events = crate::kb_decode_raydium_clmm_instruction(
let events = crate::decode_raydium_clmm_instruction(
sample_swap_v2_accounts_json(),
r#""ASCsAbe1UnDnCsnGLPALJUXSS5JREycfhGyTzKh7xRWNyRHCqBuzR23S""#,
);
assert_eq!(events.len(), 1);
match &events[0] {
crate::KbRaydiumClmmDecodedEvent::SwapV2(event) => {
crate::RaydiumClmmDecodedEvent::SwapV2(event) => {
assert_eq!(events[0].event_kind(), "raydium_clmm.swap_v2");
assert_eq!(
events[0].pool_account(),
@@ -408,7 +405,7 @@ mod tests {
#[test]
fn serializes_swap_v2_payload_json() {
let events = crate::kb_decode_raydium_clmm_instruction(
let events = crate::decode_raydium_clmm_instruction(
sample_swap_v2_accounts_json(),
r#""ASCsAbe1UnDnCsnGLPALJUXSS5JREycfhGyTzKh7xRWNyRHCqBuzR23S""#,
);
@@ -426,7 +423,7 @@ mod tests {
#[test]
fn ignores_invalid_data() {
let events = crate::kb_decode_raydium_clmm_instruction(
let events = crate::decode_raydium_clmm_instruction(
sample_swap_v2_accounts_json(),
r#""not-base58-data-0""#,
);
@@ -440,7 +437,7 @@ mod tests {
"A1BBtTYJd4i3xU8D6Tc2FzU6ZN4oXZWXKZnCxwbHXr8x",
"GUrRxvnWVQSnbcz1eP9D5BqXwPZtRhmrqVfm5wY9meWR"
]"#;
let events = crate::kb_decode_raydium_clmm_instruction(
let events = crate::decode_raydium_clmm_instruction(
accounts_json,
r#""ASCsAbe1UnDnCsnGLPALJUXSS5JREycfhGyTzKh7xRWNyRHCqBuzR23S""#,
);
@@ -456,7 +453,7 @@ mod tests {
data[40] = 1;
let encoded = bs58::encode(data).into_string();
let data_json = format!("\"{}\"", encoded);
let events = crate::kb_decode_raydium_clmm_instruction(
let events = crate::decode_raydium_clmm_instruction(
sample_swap_v2_accounts_json(),
data_json.as_str(),
);

View File

@@ -5,29 +5,24 @@
//! This module decodes Raydium constant product swap instructions from
//! already-projected Solana transaction instructions.
/// Raydium CPMM mainnet program id.
pub const KB_RAYDIUM_CPMM_PROGRAM_ID: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
/// Raydium CPMM `swap_base_input` discriminator.
const KB_RAYDIUM_CPMM_SWAP_BASE_INPUT_DISCRIMINATOR: [u8; 8] =
[143, 190, 90, 218, 196, 30, 51, 222];
const RAYDIUM_CPMM_SWAP_BASE_INPUT_DISCRIMINATOR: [u8; 8] = [143, 190, 90, 218, 196, 30, 51, 222];
/// Raydium CPMM `swap_base_output` discriminator.
const KB_RAYDIUM_CPMM_SWAP_BASE_OUTPUT_DISCRIMINATOR: [u8; 8] =
[55, 217, 98, 86, 163, 74, 180, 173];
const RAYDIUM_CPMM_SWAP_BASE_OUTPUT_DISCRIMINATOR: [u8; 8] = [55, 217, 98, 86, 163, 74, 180, 173];
/// Raydium CPMM decoded event.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
pub enum KbRaydiumCpmmDecodedEvent {
pub enum RaydiumCpmmDecodedEvent {
/// Swap where the user fixes the input amount.
SwapBaseInput(KbRaydiumCpmmSwapDecoded),
SwapBaseInput(RaydiumCpmmSwapDecoded),
/// Swap where the user fixes the output amount.
SwapBaseOutput(KbRaydiumCpmmSwapDecoded),
SwapBaseOutput(RaydiumCpmmSwapDecoded),
}
/// Raydium CPMM swap mode.
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
pub enum KbRaydiumCpmmSwapMode {
pub enum RaydiumCpmmSwapMode {
/// Fixed input swap.
BaseInput,
/// Fixed output swap.
@@ -36,9 +31,9 @@ pub enum KbRaydiumCpmmSwapMode {
/// Raydium CPMM decoded swap.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
pub struct KbRaydiumCpmmSwapDecoded {
pub struct RaydiumCpmmSwapDecoded {
/// Instruction mode.
pub swap_mode: KbRaydiumCpmmSwapMode,
pub swap_mode: RaydiumCpmmSwapMode,
/// User or payer account.
pub payer: std::string::String,
/// Raydium authority account.
@@ -87,50 +82,50 @@ pub struct KbRaydiumCpmmSwapDecoded {
pub amount_out_raw: std::option::Option<std::string::String>,
}
impl KbRaydiumCpmmDecodedEvent {
impl RaydiumCpmmDecodedEvent {
/// Returns the storage event kind.
pub fn event_kind(&self) -> &'static str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(_) => return "raydium_cpmm.swap_base_input",
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(_) => return "raydium_cpmm.swap_base_output",
RaydiumCpmmDecodedEvent::SwapBaseInput(_) => return "raydium_cpmm.swap_base_input",
RaydiumCpmmDecodedEvent::SwapBaseOutput(_) => return "raydium_cpmm.swap_base_output",
}
}
/// Returns the pool account.
pub fn pool_account(&self) -> &str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.pool_state.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.pool_state.as_str(),
RaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.pool_state.as_str(),
RaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.pool_state.as_str(),
}
}
/// Returns the normalized base mint.
pub fn base_mint(&self) -> &str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.base_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.base_mint.as_str(),
RaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.base_mint.as_str(),
RaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.base_mint.as_str(),
}
}
/// Returns the normalized quote mint.
pub fn quote_mint(&self) -> &str {
match self {
KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.quote_mint.as_str(),
KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.quote_mint.as_str(),
RaydiumCpmmDecodedEvent::SwapBaseInput(event) => return event.quote_mint.as_str(),
RaydiumCpmmDecodedEvent::SwapBaseOutput(event) => return event.quote_mint.as_str(),
}
}
/// Converts the decoded event to JSON payload.
pub fn to_payload_json(&self) -> std::option::Option<std::string::String> {
match self {
crate::KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => {
crate::RaydiumCpmmDecodedEvent::SwapBaseInput(event) => {
let result = serde_json::to_string(event);
match result {
Ok(payload) => return Some(payload),
Err(_) => return None,
}
},
crate::KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => {
crate::RaydiumCpmmDecodedEvent::SwapBaseOutput(event) => {
let result = serde_json::to_string(event);
match result {
Ok(payload) => return Some(payload),
@@ -142,15 +137,15 @@ impl KbRaydiumCpmmDecodedEvent {
}
/// Decodes one Raydium CPMM instruction from projected instruction fields.
pub fn kb_decode_raydium_cpmm_instruction(
pub fn decode_raydium_cpmm_instruction(
accounts_json: &str,
data_json: &str,
) -> std::vec::Vec<KbRaydiumCpmmDecodedEvent> {
let accounts = match kb_parse_accounts_json(accounts_json) {
) -> std::vec::Vec<RaydiumCpmmDecodedEvent> {
let accounts = match parse_accounts_json(accounts_json) {
Some(accounts) => accounts,
None => return std::vec::Vec::new(),
};
let data_base58 = match kb_parse_data_json_as_base58(data_json) {
let data_base58 = match parse_data_json_as_base58(data_json) {
Some(data_base58) => data_base58,
None => return std::vec::Vec::new(),
};
@@ -162,17 +157,17 @@ pub fn kb_decode_raydium_cpmm_instruction(
return std::vec::Vec::new();
}
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) {
if discriminator == RAYDIUM_CPMM_SWAP_BASE_INPUT_DISCRIMINATOR {
let amount_in = match read_u64_le(data.as_slice(), 8) {
Some(value) => value,
None => return std::vec::Vec::new(),
};
let minimum_amount_out = match kb_read_u64_le(data.as_slice(), 16) {
let minimum_amount_out = match read_u64_le(data.as_slice(), 16) {
Some(value) => value,
None => return std::vec::Vec::new(),
};
let swap = match kb_build_raydium_cpmm_swap(
KbRaydiumCpmmSwapMode::BaseInput,
let swap = match build_raydium_cpmm_swap(
RaydiumCpmmSwapMode::BaseInput,
accounts.as_slice(),
Some(amount_in.to_string()),
Some(minimum_amount_out.to_string()),
@@ -182,19 +177,19 @@ pub fn kb_decode_raydium_cpmm_instruction(
Some(swap) => swap,
None => return std::vec::Vec::new(),
};
return vec![KbRaydiumCpmmDecodedEvent::SwapBaseInput(swap)];
return vec![RaydiumCpmmDecodedEvent::SwapBaseInput(swap)];
}
if discriminator == KB_RAYDIUM_CPMM_SWAP_BASE_OUTPUT_DISCRIMINATOR {
let max_amount_in = match kb_read_u64_le(data.as_slice(), 8) {
if discriminator == RAYDIUM_CPMM_SWAP_BASE_OUTPUT_DISCRIMINATOR {
let max_amount_in = match read_u64_le(data.as_slice(), 8) {
Some(value) => value,
None => return std::vec::Vec::new(),
};
let amount_out = match kb_read_u64_le(data.as_slice(), 16) {
let amount_out = match read_u64_le(data.as_slice(), 16) {
Some(value) => value,
None => return std::vec::Vec::new(),
};
let swap = match kb_build_raydium_cpmm_swap(
KbRaydiumCpmmSwapMode::BaseOutput,
let swap = match build_raydium_cpmm_swap(
RaydiumCpmmSwapMode::BaseOutput,
accounts.as_slice(),
None,
None,
@@ -204,19 +199,19 @@ pub fn kb_decode_raydium_cpmm_instruction(
Some(swap) => swap,
None => return std::vec::Vec::new(),
};
return vec![KbRaydiumCpmmDecodedEvent::SwapBaseOutput(swap)];
return vec![RaydiumCpmmDecodedEvent::SwapBaseOutput(swap)];
}
return std::vec::Vec::new();
}
fn kb_build_raydium_cpmm_swap(
swap_mode: KbRaydiumCpmmSwapMode,
fn build_raydium_cpmm_swap(
swap_mode: RaydiumCpmmSwapMode,
accounts: &[std::string::String],
amount_in_raw: std::option::Option<std::string::String>,
minimum_amount_out_raw: std::option::Option<std::string::String>,
max_amount_in_raw: std::option::Option<std::string::String>,
amount_out_raw: std::option::Option<std::string::String>,
) -> std::option::Option<KbRaydiumCpmmSwapDecoded> {
) -> std::option::Option<RaydiumCpmmSwapDecoded> {
if accounts.len() < 13 {
return None;
}
@@ -224,7 +219,7 @@ fn kb_build_raydium_cpmm_swap(
let output_token_mint = accounts[11].clone();
let input_vault = accounts[6].clone();
let output_vault = accounts[7].clone();
let normalized = kb_normalize_raydium_cpmm_pair(
let normalized = normalize_raydium_cpmm_pair(
input_token_mint.as_str(),
output_token_mint.as_str(),
input_vault.as_str(),
@@ -232,7 +227,7 @@ fn kb_build_raydium_cpmm_swap(
);
let input_is_base = normalized.input_is_base;
let trade_side = if input_is_base { "sell".to_string() } else { "buy".to_string() };
return Some(KbRaydiumCpmmSwapDecoded {
return Some(RaydiumCpmmSwapDecoded {
swap_mode,
payer: accounts[0].clone(),
authority: accounts[1].clone(),
@@ -260,7 +255,7 @@ fn kb_build_raydium_cpmm_swap(
});
}
struct KbRaydiumCpmmNormalizedPair {
struct RaydiumCpmmNormalizedPair {
base_mint: std::string::String,
quote_mint: std::string::String,
base_vault: std::string::String,
@@ -268,14 +263,14 @@ struct KbRaydiumCpmmNormalizedPair {
input_is_base: bool,
}
fn kb_normalize_raydium_cpmm_pair(
fn normalize_raydium_cpmm_pair(
input_mint: &str,
output_mint: &str,
input_vault: &str,
output_vault: &str,
) -> KbRaydiumCpmmNormalizedPair {
if kb_is_quote_mint(output_mint) && !kb_is_quote_mint(input_mint) {
return KbRaydiumCpmmNormalizedPair {
) -> RaydiumCpmmNormalizedPair {
if is_quote_mint(output_mint) && !is_quote_mint(input_mint) {
return RaydiumCpmmNormalizedPair {
base_mint: input_mint.to_string(),
quote_mint: output_mint.to_string(),
base_vault: input_vault.to_string(),
@@ -283,8 +278,8 @@ fn kb_normalize_raydium_cpmm_pair(
input_is_base: true,
};
}
if kb_is_quote_mint(input_mint) && !kb_is_quote_mint(output_mint) {
return KbRaydiumCpmmNormalizedPair {
if is_quote_mint(input_mint) && !is_quote_mint(output_mint) {
return RaydiumCpmmNormalizedPair {
base_mint: output_mint.to_string(),
quote_mint: input_mint.to_string(),
base_vault: output_vault.to_string(),
@@ -293,7 +288,7 @@ fn kb_normalize_raydium_cpmm_pair(
};
}
if input_mint <= output_mint {
return KbRaydiumCpmmNormalizedPair {
return RaydiumCpmmNormalizedPair {
base_mint: input_mint.to_string(),
quote_mint: output_mint.to_string(),
base_vault: input_vault.to_string(),
@@ -301,7 +296,7 @@ fn kb_normalize_raydium_cpmm_pair(
input_is_base: true,
};
}
return KbRaydiumCpmmNormalizedPair {
return RaydiumCpmmNormalizedPair {
base_mint: output_mint.to_string(),
quote_mint: input_mint.to_string(),
base_vault: output_vault.to_string(),
@@ -310,8 +305,8 @@ fn kb_normalize_raydium_cpmm_pair(
};
}
fn kb_is_quote_mint(mint: &str) -> bool {
if mint == "So11111111111111111111111111111111111111112" {
fn is_quote_mint(mint: &str) -> bool {
if mint == crate::WSOL_MINT_ID {
return true;
}
if mint == "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" {
@@ -326,7 +321,7 @@ fn kb_is_quote_mint(mint: &str) -> bool {
return false;
}
fn kb_parse_accounts_json(
fn parse_accounts_json(
accounts_json: &str,
) -> std::option::Option<std::vec::Vec<std::string::String>> {
let result = serde_json::from_str::<std::vec::Vec<std::string::String>>(accounts_json);
@@ -336,7 +331,7 @@ fn kb_parse_accounts_json(
}
}
fn kb_parse_data_json_as_base58(data_json: &str) -> std::option::Option<std::string::String> {
fn 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);
if let Ok(value) = json_string_result {
return Some(value);
@@ -352,7 +347,7 @@ fn kb_parse_data_json_as_base58(data_json: &str) -> std::option::Option<std::str
return Some(without_quotes.to_string());
}
fn kb_read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
fn read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
if data.len() < offset + 8 {
return None;
}
@@ -388,13 +383,13 @@ mod tests {
"USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB",
"9fzQ17bEnqJSsyHL5CodJqY2sdjZJBQCQLbgFZ1BYTWn"
]"#;
let events = crate::kb_decode_raydium_cpmm_instruction(
let events = crate::decode_raydium_cpmm_instruction(
accounts_json,
r#""E73fXHPWvSRF4Q2ZQFvPoeJBVGDUEMmxB""#,
);
assert_eq!(events.len(), 1);
match &events[0] {
crate::KbRaydiumCpmmDecodedEvent::SwapBaseInput(event) => {
crate::RaydiumCpmmDecodedEvent::SwapBaseInput(event) => {
assert_eq!(event.pool_state, "2ErXvV1tKtG3wiHqdofDjMou7Jusdsfasvfh8HrTj5oV");
assert_eq!(event.base_mint, "Pf9aSicGu3g6tTUBqrRbjNsGape9HopibspX5KSbonk");
assert_eq!(event.quote_mint, "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB");
@@ -425,13 +420,13 @@ mod tests {
"Pf9aSicGu3g6tTUBqrRbjNsGape9HopibspX5KSbonk",
"9fzQ17bEnqJSsyHL5CodJqY2sdjZJBQCQLbgFZ1BYTWn"
]"#;
let events = crate::kb_decode_raydium_cpmm_instruction(
let events = crate::decode_raydium_cpmm_instruction(
accounts_json,
r#""66JafaVu7KNEtTngqQyD7ETVurF3rxJ47""#,
);
assert_eq!(events.len(), 1);
match &events[0] {
crate::KbRaydiumCpmmDecodedEvent::SwapBaseOutput(event) => {
crate::RaydiumCpmmDecodedEvent::SwapBaseOutput(event) => {
assert_eq!(event.pool_state, "2ErXvV1tKtG3wiHqdofDjMou7Jusdsfasvfh8HrTj5oV");
assert_eq!(event.base_mint, "Pf9aSicGu3g6tTUBqrRbjNsGape9HopibspX5KSbonk");
assert_eq!(event.quote_mint, "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB");