use lil_tabby::tabled;
use std::fmt::Display;
use base64ct::{Base64, Encoding};
use borsh::{BorshDeserialize, BorshSerialize};
use color_eyre::{Result, eyre::eyre};
use lil_tabby::tabby;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
use yansi::Paint;
use yansi::hyperlink::HyperlinkExt;
use crate::utils::SolanaLinks;
pub const EVENT_DISCRIMINATOR_COMPLETE_EVENT: [u8; 8] = [95, 114, 97, 156, 212, 46, 152, 8];
pub const EVENT_DISCRIMINATOR_UPDATE_GLOBAL_AUTHORITY_EVENT: [u8; 8] =
[182, 195, 137, 42, 35, 206, 207, 247];
pub const EVENT_DISCRIMINATOR_EXTEND_ACCOUNT_EVENT: [u8; 8] = [97, 97, 215, 144, 93, 146, 22, 124];
pub const EVENT_DISCRIMINATOR_COMPLETE_PUMP_AMM_MIGRATION_EVENT: [u8; 8] =
[189, 233, 93, 185, 92, 148, 234, 148];
pub const EVENT_DISCRIMINATOR_SET_CREATOR_EVENT: [u8; 8] = [142, 203, 6, 32, 127, 105, 191, 162];
pub const EVENT_DISCRIMINATOR_COLLECT_CREATOR_FEE_EVENT: [u8; 8] =
[122, 2, 127, 1, 14, 191, 12, 175];
pub const EVENT_DISCRIMINATOR_TRADE_EVENT: [u8; 8] = [189, 219, 127, 211, 78, 230, 97, 238];
pub const EVENT_DISCRIMINATOR_CREATE_EVENT: [u8; 8] = [27, 114, 169, 77, 222, 235, 99, 118];
pub const EVENT_DISCRIMINATOR_SET_PARAMS_EVENT: [u8; 8] = [223, 195, 159, 246, 62, 48, 143, 131];
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct CreateEvent {
pub name: String,
pub symbol: String,
pub uri: String,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub mint: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub bonding_curve: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub user: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub creator: Pubkey,
pub timestamp: i64,
pub virtual_token_reserves: u64,
pub virtual_sol_reserves: u64,
pub real_token_reserves: u64,
pub token_total_supply: u64,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct CompleteEvent {
#[serde_as(as = "serde_with::DisplayFromStr")]
pub user: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub mint: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub bonding_curve: Pubkey,
pub timestamp: i64,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct CompletePumpAmmMigrationEvent {
#[serde_as(as = "serde_with::DisplayFromStr")]
pub user: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub mint: Pubkey,
#[serde_as(as = "Option<serde_with::DisplayFromStr>")]
pub creator: Option<Pubkey>,
pub mint_amount: u64,
pub sol_amount: u64,
pub pool_migration_fee: u64,
pub creator_fee: u64,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub bonding_curve: Pubkey,
pub timestamp: i64,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub pool: Pubkey,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ExtendAccountEvent {
#[serde_as(as = "serde_with::DisplayFromStr")]
pub account: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub user: Pubkey,
pub current_size: u64,
pub new_size: u64,
pub timestamp: i64,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct SetParamsEvent {
pub initial_virtual_token_reserves: u64,
pub initial_virtual_sol_reserves: u64,
pub initial_real_token_reserves: u64,
pub final_real_sol_reserves: u64,
pub token_total_supply: u64,
pub fee_basis_points: u64,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub withdraw_authority: Pubkey,
pub enable_migrate: bool,
pub pool_migration_fee: u64,
pub creator_fee: u64,
#[serde_as(as = "[serde_with::DisplayFromStr; 8]")]
pub fee_recipients: [Pubkey; 8],
pub timestamp: i64,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TradeEvent {
#[serde_as(as = "serde_with::DisplayFromStr")]
pub mint: Pubkey,
pub sol_amount: u64,
pub token_amount: u64,
pub is_buy: bool,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub user: Pubkey,
pub timestamp: i64,
pub virtual_sol_reserves: u64,
pub virtual_token_reserves: u64,
pub real_sol_reserves: u64,
pub real_token_reserves: u64,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub fee_recipient: Pubkey,
pub fee_basis_points: u64,
pub fee: u64,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub creator: Pubkey,
pub creator_fee_basis_points: u64,
pub creator_fee: u64,
pub track_volume: bool,
pub total_unclaimed_tokens: u64,
pub total_claimed_tokens: u64,
pub current_sol_volume: u64,
pub last_update_timestamp: u64,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct UpdateGlobalAuthorityEvent {
#[serde_as(as = "serde_with::DisplayFromStr")]
pub global: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub authority: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub new_authority: Pubkey,
pub timestamp: i64,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct SetCreatorEvent {
pub timestamp: i64,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub mint: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub bonding_curve: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub metadata: Pubkey,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub creator: Pubkey,
}
#[serde_with::serde_as]
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct CollectCreatorFeeEvent {
pub timestamp: i64,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub creator: Pubkey,
pub creator_fee: u64,
}
#[serde_with::serde_as]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Event {
Create(CreateEvent),
Trade(TradeEvent),
Complete(CompleteEvent),
CompletePumpAmmMigration(CompletePumpAmmMigrationEvent),
SetParams(SetParamsEvent),
ExtendAccount(ExtendAccountEvent),
SetCreator(SetCreatorEvent),
CollectCreatorFee(CollectCreatorFeeEvent),
UpdateGlobalAuthority(UpdateGlobalAuthorityEvent),
}
pub fn decode_event(event: impl Into<String>) -> Result<Event> {
let event_data_bytes = Base64::decode_vec(&event.into())?;
if event_data_bytes.len() < 8 {
return Err(eyre!(
"Event data too short to contain a discriminator.".to_string()
));
}
let discriminator: [u8; 8] = event_data_bytes[0..8]
.try_into()
.map_err(|_| eyre!("Failed to extract event discriminator slice"))?;
let rest = &event_data_bytes[8..];
match discriminator {
EVENT_DISCRIMINATOR_COMPLETE_EVENT => CompleteEvent::try_from_slice(rest)
.map(Event::Complete)
.map_err(|e| eyre!("Failed to deserialize CompleteEvent: {:?}", e)),
EVENT_DISCRIMINATOR_UPDATE_GLOBAL_AUTHORITY_EVENT => {
UpdateGlobalAuthorityEvent::try_from_slice(rest)
.map(Event::UpdateGlobalAuthority)
.map_err(|e| eyre!("Failed to deserialize UpdateGlobalAuthorityEvent: {:?}", e))
}
EVENT_DISCRIMINATOR_EXTEND_ACCOUNT_EVENT => ExtendAccountEvent::try_from_slice(rest)
.map(Event::ExtendAccount)
.map_err(|e| eyre!("Failed to deserialize ExtendAccountEvent: {:?}", e)),
EVENT_DISCRIMINATOR_COMPLETE_PUMP_AMM_MIGRATION_EVENT => {
CompletePumpAmmMigrationEvent::try_from_slice(rest)
.map(Event::CompletePumpAmmMigration)
.map_err(|e| {
eyre!(
"Failed to deserialize CompletePumpAmmMigrationEvent: {:?}",
e
)
})
}
EVENT_DISCRIMINATOR_SET_CREATOR_EVENT => SetCreatorEvent::try_from_slice(rest)
.map(Event::SetCreator)
.map_err(|e| eyre!("Failed to deserialize SetCreatorEvent: {:?}", e)),
EVENT_DISCRIMINATOR_COLLECT_CREATOR_FEE_EVENT => {
CollectCreatorFeeEvent::try_from_slice(rest)
.map(Event::CollectCreatorFee)
.map_err(|e| eyre!("Failed to deserialize CollectCreatorFeeEvent: {:?}", e))
}
EVENT_DISCRIMINATOR_TRADE_EVENT => TradeEvent::try_from_slice(rest)
.map(Event::Trade)
.map_err(|e| eyre!("Failed to deserialize TradeEvent: {:?}", e)),
EVENT_DISCRIMINATOR_CREATE_EVENT => CreateEvent::try_from_slice(rest)
.map(Event::Create)
.map_err(|e| eyre!("Failed to deserialize CreateEvent: {:?}", e)),
EVENT_DISCRIMINATOR_SET_PARAMS_EVENT => SetParamsEvent::try_from_slice(rest)
.map(Event::SetParams)
.map_err(|e| eyre!("Failed to deserialize SetParamsEvent: {:?}", e)),
_ => Err(eyre!("Unknown event discriminator: {:?}", discriminator)),
}
}
pub fn get_metadata(url: impl AsRef<str> + Display) -> String {
let request = ehttp::Request::get(url.as_ref());
if let Ok(resp) = ehttp::fetch_blocking(&request)
&& let Ok(json) = resp.json::<serde_json::Value>()
{
let data = json.as_object().map(|obj| {
obj.into_iter()
.map(|(key, value)| {
vec![
key.clone(),
value
.as_str()
.map(|v| v.to_string())
.unwrap_or(value.to_string()),
]
})
.collect::<Vec<Vec<String>>>()
});
if let Some(mut rows) = data {
rows.insert(0, vec!["Key".to_string(), "Value".to_string()]);
return tabby![
{ style: psql, labels: cyan, color: yellow, header: green }
rows
]
.with(tabled::settings::Margin::new(0, 0, 0, 0))
.to_string();
}
};
tabby![
{ style: empty, header: bright_red}
["Error! Can't process metadata url:"],
[format!("{}", url.as_ref().to_string().link(&url).blue())]
]
.to_string()
}
impl std::fmt::Display for Event {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Event::Create(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("CreateEvent: {}", event.mint.acc().blue())],
["name", &event.name],
["symbol", &event.symbol],
["uri", &event.uri],
["metadata", get_metadata(&event.uri)],
["bonding_curve", &event.bonding_curve.acc()],
["user", &event.user.acc()],
["creator", &event.creator.acc()],
["timestamp", &event.timestamp.to_string()],
["supply", format!("{:.2}", event.token_total_supply as f64 / 1e6)],
["token reserve", format!("{:.2}", event.real_token_reserves as f64 / 1e6)],
["virtual reserves", format!("{:.2} / {:.2}", event.virtual_token_reserves as f64 / 1e6, event.virtual_sol_reserves as f64 / 1e9)],
]
.modify(
tabled::settings::object::Cell::new(4, 1),
tabled::settings::Padding::zero()
)
)
}
Event::Trade(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("TradeEvent: {}", event.mint.acc().blue())],
["sol_amount", &event.sol_amount.to_string()],
["token_amount", &event.token_amount.to_string()],
["side", if event.is_buy { "BUY".green() } else { "SELL".red() }.bold()],
["user", &event.user.acc()],
["timestamp", &event.timestamp.to_string()],
["reserves", format!("{:.4} / {:.2}", event.real_token_reserves as f64 / 1e6, event.real_sol_reserves as f64 / 1e9)],
["virtual reserves", format!("{:.4} / {:.2}", event.virtual_token_reserves as f64 / 1e6, event.virtual_sol_reserves as f64 / 1e9)],
["creator", &event.creator.acc()],
["fee_recipient", &event.fee_recipient.acc()],
["fee", format!("{} ({}%)", event.fee.to_string(), event.fee_basis_points as f64/1e2)],
["creator_fee", format!("{} ({}%)", event.creator_fee.to_string(), event.creator_fee_basis_points as f64/1e2)]
]
)
}
Event::Complete(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("CompleteEvent: {}", event.mint.acc().blue())],
["user", &event.user.acc()],
["mint", &event.mint.acc()],
["bonding_curve", &event.bonding_curve.acc()],
["timestamp", &event.timestamp.to_string()]
]
)
}
Event::CompletePumpAmmMigration(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("CompletePumpAmmMigrationEvent: {}", event.mint.acc().blue())],
["user", &event.user.acc()],
["mint", &event.mint.acc()],
["creator", event.creator.map_or("None".to_string(), |c| c.acc().to_string())],
["mint_amount", &event.mint_amount.to_string()],
["sol_amount", &event.sol_amount.to_string()],
["pool_migration_fee", &event.pool_migration_fee.to_string()],
["creator_fee", &event.creator_fee.to_string()],
["bonding_curve", &event.bonding_curve.acc()],
["pool", &event.pool.acc()],
["timestamp", &event.timestamp.to_string()]
]
)
}
Event::SetParams(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("SetParamsEvent")],
["initial_virtual_token_reserves", &event.initial_virtual_token_reserves.to_string()],
["initial_virtual_sol_reserves", &event.initial_virtual_sol_reserves.to_string()],
["initial_real_token_reserves", &event.initial_real_token_reserves.to_string()],
["final_real_sol_reserves", &event.final_real_sol_reserves.to_string()],
["token_total_supply", &event.token_total_supply.to_string()],
["fee_basis_points", &event.fee_basis_points.to_string()],
["withdraw_authority", &event.withdraw_authority.acc()],
["enable_migrate", &event.enable_migrate.to_string()],
["pool_migration_fee", &event.pool_migration_fee.to_string()],
["creator_fee", &event.creator_fee.to_string()],
["fee_recipients", &event.fee_recipients.iter().map(|p| p.acc().to_string()).collect::<Vec<String>>().join("\n")],
["timestamp", &event.timestamp.to_string()]
]
)
}
Event::ExtendAccount(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("ExtendAccountEvent: {}", event.account.acc().blue())],
["account", &event.account.acc()],
["user", &event.user.acc()],
["current_size", &event.current_size.to_string()],
["new_size", &event.new_size.to_string()],
["timestamp", &event.timestamp.to_string()]
]
)
}
Event::SetCreator(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("SetCreatorEvent: {}", event.mint.acc().blue())],
["timestamp", &event.timestamp.to_string()],
["mint", &event.mint.acc()],
["bonding_curve", &event.bonding_curve.acc()],
["metadata", &event.metadata.acc()],
["creator", &event.creator.acc()]
]
)
}
Event::CollectCreatorFee(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("CollectCreatorFeeEvent: {}", event.creator.acc().blue())],
["timestamp", &event.timestamp.to_string()],
["creator", &event.creator.acc()],
["creator_fee", &event.creator_fee.to_string()]
]
)
}
Event::UpdateGlobalAuthority(event) => {
writeln!(
f,
"{}",
tabby![
{ color: yellow, header: bright_green, labels: magenta }
[format!("UpdateGlobalAuthorityEvent: {}", event.global.acc().blue())],
["global", &event.global.acc()],
["authority", &event.authority.acc()],
["new_authority", &event.new_authority.acc()],
["timestamp", &event.timestamp.to_string()]
]
)
}
}
}
}