This commit is contained in:
2026-06-09 10:13:03 +02:00
parent f2ea1a392f
commit bfdb2e69ae
41 changed files with 4485 additions and 1124 deletions

View File

@@ -40,6 +40,12 @@ pub struct RaydiumAmmV4SwapDecoded {
pub signature: std::string::String,
/// Program id.
pub program_id: std::string::String,
/// Local decoded event kind derived from the AMM v4 instruction discriminator.
pub event_kind: std::string::String,
/// Upstream instruction name derived from the AMM v4 instruction discriminator.
pub instruction_name: std::string::String,
/// One-byte Raydium AMM v4 instruction discriminator in hex form.
pub discriminator_hex: std::string::String,
/// AMM pool/state account.
pub pool_account: std::string::String,
/// Raydium AMM authority account.
@@ -78,6 +84,49 @@ pub struct RaydiumAmmV4SwapDecoded {
pub payload_json: serde_json::Value,
}
/// Decoded Raydium AmmV4 non-swap or decoded-only instruction event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RaydiumAmmV4InstructionDecoded {
/// Parent transaction id.
pub transaction_id: i64,
/// Parent instruction id.
pub instruction_id: i64,
/// Transaction signature.
pub signature: std::string::String,
/// Program id.
pub program_id: std::string::String,
/// Local decoded event kind derived from the AMM v4 instruction discriminator.
pub event_kind: std::string::String,
/// Upstream instruction name derived from the AMM v4 instruction discriminator.
pub instruction_name: std::string::String,
/// One-byte Raydium AMM v4 instruction discriminator in hex form.
pub discriminator_hex: std::string::String,
/// Optional AMM pool/state account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional Raydium AMM authority account.
pub authority: std::option::Option<std::string::String>,
/// Optional AMM open-orders account.
pub open_orders: std::option::Option<std::string::String>,
/// Optional AMM target-orders account.
pub target_orders: std::option::Option<std::string::String>,
/// Optional market program account.
pub market_program: std::option::Option<std::string::String>,
/// Optional market account.
pub market_account: std::option::Option<std::string::String>,
/// Optional LP mint account.
pub lp_mint: std::option::Option<std::string::String>,
/// Optional token A mint after best-effort account layout extraction.
pub token_a_mint: std::option::Option<std::string::String>,
/// Optional token B mint after best-effort account layout extraction.
pub token_b_mint: std::option::Option<std::string::String>,
/// Optional AMM coin/base vault account.
pub base_vault: std::option::Option<std::string::String>,
/// Optional AMM pc/quote vault account.
pub quote_vault: std::option::Option<std::string::String>,
/// Decoded payload.
pub payload_json: serde_json::Value,
}
/// Decoded Raydium AmmV4 event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum RaydiumAmmV4DecodedEvent {
@@ -85,6 +134,8 @@ pub enum RaydiumAmmV4DecodedEvent {
Initialize2Pool(std::boxed::Box<RaydiumAmmV4Initialize2PoolDecoded>),
/// Swap event decoded from a direct or inner Raydium AMM v4 instruction.
Swap(std::boxed::Box<RaydiumAmmV4SwapDecoded>),
/// Known Raydium AMM v4 instruction decoded without direct trade materialization.
Instruction(std::boxed::Box<RaydiumAmmV4InstructionDecoded>),
}
/// Raydium AmmV4 decoder.
@@ -182,11 +233,27 @@ impl RaydiumAmmV4Decoder {
token_balances.as_slice(),
);
match swap_result {
Ok(Some(swap)) => decoded_events
.push(crate::RaydiumAmmV4DecodedEvent::Swap(std::boxed::Box::new(swap))),
Ok(Some(swap)) => {
decoded_events
.push(crate::RaydiumAmmV4DecodedEvent::Swap(std::boxed::Box::new(swap)));
continue;
},
Ok(None) => {},
Err(error) => return Err(error),
}
let instruction_event = decode_known_instruction_event(
transaction,
transaction_id,
instruction,
instruction_id,
program_id,
accounts.as_slice(),
);
if let Some(instruction_event) = instruction_event {
decoded_events.push(crate::RaydiumAmmV4DecodedEvent::Instruction(
std::boxed::Box::new(instruction_event),
));
}
}
return Ok(decoded_events);
}
@@ -209,9 +276,15 @@ fn decode_initialize2_event(
let token_a_mint = extract_account(accounts, 8);
let token_b_mint = extract_account(accounts, 9);
let market_account = extract_account(accounts, 16);
let data_base58 = parse_optional_data_json_as_base58(instruction.data_json.as_deref());
let discriminator_hex = raydium_amm_v4_instruction_discriminator_hex(data_base58.as_deref());
let payload_json = serde_json::json!({
"decoder": "raydium_amm_v4",
"eventKind": "initialize2_pool",
"instructionName": "initialize2",
"upstreamInstructionName": "initialize2",
"discriminatorHex": discriminator_hex,
"instructionDiscriminatorHex": discriminator_hex,
"signature": transaction.signature,
"instructionId": instruction_id,
"instructionIndex": instruction.instruction_index,
@@ -251,6 +324,11 @@ fn decode_swap_event(
if accounts.len() < 8 {
return Ok(None);
}
let data_base58 = parse_optional_data_json_as_base58(instruction.data_json.as_deref());
let swap_instruction = match raydium_amm_v4_swap_instruction(data_base58.as_deref()) {
Some(swap_instruction) => swap_instruction,
None => return Ok(None),
};
let pool_account = match extract_account(accounts, 1) {
Some(pool_account) => pool_account,
None => return Ok(None),
@@ -294,11 +372,14 @@ fn decode_swap_event(
normalized_pair.quote_vault.as_str(),
);
let parent_program = parent_program_id_for_instruction(instruction, transaction_instructions);
let data_base58 = parse_optional_data_json_as_base58(instruction.data_json.as_deref());
let trade_candidate = base_amount_raw.is_some() && quote_amount_raw.is_some();
let payload_json = serde_json::json!({
"decoder": "raydium_amm_v4",
"eventKind": "swap",
"eventKind": swap_instruction.event_kind,
"instructionName": swap_instruction.instruction_name,
"upstreamInstructionName": swap_instruction.instruction_name,
"discriminatorHex": swap_instruction.discriminator_hex,
"instructionDiscriminatorHex": swap_instruction.discriminator_hex,
"signature": transaction.signature,
"instructionId": instruction_id,
"instructionIndex": instruction.instruction_index,
@@ -337,6 +418,9 @@ fn decode_swap_event(
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.to_string(),
event_kind: swap_instruction.event_kind.to_string(),
instruction_name: swap_instruction.instruction_name.to_string(),
discriminator_hex: swap_instruction.discriminator_hex.to_string(),
pool_account,
authority,
token_a_mint: normalized_pair.base_mint,
@@ -358,6 +442,439 @@ fn decode_swap_event(
}));
}
fn decode_known_instruction_event(
transaction: &crate::ChainTransactionDto,
transaction_id: i64,
instruction: &crate::ChainInstructionDto,
instruction_id: i64,
program_id: &str,
accounts: &[std::string::String],
) -> std::option::Option<crate::RaydiumAmmV4InstructionDecoded> {
let data_base58 = parse_optional_data_json_as_base58(instruction.data_json.as_deref());
let data_bytes = raydium_amm_v4_instruction_data_bytes(data_base58.as_deref());
let identity = match raydium_amm_v4_instruction_identity(data_base58.as_deref()) {
Some(identity) => identity,
None => return None,
};
let account_layout = raydium_amm_v4_account_layout(identity.discriminator_hex);
let pool_account = extract_account_by_index(accounts, account_layout.pool_account_index);
let authority = extract_account_by_index(accounts, account_layout.authority_index);
let open_orders = extract_account_by_index(accounts, account_layout.open_orders_index);
let target_orders = extract_account_by_index(accounts, account_layout.target_orders_index);
let market_program = extract_account_by_index(accounts, account_layout.market_program_index);
let market_account = extract_account_by_index(accounts, account_layout.market_account_index);
let lp_mint = extract_account_by_index(accounts, account_layout.lp_mint_index);
let token_a_mint = extract_account_by_index(accounts, account_layout.token_a_mint_index);
let token_b_mint = extract_account_by_index(accounts, account_layout.token_b_mint_index);
let base_vault = extract_account_by_index(accounts, account_layout.base_vault_index);
let quote_vault = extract_account_by_index(accounts, account_layout.quote_vault_index);
let mut payload_json = serde_json::json!({
"decoder": "raydium_amm_v4",
"eventKind": identity.event_kind,
"instructionName": identity.instruction_name,
"upstreamInstructionName": identity.instruction_name,
"discriminatorHex": identity.discriminator_hex,
"instructionDiscriminatorHex": identity.discriminator_hex,
"signature": transaction.signature,
"instructionId": instruction_id,
"instructionIndex": instruction.instruction_index,
"innerInstructionIndex": instruction.inner_instruction_index,
"innerInstruction": instruction.inner_instruction_index.is_some(),
"parentInstructionId": instruction.parent_instruction_id,
"programId": program_id,
"accounts": accounts,
"data": data_base58,
"instructionDataLength": data_bytes.as_ref().map(std::vec::Vec::len),
"poolAccount": pool_account.clone(),
"authority": authority.clone(),
"openOrders": open_orders.clone(),
"targetOrders": target_orders.clone(),
"marketProgram": market_program.clone(),
"marketAccount": market_account.clone(),
"lpMint": lp_mint.clone(),
"tokenAMint": token_a_mint.clone(),
"tokenBMint": token_b_mint.clone(),
"baseVault": base_vault.clone(),
"quoteVault": quote_vault.clone(),
"tradeCandidate": false,
"candleCandidate": false,
"skipTradeReason": "decoded_only_instruction",
"skipCandleReason": "decoded_only_instruction"
});
enrich_known_instruction_payload(&mut payload_json, identity.discriminator_hex, data_bytes.as_deref());
return Some(crate::RaydiumAmmV4InstructionDecoded {
transaction_id,
instruction_id,
signature: transaction.signature.clone(),
program_id: program_id.to_string(),
event_kind: identity.event_kind.to_string(),
instruction_name: identity.instruction_name.to_string(),
discriminator_hex: identity.discriminator_hex.to_string(),
pool_account,
authority,
open_orders,
target_orders,
market_program,
market_account,
lp_mint,
token_a_mint,
token_b_mint,
base_vault,
quote_vault,
payload_json,
});
}
#[derive(Clone, Copy)]
struct RaydiumAmmV4AccountLayout {
pool_account_index: std::option::Option<usize>,
authority_index: std::option::Option<usize>,
open_orders_index: std::option::Option<usize>,
target_orders_index: std::option::Option<usize>,
market_program_index: std::option::Option<usize>,
market_account_index: std::option::Option<usize>,
lp_mint_index: std::option::Option<usize>,
token_a_mint_index: std::option::Option<usize>,
token_b_mint_index: std::option::Option<usize>,
base_vault_index: std::option::Option<usize>,
quote_vault_index: std::option::Option<usize>,
}
fn raydium_amm_v4_empty_account_layout() -> RaydiumAmmV4AccountLayout {
return RaydiumAmmV4AccountLayout {
pool_account_index: None,
authority_index: None,
open_orders_index: None,
target_orders_index: None,
market_program_index: None,
market_account_index: None,
lp_mint_index: None,
token_a_mint_index: None,
token_b_mint_index: None,
base_vault_index: None,
quote_vault_index: None,
};
}
fn raydium_amm_v4_account_layout(discriminator_hex: &str) -> RaydiumAmmV4AccountLayout {
let mut layout = raydium_amm_v4_empty_account_layout();
match discriminator_hex {
"00" => {
layout.pool_account_index = Some(3);
},
"01" => {
layout.pool_account_index = Some(4);
layout.authority_index = Some(5);
layout.open_orders_index = Some(6);
layout.lp_mint_index = Some(7);
layout.token_a_mint_index = Some(8);
layout.token_b_mint_index = Some(9);
layout.base_vault_index = Some(10);
layout.quote_vault_index = Some(11);
layout.target_orders_index = Some(12);
layout.market_program_index = Some(15);
layout.market_account_index = Some(16);
},
"02" => {
layout.pool_account_index = Some(3);
layout.authority_index = Some(4);
layout.open_orders_index = Some(5);
layout.target_orders_index = Some(6);
layout.base_vault_index = Some(7);
layout.quote_vault_index = Some(8);
layout.market_program_index = Some(9);
layout.market_account_index = Some(10);
},
"03" => {
layout.pool_account_index = Some(1);
layout.authority_index = Some(2);
layout.open_orders_index = Some(3);
layout.target_orders_index = Some(4);
layout.lp_mint_index = Some(5);
layout.base_vault_index = Some(6);
layout.quote_vault_index = Some(7);
layout.market_account_index = Some(8);
},
"04" => {
layout.pool_account_index = Some(1);
layout.authority_index = Some(2);
layout.open_orders_index = Some(3);
layout.target_orders_index = Some(4);
layout.lp_mint_index = Some(5);
layout.base_vault_index = Some(6);
layout.quote_vault_index = Some(7);
layout.market_program_index = Some(8);
layout.market_account_index = Some(9);
},
"05" => {
layout.pool_account_index = Some(3);
layout.authority_index = Some(4);
layout.open_orders_index = Some(5);
layout.base_vault_index = Some(6);
layout.quote_vault_index = Some(7);
layout.target_orders_index = Some(8);
layout.market_program_index = Some(9);
layout.market_account_index = Some(10);
},
"06" => {
layout.pool_account_index = Some(1);
layout.authority_index = Some(2);
layout.open_orders_index = Some(3);
layout.target_orders_index = Some(4);
layout.base_vault_index = Some(5);
layout.quote_vault_index = Some(6);
layout.market_program_index = Some(7);
layout.market_account_index = Some(8);
},
"07" => {
layout.pool_account_index = Some(1);
layout.authority_index = Some(3);
layout.open_orders_index = Some(4);
layout.base_vault_index = Some(5);
layout.quote_vault_index = Some(6);
layout.target_orders_index = Some(10);
layout.market_program_index = Some(11);
layout.market_account_index = Some(12);
},
"08" => {
layout.pool_account_index = Some(1);
layout.authority_index = Some(3);
},
"09" | "0b" => {
layout.pool_account_index = Some(1);
layout.authority_index = Some(2);
layout.open_orders_index = Some(3);
layout.target_orders_index = Some(4);
layout.base_vault_index = Some(5);
layout.quote_vault_index = Some(6);
layout.market_program_index = Some(7);
layout.market_account_index = Some(8);
},
"0a" => {
layout.pool_account_index = Some(4);
},
"0c" => {
layout.pool_account_index = Some(1);
},
"0d" => {
layout.pool_account_index = Some(1);
},
"10" | "11" => {
layout.pool_account_index = Some(1);
layout.authority_index = Some(2);
layout.base_vault_index = Some(3);
layout.quote_vault_index = Some(4);
},
_ => {},
}
return layout;
}
fn extract_account_by_index(
accounts: &[std::string::String],
index: std::option::Option<usize>,
) -> std::option::Option<std::string::String> {
let index = match index {
Some(index) => index,
None => return None,
};
return extract_account(accounts, index);
}
fn raydium_amm_v4_instruction_data_bytes(
data_base58: std::option::Option<&str>,
) -> std::option::Option<std::vec::Vec<u8>> {
let data_base58 = match data_base58 {
Some(data_base58) => data_base58,
None => return None,
};
let bytes_result = bs58::decode(data_base58).into_vec();
match bytes_result {
Ok(bytes) => return Some(bytes),
Err(_) => return None,
}
}
fn enrich_known_instruction_payload(
payload_json: &mut serde_json::Value,
discriminator_hex: &str,
data: std::option::Option<&[u8]>,
) {
let data = match data {
Some(data) => data,
None => return,
};
match discriminator_hex {
"00" => {
insert_u8(payload_json, "nonce", data, 1);
insert_u64_string(payload_json, "openTime", data, 2);
},
"01" => {
insert_u8(payload_json, "nonce", data, 1);
insert_u64_string(payload_json, "openTime", data, 2);
insert_u64_string(payload_json, "initPcAmount", data, 10);
insert_u64_string(payload_json, "initCoinAmount", data, 18);
insert_u64_string(payload_json, "tokenAAmount", data, 18);
insert_u64_string(payload_json, "tokenBAmount", data, 10);
},
"02" => {
insert_u16(payload_json, "planOrderLimit", data, 1);
insert_u16(payload_json, "placeOrderLimit", data, 3);
insert_u16(payload_json, "cancelOrderLimit", data, 5);
},
"03" => {
insert_u64_string(payload_json, "maxCoinAmount", data, 1);
insert_u64_string(payload_json, "maxPcAmount", data, 9);
insert_u64_string(payload_json, "baseSide", data, 17);
insert_u64_string(payload_json, "otherAmountMin", data, 25);
insert_u64_string(payload_json, "tokenAAmount", data, 1);
insert_u64_string(payload_json, "tokenBAmount", data, 9);
},
"04" => {
insert_u64_string(payload_json, "lpAmountRaw", data, 1);
insert_u64_string(payload_json, "liquidity", data, 1);
insert_u64_string(payload_json, "minCoinAmount", data, 9);
insert_u64_string(payload_json, "minPcAmount", data, 17);
},
"06" => {
insert_u8(payload_json, "configParam", data, 1);
insert_u64_string(payload_json, "configValue", data, 2);
insert_fixed_hex(payload_json, "configPubkeyHex", data, 2, 32);
insert_u64_string(payload_json, "lastOrderNumerator", data, 2);
insert_u64_string(payload_json, "lastOrderDenominator", data, 10);
},
"08" => {
insert_u64_string(payload_json, "amountRaw", data, 1);
},
"09" | "10" => {
insert_u64_string(payload_json, "amountIn", data, 1);
insert_u64_string(payload_json, "minimumAmountOut", data, 9);
},
"0a" => {
insert_u8(payload_json, "nonce", data, 1);
},
"0b" | "11" => {
insert_u64_string(payload_json, "maxAmountIn", data, 1);
insert_u64_string(payload_json, "amountOut", data, 9);
},
"0c" => {
insert_u8(payload_json, "simulateParam", data, 1);
insert_u64_string(payload_json, "amountIn", data, 2);
insert_u64_string(payload_json, "minimumAmountOut", data, 10);
insert_u64_string(payload_json, "maxAmountIn", data, 2);
insert_u64_string(payload_json, "amountOut", data, 10);
},
"0d" => {
insert_u16(payload_json, "orderCancelLimit", data, 1);
},
"0f" => {
insert_u8(payload_json, "configParam", data, 1);
insert_fixed_hex(payload_json, "configOwnerHex", data, 2, 32);
insert_u64_string(payload_json, "createPoolFee", data, 2);
},
_ => {},
}
}
fn insert_u8(payload_json: &mut serde_json::Value, key: &str, data: &[u8], offset: usize) {
let value = match read_u8(data, offset) {
Some(value) => value,
None => return,
};
insert_json_value(
payload_json,
key,
serde_json::Value::Number(serde_json::Number::from(value as u64)),
);
}
fn insert_u16(payload_json: &mut serde_json::Value, key: &str, data: &[u8], offset: usize) {
let value = match read_u16_le(data, offset) {
Some(value) => value,
None => return,
};
insert_json_value(
payload_json,
key,
serde_json::Value::Number(serde_json::Number::from(value as u64)),
);
}
fn insert_u64_string(payload_json: &mut serde_json::Value, key: &str, data: &[u8], offset: usize) {
let value = match read_u64_le(data, offset) {
Some(value) => value,
None => return,
};
insert_json_value(
payload_json,
key,
serde_json::Value::String(value.to_string()),
);
}
fn insert_fixed_hex(
payload_json: &mut serde_json::Value,
key: &str,
data: &[u8],
offset: usize,
len: usize,
) {
if data.len() < offset + len {
return;
}
let slice = &data[offset..offset + len];
insert_json_value(payload_json, key, serde_json::Value::String(bytes_to_hex(slice)));
}
fn insert_json_value(payload_json: &mut serde_json::Value, key: &str, value: serde_json::Value) {
let object_option = payload_json.as_object_mut();
let object = match object_option {
Some(object) => object,
None => return,
};
object.insert(key.to_string(), value);
}
fn read_u8(data: &[u8], offset: usize) -> std::option::Option<u8> {
if data.len() < offset + 1 {
return None;
}
return Some(data[offset]);
}
fn read_u16_le(data: &[u8], offset: usize) -> std::option::Option<u16> {
if data.len() < offset + 2 {
return None;
}
let bytes = [data[offset], data[offset + 1]];
return Some(u16::from_le_bytes(bytes));
}
fn read_u64_le(data: &[u8], offset: usize) -> std::option::Option<u64> {
if data.len() < offset + 8 {
return None;
}
let 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(u64::from_le_bytes(bytes));
}
fn bytes_to_hex(bytes: &[u8]) -> std::string::String {
let mut output = std::string::String::new();
for byte in bytes {
output.push_str(format!("{byte:02x}").as_str());
}
return output;
}
#[derive(Debug, Clone)]
struct TokenBalanceRecord {
account_address: std::option::Option<std::string::String>,
@@ -401,6 +918,183 @@ struct NormalizedVaultPair {
quote_mint: std::string::String,
}
#[derive(Clone, Copy)]
struct RaydiumAmmV4InstructionIdentity {
instruction_name: &'static str,
event_kind: &'static str,
discriminator_hex: &'static str,
}
fn raydium_amm_v4_swap_instruction(
data_base58: std::option::Option<&str>,
) -> std::option::Option<RaydiumAmmV4InstructionIdentity> {
let identity = match raydium_amm_v4_instruction_identity(data_base58) {
Some(identity) => identity,
None => return None,
};
match identity.discriminator_hex {
"09" | "0b" | "10" | "11" => return Some(identity),
_ => return None,
}
}
fn raydium_amm_v4_instruction_identity(
data_base58: std::option::Option<&str>,
) -> std::option::Option<RaydiumAmmV4InstructionIdentity> {
let discriminator_hex = match raydium_amm_v4_instruction_discriminator_hex(data_base58) {
Some(discriminator_hex) => discriminator_hex,
None => return None,
};
match discriminator_hex.as_str() {
"00" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "initialize",
event_kind: "raydium_amm_v4.initialize",
discriminator_hex: "00",
});
},
"01" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "initialize2",
event_kind: "raydium_amm_v4.initialize2_pool",
discriminator_hex: "01",
});
},
"02" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "monitor_step",
event_kind: "raydium_amm_v4.monitor_step",
discriminator_hex: "02",
});
},
"03" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "deposit",
event_kind: "raydium_amm_v4.deposit",
discriminator_hex: "03",
});
},
"04" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "withdraw",
event_kind: "raydium_amm_v4.withdraw",
discriminator_hex: "04",
});
},
"05" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "migrate_to_open_book",
event_kind: "raydium_amm_v4.migrate_to_open_book",
discriminator_hex: "05",
});
},
"06" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "set_params",
event_kind: "raydium_amm_v4.set_params",
discriminator_hex: "06",
});
},
"07" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "withdraw_pnl",
event_kind: "raydium_amm_v4.withdraw_pnl",
discriminator_hex: "07",
});
},
"08" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "withdraw_srm",
event_kind: "raydium_amm_v4.withdraw_srm",
discriminator_hex: "08",
});
},
"09" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "swap_base_in",
event_kind: "raydium_amm_v4.swap_base_in",
discriminator_hex: "09",
});
},
"0a" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "pre_initialize",
event_kind: "raydium_amm_v4.pre_initialize",
discriminator_hex: "0a",
});
},
"0b" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "swap_base_out",
event_kind: "raydium_amm_v4.swap_base_out",
discriminator_hex: "0b",
});
},
"0c" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "simulate_info",
event_kind: "raydium_amm_v4.simulate_info",
discriminator_hex: "0c",
});
},
"0d" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "admin_cancel_orders",
event_kind: "raydium_amm_v4.admin_cancel_orders",
discriminator_hex: "0d",
});
},
"0e" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "create_config_account",
event_kind: "raydium_amm_v4.create_config_account",
discriminator_hex: "0e",
});
},
"0f" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "update_config_account",
event_kind: "raydium_amm_v4.update_config_account",
discriminator_hex: "0f",
});
},
"10" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "swap_base_in_v2",
event_kind: "raydium_amm_v4.swap_base_in_v2",
discriminator_hex: "10",
});
},
"11" => {
return Some(RaydiumAmmV4InstructionIdentity {
instruction_name: "swap_base_out_v2",
event_kind: "raydium_amm_v4.swap_base_out_v2",
discriminator_hex: "11",
});
},
_ => return None,
}
}
fn raydium_amm_v4_instruction_discriminator_hex(
data_base58: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
let data_base58 = match data_base58 {
Some(data_base58) => data_base58,
None => return None,
};
let bytes_result = bs58::decode(data_base58).into_vec();
let bytes = match bytes_result {
Ok(bytes) => bytes,
Err(_) => return None,
};
let first_byte = match bytes.first() {
Some(first_byte) => first_byte,
None => return None,
};
return Some(format!("{first_byte:02x}"));
}
fn parse_transaction_meta_value(
transaction: &crate::ChainTransactionDto,
transaction_json: &serde_json::Value,
@@ -1209,6 +1903,14 @@ mod tests {
}
fn make_swap_instruction() -> crate::ChainInstructionDto {
return make_swap_instruction_with_data("63SfuT4qF7xK35jRTGqxuUT");
}
fn make_swap_v2_instruction() -> crate::ChainInstructionDto {
return make_swap_instruction_with_data("9rj8cBJgMm4L1xvzfy5AUsy");
}
fn make_swap_instruction_with_data(data_base58: &str) -> crate::ChainInstructionDto {
let mut dto = crate::ChainInstructionDto::new(
100,
Some(55),
@@ -1228,7 +1930,7 @@ mod tests {
"UserQuote111"
])
.to_string(),
Some(serde_json::json!("9rj8cBJgMm4L1xvzfy5AUsy").to_string()),
Some(serde_json::json!(data_base58).to_string()),
None,
None,
);
@@ -1305,6 +2007,9 @@ mod tests {
assert_eq!(event.transaction_id, 100);
assert_eq!(event.instruction_id, 200);
assert_eq!(event.pool_account, "Pool111".to_string());
assert_eq!(event.event_kind, "raydium_amm_v4.swap_base_in".to_string());
assert_eq!(event.instruction_name, "swap_base_in".to_string());
assert_eq!(event.discriminator_hex, "09".to_string());
assert_eq!(event.token_a_mint, "BaseMint111".to_string());
assert_eq!(event.token_b_mint, crate::WSOL_MINT_ID.to_string());
assert_eq!(event.base_vault, "BaseVault111".to_string());
@@ -1320,4 +2025,29 @@ mod tests {
_ => panic!("expected swap event"),
}
}
#[test]
fn raydium_amm_v4_swap_base_in_v2_is_decoded_from_inner_instruction_and_vault_deltas() {
let decoder = crate::RaydiumAmmV4Decoder::new();
let transaction = make_swap_transaction();
let instructions = vec![make_swap_v2_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::RaydiumAmmV4DecodedEvent::Swap(event) => {
assert_eq!(event.event_kind, "raydium_amm_v4.swap_base_in_v2".to_string());
assert_eq!(event.instruction_name, "swap_base_in_v2".to_string());
assert_eq!(event.discriminator_hex, "10".to_string());
assert_eq!(event.pool_account, "Pool111".to_string());
assert_eq!(event.base_amount_raw, Some("100".to_string()));
assert_eq!(event.quote_amount_raw, Some("100".to_string()));
assert_eq!(event.trade_side, Some("BuyBase".to_string()));
},
_ => panic!("expected swap event"),
}
}
}