1
0
mirror of https://github.com/mortdeus/legacy-cc.git synced 2026-09-22 20:04:59 +00:00
This commit is contained in:
cryptoking1106
2025-09-18 18:12:50 -03:00
parent 936e12cfc7
commit 086ec82844
141 changed files with 67491 additions and 6883 deletions

View File

@@ -0,0 +1 @@
**/target

View File

@@ -0,0 +1,80 @@
[package]
name = "grpc-raydium-pool-monitoring-rust"
version = "3.0.0"
authors = ["Mephisto"]
edition = "2021"
license = "Apache-2.0"
[dependencies]
anyhow = "1.0.62"
backoff = { version = "0.4.0", features = ["tokio"] }
bincode = "1.3.3"
borsh = { version = "1.5.3" }
bs58 = "0.5.1"
chrono = "0.4.39"
clap = { version = "4.3.0", features = ["derive"] }
env_logger = "0.11.3"
futures = "0.3.24"
hex = "0.4.3"
log = "0.4.17"
maplit = "1.0.2"
serde_json = "1.0.135"
solana-sdk = "2.1.7"
solana-entry = "=2.1.7"
solana-transaction-status = "2.1.7"
solana-program = "2.1.7"
solana-account-decoder-client-types = "2.1.7"
tokio = { version = "1.21.2", features = ["full", "rt-multi-thread", "fs"] }
openssl = { version = "0.10", features = ["vendored"] }
reqwest = { version = "0.11.27", features = ["json", "socks", "native-tls", "blocking"] }
tonic = "0.12.1"
yellowstone-grpc-client = "4.0.0"
yellowstone-grpc-proto = { version = "4.0.0", default-features = false ,features = ["plugin"] }
yellowstone-vixen-core = { git = "https://github.com/rpcpool/yellowstone-vixen" }
yellowstone-vixen-parser = { git = "https://github.com/rpcpool/yellowstone-vixen", features = ["raydium"] }
indicatif = "0.17.9"
pump_interface = { path = "./parsers/pump_interface", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_with = "3.0"
postgres = "0.19"
solana-client = "2.1.7"
spl-associated-token-account = { version = "4.0.0", features = ["no-entrypoint"] }
dotenv = "0.15"
lazy_static = "1.5.0"
shared_state = { path = "./shared_state" }
sdk = "0.1.0"
once_cell = "1.21.3"
uuid = { version = "1", features = ["v4"] }
solana-account-decoder = "=2.1.7"
spl-token = { version = "7.0.0", features = ["no-entrypoint"] }
spl-token-2022 = { version = "7.0.0", features = ["no-entrypoint"] }
spl-token-client = "=0.14.0"
jito-json-rpc-client = { path = "./json-rpc-client", package = "jito-block-engine-json-rpc-client" }
anchor-lang = "=0.31.0"
bytemuck = "1.21.0"
rand = "0.8.5"
tracing = "0.1.40"
futures-util = "0.3.30"
tokio-tungstenite = { version = "0.26.1", features = ["native-tls"] }
tokio-stream = "0.1.17"
borsh-derive = "1.5.3"
solana-transaction-status-client-types = "=2.1.7"
url = "2.3.1"
base64 = "0.22.1"
jito-protos = { path = "/home/ubuntu/shreds_sniper/shredstream-proxy/jito_protos" }
[workspace]
members = [
"json-rpc-client",
"shared_state",
]
[build-dependencies]
tonic-build = "0.10"
protobuf-src = "1.0"

View File

@@ -0,0 +1,29 @@
version: '3.9'
services:
rustapp:
container_name: rustapp
image: francescoxx/rustapp:1.0.0
build:
context: .
dockerfile: Dockerfile
args:
DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
ports:
- '8080:8080'
depends_on:
- db
db:
container_name: db
image: postgres:12
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- '5432:5432'
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata: {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,845 @@
{
"version": "3.3.0",
"name": "spl_token",
"instructions": [
{
"name": "initializeMint",
"accounts": [
{
"name": "mint",
"isMut": true,
"isSigner": false
},
{
"name": "rent",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "decimals",
"type": "u8"
},
{
"name": "mintAuthority",
"type": "publicKey"
},
{
"name": "freezeAuthority",
"type": {
"option": "publicKey"
}
}
]
},
{
"name": "initializeAccount",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": false
},
{
"name": "rent",
"isMut": false,
"isSigner": false
}
],
"args": []
},
{
"name": "initializeMultisig",
"accounts": [
{
"name": "multisig",
"isMut": true,
"isSigner": false
},
{
"name": "rent",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "m",
"type": "u8"
}
]
},
{
"name": "transfer",
"accounts": [
{
"name": "source",
"isMut": true,
"isSigner": false
},
{
"name": "destination",
"isMut": true,
"isSigner": false
},
{
"name": "authority",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
}
]
},
{
"name": "approve",
"accounts": [
{
"name": "source",
"isMut": true,
"isSigner": false
},
{
"name": "delegate",
"isMut": false,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
}
]
},
{
"name": "revoke",
"accounts": [
{
"name": "source",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": []
},
{
"name": "setAuthority",
"accounts": [
{
"name": "owned",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "signer",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "authorityType",
"type": {
"defined": "AuthorityType"
}
},
{
"name": "newAuthority",
"type": {
"option": "publicKey"
}
}
]
},
{
"name": "mintTo",
"accounts": [
{
"name": "mint",
"isMut": true,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
}
]
},
{
"name": "burn",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": true,
"isSigner": false
},
{
"name": "authority",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
}
]
},
{
"name": "closeAccount",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "destination",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": []
},
{
"name": "freezeAccount",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": []
},
{
"name": "thawAccount",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": []
},
{
"name": "transferChecked",
"accounts": [
{
"name": "source",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "destination",
"isMut": true,
"isSigner": false
},
{
"name": "authority",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
},
{
"name": "decimals",
"type": "u8"
}
]
},
{
"name": "approveChecked",
"accounts": [
{
"name": "source",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "delegate",
"isMut": false,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
},
{
"name": "decimals",
"type": "u8"
}
]
},
{
"name": "mintToChecked",
"accounts": [
{
"name": "mint",
"isMut": true,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
},
{
"name": "decimals",
"type": "u8"
}
]
},
{
"name": "burnChecked",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": true,
"isSigner": false
},
{
"name": "authority",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "amount",
"type": "u64"
},
{
"name": "decimals",
"type": "u8"
}
]
},
{
"name": "initializeAccount2",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "rent",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "owner",
"type": "publicKey"
}
]
},
{
"name": "syncNative",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
}
],
"args": []
},
{
"name": "initializeAccount3",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "mint",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "owner",
"type": "publicKey"
}
]
},
{
"name": "initializeMultisig2",
"accounts": [
{
"name": "multisig",
"isMut": true,
"isSigner": false
},
{
"name": "signer",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "m",
"type": "u8"
}
]
},
{
"name": "initializeMint2",
"accounts": [
{
"name": "mint",
"isMut": true,
"isSigner": false
}
],
"args": [
{
"name": "decimals",
"type": "u8"
},
{
"name": "mintAuthority",
"type": "publicKey"
},
{
"name": "freezeAuthority",
"type": {
"option": "publicKey"
}
}
]
},
{
"name": "getAccountDataSize",
"accounts": [
{
"name": "mint",
"isMut": false,
"isSigner": false
}
],
"args": []
},
{
"name": "initializeImmutableOwner",
"accounts": [
{
"name": "account",
"isMut": true,
"isSigner": false
}
],
"args": []
},
{
"name": "amountToUiAmount",
"accounts": [
{
"name": "mint",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "amount",
"type": "u64"
}
]
},
{
"name": "uiAmountToAmount",
"accounts": [
{
"name": "mint",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "uiAmount",
"type": "u64"
}
]
}
],
"accounts": [
{
"name": "Mint",
"type": {
"kind": "struct",
"fields": [
{
"name": "mintAuthority",
"type": {
"option": "publicKey"
}
},
{
"name": "supply",
"type": "u64"
},
{
"name": "decimals",
"type": "u8"
},
{
"name": "isInitialized",
"type": "bool"
},
{
"name": "freezeAuthority",
"type": {
"option": "publicKey"
}
}
]
}
},
{
"name": "Account",
"type": {
"kind": "struct",
"fields": [
{
"name": "mint",
"type": "publicKey"
},
{
"name": "owner",
"type": "publicKey"
},
{
"name": "amount",
"type": "u64"
},
{
"name": "delegate",
"type": {
"option": "publicKey"
}
},
{
"name": "state",
"type": {
"defined": "AccountState"
}
},
{
"name": "isNative",
"type": {
"option": "u64"
}
},
{
"name": "delegatedAmount",
"type": "u64"
},
{
"name": "closeAuthority",
"type": {
"option": "publicKey"
}
}
]
}
},
{
"name": "Multisig",
"type": {
"kind": "struct",
"fields": [
{
"name": "m",
"type": "u8"
},
{
"name": "n",
"type": "u8"
},
{
"name": "isInitialized",
"type": "bool"
},
{
"name": "signers",
"type": {
"array": [
"publicKey",
11
]
}
}
]
}
}
],
"types": [
{
"name": "AccountState",
"type": {
"kind": "enum",
"variants": [
{
"name": "Uninitialized"
},
{
"name": "Initialized"
},
{
"name": "Frozen"
}
]
}
},
{
"name": "AuthorityType",
"type": {
"kind": "enum",
"variants": [
{
"name": "MintTokens"
},
{
"name": "FreezeAccount"
},
{
"name": "AccountOwner"
},
{
"name": "CloseAccount"
}
]
}
}
],
"errors": [
{
"code": 0,
"name": "NotRentExempt",
"msg": "Lamport balance below rent-exempt threshold"
},
{
"code": 1,
"name": "InsufficientFunds",
"msg": "Insufficient funds"
},
{
"code": 2,
"name": "InvalidMint",
"msg": "Invalid Mint"
},
{
"code": 3,
"name": "MintMismatch",
"msg": "Account not associated with this Mint"
},
{
"code": 4,
"name": "OwnerMismatch",
"msg": "Owner does not match"
},
{
"code": 5,
"name": "FixedSupply",
"msg": "Fixed supply"
},
{
"code": 6,
"name": "AlreadyInUse",
"msg": "Already in use"
},
{
"code": 7,
"name": "InvalidNumberOfProvidedSigners",
"msg": "Invalid number of provided signers"
},
{
"code": 8,
"name": "InvalidNumberOfRequiredSigners",
"msg": "Invalid number of required signers"
},
{
"code": 9,
"name": "UninitializedState",
"msg": "State is unititialized"
},
{
"code": 10,
"name": "NativeNotSupported",
"msg": "Instruction does not support native tokens"
},
{
"code": 11,
"name": "NonNativeHasBalance",
"msg": "Non-native account can only be closed if its balance is zero"
},
{
"code": 12,
"name": "InvalidInstruction",
"msg": "Invalid instruction"
},
{
"code": 13,
"name": "InvalidState",
"msg": "State is invalid for requested operation"
},
{
"code": 14,
"name": "Overflow",
"msg": "Operation overflowed"
},
{
"code": 15,
"name": "AuthorityTypeNotSupported",
"msg": "Account does not support specified authority type"
},
{
"code": 16,
"name": "MintCannotFreeze",
"msg": "This token mint cannot freeze accounts"
},
{
"code": 17,
"name": "AccountFrozen",
"msg": "Account is frozen"
},
{
"code": 18,
"name": "MintDecimalsMismatch",
"msg": "The provided decimals value different from the Mint decimals"
},
{
"code": 19,
"name": "NonNativeNotSupported",
"msg": "Instruction does not support non-native tokens"
}
],
"metadata": {
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
[package]
name = "jito-block-engine-json-rpc-client"
version = "0.1.0"
edition = "2021"
description = "A sample rpc client to generate and send requests to jito block engine server"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = "0.1.68"
bincode = "1.3.3"
log = "0.4.17"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0.189", features = ["derive"] }
serde_json = "1.0.107"
solana-rpc-client = "=2.1.7"
solana-rpc-client-api = "=2.1.7"
solana-sdk = "=2.1.7"
solana-transaction-status = "=2.1.7"
thiserror = "1.0.40"
tokio = { version = "1", features = ["rt-multi-thread"] }
[dev-dependencies]
solana-program = "=2.1.7"

View File

@@ -0,0 +1,3 @@
# json-rpc-client
This repository contains code to communicate with Jito's Block-Engine.

View File

@@ -0,0 +1,5 @@
edition = "2021" # required by rust-analyzer
imports_granularity="Crate"
format_code_in_doc_comments = true
error_on_unformatted = true
group_imports = "StdExternalCrate"

View File

@@ -0,0 +1,125 @@
pub use reqwest;
use solana_rpc_client_api::{client_error::ErrorKind, request};
use solana_sdk::{
signature::SignerError, transaction::TransactionError, transport::TransportError,
};
use thiserror::Error as ThisError;
use crate::jsonrpc_client::request::RpcRequest;
#[derive(ThisError, Debug)]
#[error("{kind}")]
pub struct Error {
pub request: Option<RpcRequest>,
#[source]
pub kind: ErrorKind,
}
impl Error {
pub fn new_with_request(kind: ErrorKind, request: RpcRequest) -> Self {
Self {
request: Some(request),
kind,
}
}
pub fn into_with_request(self, request: RpcRequest) -> Self {
Self {
request: Some(request),
..self
}
}
pub fn request(&self) -> Option<&RpcRequest> {
self.request.as_ref()
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub fn get_transaction_error(&self) -> Option<TransactionError> {
self.kind.get_transaction_error()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Self {
request: None,
kind,
}
}
}
impl From<TransportError> for Error {
fn from(err: TransportError) -> Self {
Self {
request: None,
kind: err.into(),
}
}
}
impl From<Error> for TransportError {
fn from(client_error: Error) -> Self {
client_error.kind.into()
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self {
request: None,
kind: err.into(),
}
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Self {
request: None,
kind: err.into(),
}
}
}
impl From<request::RpcError> for Error {
fn from(err: request::RpcError) -> Self {
Self {
request: None,
kind: err.into(),
}
}
}
impl From<serde_json::error::Error> for Error {
fn from(err: serde_json::error::Error) -> Self {
Self {
request: None,
kind: err.into(),
}
}
}
impl From<SignerError> for Error {
fn from(err: SignerError) -> Self {
Self {
request: None,
kind: err.into(),
}
}
}
impl From<TransactionError> for Error {
fn from(err: TransactionError) -> Self {
Self {
request: None,
kind: err.into(),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -0,0 +1,218 @@
use std::{
sync::{
atomic::{AtomicU64, Ordering},
Arc, RwLock,
},
time::{Duration, Instant},
};
use async_trait::async_trait;
use log::debug;
use reqwest::{
self,
header::{CONTENT_TYPE, RETRY_AFTER},
StatusCode,
};
use solana_rpc_client_api::{
custom_error,
error_object::RpcErrorObject,
request::{RpcError, RpcResponseErrorData},
response::RpcSimulateTransactionResult,
};
use tokio::time::sleep;
use crate::jsonrpc_client::{client_error::Result, request::RpcRequest, rpc_sender::RpcSender};
pub struct HttpSender {
client: Arc<reqwest::Client>,
url: String,
request_id: AtomicU64,
stats: RwLock<solana_rpc_client::rpc_sender::RpcTransportStats>,
}
/// Nonblocking [`RpcSender`] over HTTP.
impl HttpSender {
/// Create an HTTP RPC sender.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899". The sender has a default timeout of 30 seconds.
pub fn new<U: ToString>(url: U) -> Self {
Self::new_with_timeout(url, Duration::from_secs(30))
}
/// Create an HTTP RPC sender.
///
/// The URL is an HTTP URL, usually for port 8899.
pub fn new_with_timeout<U: ToString>(url: U, timeout: Duration) -> Self {
let client = Arc::new(
reqwest::Client::builder()
.timeout(timeout)
.pool_idle_timeout(timeout)
.build()
.expect("build rpc client"),
);
Self {
client,
url: url.to_string(),
request_id: AtomicU64::new(0),
stats: RwLock::new(solana_rpc_client::rpc_sender::RpcTransportStats::default()),
}
}
}
struct StatsUpdater<'a> {
stats: &'a RwLock<solana_rpc_client::rpc_sender::RpcTransportStats>,
request_start_time: Instant,
rate_limited_time: Duration,
}
impl<'a> StatsUpdater<'a> {
fn new(stats: &'a RwLock<solana_rpc_client::rpc_sender::RpcTransportStats>) -> Self {
Self {
stats,
request_start_time: Instant::now(),
rate_limited_time: Duration::default(),
}
}
fn add_rate_limited_time(&mut self, duration: Duration) {
self.rate_limited_time += duration;
}
}
impl Drop for StatsUpdater<'_> {
fn drop(&mut self) {
let mut stats = self.stats.write().unwrap();
stats.request_count += 1;
stats.elapsed_time += Instant::now().duration_since(self.request_start_time);
stats.rate_limited_time += self.rate_limited_time;
}
}
#[async_trait]
impl RpcSender for HttpSender {
fn get_transport_stats(&self) -> solana_rpc_client::rpc_sender::RpcTransportStats {
self.stats.read().unwrap().clone()
}
async fn send(
&self,
request: RpcRequest,
params: serde_json::Value,
) -> Result<serde_json::Value> {
let mut stats_updater = StatsUpdater::new(&self.stats);
let request_id = self.request_id.fetch_add(1, Ordering::Relaxed);
let request_json = request.build_request_json(request_id, params).to_string();
let mut too_many_requests_retries = 5;
loop {
let response = {
let client = self.client.clone();
let request_json = request_json.clone();
client
.post(&self.url)
.header(CONTENT_TYPE, "application/json")
.body(request_json)
.send()
.await
}?;
if !response.status().is_success() {
if response.status() == StatusCode::TOO_MANY_REQUESTS
&& too_many_requests_retries > 0
{
let mut duration = Duration::from_millis(500);
if let Some(retry_after) = response.headers().get(RETRY_AFTER) {
if let Ok(retry_after) = retry_after.to_str() {
if let Ok(retry_after) = retry_after.parse::<u64>() {
if retry_after < 120 {
duration = Duration::from_secs(retry_after);
}
}
}
}
too_many_requests_retries -= 1;
debug!(
"Too many requests: server responded with {:?}, {} retries left, pausing for {:?}",
response, too_many_requests_retries, duration
);
sleep(duration).await;
stats_updater.add_rate_limited_time(duration);
continue;
}
return Err(response.error_for_status().unwrap_err().into());
}
let mut json = response.json::<serde_json::Value>().await?;
if json["error"].is_object() {
return match serde_json::from_value::<RpcErrorObject>(json["error"].clone()) {
Ok(rpc_error_object) => {
let data = match rpc_error_object.code {
solana_rpc_client_api::custom_error::JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE => {
match serde_json::from_value::<RpcSimulateTransactionResult>(json["error"]["data"].clone()) {
Ok(data) => RpcResponseErrorData::SendTransactionPreflightFailure(data),
Err(err) => {
debug!("Failed to deserialize RpcSimulateTransactionResult: {:?}", err);
RpcResponseErrorData::Empty
}
}
},
custom_error::JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY => {
match serde_json::from_value::<custom_error::NodeUnhealthyErrorData>(json["error"]["data"].clone()) {
Ok(custom_error::NodeUnhealthyErrorData {num_slots_behind}) => RpcResponseErrorData::NodeUnhealthy {num_slots_behind},
Err(_err) => {
RpcResponseErrorData::Empty
}
}
},
_ => RpcResponseErrorData::Empty
};
Err(RpcError::RpcResponseError {
code: rpc_error_object.code,
message: rpc_error_object.message,
data,
}
.into())
}
Err(err) => Err(RpcError::RpcRequestError(format!(
"Failed to deserialize RPC error response: {} [{}]",
serde_json::to_string(&json["error"]).unwrap(),
err
))
.into()),
};
}
return Ok(json["result"].take());
}
}
fn url(&self) -> String {
self.url.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn http_sender_on_tokio_multi_thread() {
let http_sender = HttpSender::new("http://localhost:1234".to_string());
let _ = http_sender
.send(RpcRequest::GetTipAccounts, serde_json::Value::Null)
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn http_sender_on_tokio_current_thread() {
let http_sender = HttpSender::new("http://localhost:1234".to_string());
let _ = http_sender
.send(RpcRequest::GetTipAccounts, serde_json::Value::Null)
.await;
}
}

View File

@@ -0,0 +1,5 @@
pub mod client_error;
pub mod http_sender;
pub mod request;
pub mod rpc_client;
pub mod rpc_sender;

View File

@@ -0,0 +1,55 @@
use std::fmt;
use serde_json::{json, Value};
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum RpcRequest {
Custom { method: &'static str },
GetBundlesStatuses,
GetTipAccounts,
SendBundle,
}
impl fmt::Display for RpcRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let method = match self {
RpcRequest::Custom { method } => method,
RpcRequest::GetBundlesStatuses => "getBundleStatuses",
RpcRequest::GetTipAccounts => "getTipAccounts",
RpcRequest::SendBundle => "sendBundle",
};
write!(f, "{method}")
}
}
impl RpcRequest {
pub fn build_request_json(self, id: u64, params: Value) -> Value {
let jsonrpc = "2.0";
json!({
"jsonrpc": jsonrpc,
"id": id,
"method": format!("{self}"),
"params": params,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_request_json() {
let test_request = RpcRequest::GetTipAccounts;
let request = test_request.build_request_json(1, json!([]));
assert_eq!(request["method"], "getTipAccounts");
assert_eq!(request["params"], json!([]));
let test_request = RpcRequest::GetBundlesStatuses;
let addr = json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx");
let request = test_request.build_request_json(1, json!([addr]));
assert_eq!(request["method"], "getBundleStatuses");
assert_eq!(request["params"], json!([addr]));
}
}

View File

@@ -0,0 +1,406 @@
use std::time::Duration;
use bincode::serialize;
use log::*;
use serde_json::{json, Value};
use solana_rpc_client::{
rpc_client::{RpcClientConfig, SerializableTransaction},
rpc_sender::RpcTransportStats,
};
use solana_rpc_client_api::{
client_error::ErrorKind as ClientErrorKind, request::RpcError, response::Response,
};
use solana_sdk::{bs58, commitment_config::CommitmentConfig};
use solana_transaction_status::UiTransactionEncoding;
use crate::jsonrpc_client::{
client_error,
client_error::{Error as ClientError, Result as ClientResult},
http_sender::HttpSender,
request::RpcRequest,
rpc_sender::*,
};
pub type RpcResult<T> = client_error::Result<Response<T>>;
/// A client of a remote Jito block engine node.
///
/// `RpcClient` communicates with a block engine node over [JSON-RPC]
/// It is the primary Rust interface for querying and transacting with the network
/// from external programs.
///
/// This is modeled very closely with the solan RpcClient with similar error types.
/// You can treat the client similar to the Solana RpcClient with the difference being
/// the RpcClient supports the block engine apis.
///
/// The client can be used with jito block engine proxy server for authentication
/// The client can be used as is to send requests unauthenticated to the jito block engine as well
///
/// Please note that the commitment level is not used at the moment. Support will be added
/// later on to specify and use the commitment levels.
pub struct RpcClient {
sender: Box<dyn RpcSender + Send + Sync + 'static>,
config: RpcClientConfig,
}
impl RpcClient {
/// Create an `RpcClient` from an [`RpcSender`] and an [`RpcClientConfig`].
///
/// This is the basic constructor, allowing construction with any type of
/// `RpcSender`. Most applications should use one of the other constructors,
/// such as [`RpcClient::new`], [`RpcClient::new_with_commitment`] or
/// [`RpcClient::new_with_timeout`].
pub fn new_sender<T: RpcSender + Send + Sync + 'static>(
sender: T,
config: RpcClientConfig,
) -> Self {
Self {
sender: Box::new(sender),
config,
}
}
/// Create an HTTP `RpcClient`.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// The client has a default timeout of 30 seconds, and a default [commitment
/// level][cl] of [`Finalized`](CommitmentLevel::Finalized).
///
/// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment
///
/// # Examples
///
/// ```
/// # use jito_block_engine_json_rpc_client::jsonrpc_client::rpc_client::RpcClient;
/// let url = "http://localhost:8899".to_string();
/// let client = RpcClient::new(url);
/// ```
pub fn new(url: String) -> Self {
Self::new_with_commitment(url, CommitmentConfig::default())
}
/// Create an HTTP `RpcClient` with specified [commitment level][cl].
///
/// Please note the client is not currently implemented to support commitment level configs
///
/// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// The client has a default timeout of 30 seconds, and a user-specified
/// [`CommitmentLevel`] via [`CommitmentConfig`].
///
/// # Examples
///
/// # use solana_sdk::commitment_config::CommitmentConfig;
/// # use jito_block_engine_json_rpc_client::jsonrpc_client::rpc_client::RpcClient;
/// let url = "http://localhost:8899".to_string();
/// let commitment_config = CommitmentConfig::processed();
/// let client = RpcClient::new_with_commitment(url, commitment_config);
fn new_with_commitment(url: String, commitment_config: CommitmentConfig) -> Self {
Self::new_sender(
HttpSender::new(url),
RpcClientConfig::with_commitment(commitment_config),
)
}
/// Create an HTTP `RpcClient` with specified timeout.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// The client has and a default [commitment level][cl] of
/// [`Finalized`](CommitmentLevel::Finalized).
///
/// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment
///
/// # Examples
///
/// ```
/// # use std::time::Duration;
/// # use jito_block_engine_json_rpc_client::jsonrpc_client::rpc_client::RpcClient;
/// let url = "http://localhost::8899".to_string();
/// let timeout = Duration::from_secs(1);
/// let client = RpcClient::new_with_timeout(url, timeout);
/// ```
pub fn new_with_timeout(url: String, timeout: Duration) -> Self {
Self::new_sender(
HttpSender::new_with_timeout(url, timeout),
RpcClientConfig::with_commitment(CommitmentConfig::default()),
)
}
/// Get the configured url of the client's sender
pub fn url(&self) -> String {
self.sender.url()
}
/// Get the configured default [commitment level][cl].
///
/// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment
///
/// The commitment config may be specified during construction, and
/// determines how thoroughly committed a transaction must be when waiting
/// for its confirmation or otherwise checking for confirmation. If not
/// specified, the default commitment level is
/// [`Finalized`](CommitmentLevel::Finalized).
///
/// The default commitment level is overridden when calling methods that
/// explicitly provide a [`CommitmentConfig`], like
/// [`RpcClient::confirm_transaction_with_commitment`].
pub fn commitment(&self) -> CommitmentConfig {
self.config.commitment_config
}
/// Submits a bundle of signed transactions to the network.
///
/// This returns a bundle_id on success and will return an error
/// code on failure. The error code will only be on the basic
/// validation of the bundle and publishing the bundle to the mempool
/// For the bundle status regarding whether it landed or not, get_bundle_statuses
/// should be used with the bundle id.
///
/// Example excerpt can be as below
///
/// use jito_block_engine_json_rpc_client::jsonrpc_client::rpc_client::RpcClient;
/// use solana_program::hash::Hash;
/// use solana_sdk::{pubkey::Pubkey, signer::keypair::Keypair};
///
/// let base = 0;
/// let MAX_BUNDLE_LEN = 5;
/// let searcher_keypair = Keypair::new();
/// let recent_blockhash = Hash::new_unique();
///
/// let mut bundle: Vec<_> = (0..(MAX_BUNDLE_LEN) as u64)
/// .map(|amount| {
/// VersionedTransaction::from(system_transaction::transfer(
/// &searcher_keypair,
/// &searcher_keypair.pubkey(),
/// base + amount,
/// recent_blockhash,
/// ))
/// })
/// .collect();
///
/// let rpc_client = RpcClient::new(SERVER_URL.to_owned());
/// let response = rpc_client.send_bundle(&bundle).await;
pub async fn send_bundle(
&self,
transactions: &[impl SerializableTransaction],
) -> ClientResult<String> {
let mut serialized_encoded: Vec<String> = Vec::with_capacity(transactions.len());
for transaction in transactions {
let encoding = self.default_cluster_transaction_encoding().await?;
serialized_encoded.push(serialize_and_encode(transaction, encoding)?);
}
//The bundle may or may
// not have been submitted to the cluster, so callers should verify the success of
// the correct transaction signature independently.
match self
.send(RpcRequest::SendBundle, json!([serialized_encoded]))
.await
{
Ok(signature_base58_str) => ClientResult::Ok(signature_base58_str),
Err(err) => {
if let ClientErrorKind::RpcError(RpcError::RpcResponseError {
code, message, ..
}) = &err.kind
{
debug!("{} {}", code, message);
}
Err(err)
}
}
}
async fn default_cluster_transaction_encoding(
&self,
) -> Result<UiTransactionEncoding, RpcError> {
Ok(UiTransactionEncoding::Base58)
}
/// Gets the statuses of a list of bundle ids.
///
/// Returns the statuses of a list of signatures. Each signature must be a bundle_id.
/// bundle ids are sha256 hashes of their tx signatures (we get it after a sendBundle)
/// This method currently will provide information regarding whether the bundle
/// landed or not.
/// The behavior is similar to the solana rpc method getSignatureStatuses
/// https://docs.solana.com/api/http#getsignaturestatuses
///
/// If the bundle_id is not found or the all of the transactions in the bundle has not landed,
/// we return null. If found and landed, we return the context information including the slot
/// at which the request was made and result with the bundle_id(s) and the transactions with the
/// slot and confirmation status. At this point, its assumed that all transactions within a bundle
/// will have the same slot number and confirmation status.
///
/// The confirmation status of a bundle is the confirmation status of the transactions.
/// This api does not provide a commitment level to configure, but will return the commitment level
/// as returned by the rpc. The rpc used to fetch bulk transaction status does not provide a commitment
/// level configuration option either.
///
/// Example excerpt can be as below
///
/// use jito_block_engine_json_rpc_client::jsonrpc_client::rpc_client::RpcClient;
///
/// let SERVER_URL = "http://localhost:8899";
/// let bundle_id = "bundle_id".to_owned();
///
/// let rpc_client = RpcClient::new(SERVER_URL.to_owned());
/// let response = rpc_client.get_bundle_statuses(&[bundle_id.clone()]).await;
pub async fn get_bundle_statuses(
&self,
signatures: &[String],
) -> RpcResult<Vec<serde_json::Value>> {
self.send(RpcRequest::GetBundlesStatuses, json!([signatures]))
.await
}
/// Returns the tip accounts to be used for tip payments.
pub async fn get_tip_accounts(&self) -> ClientResult<Vec<String>> {
self.send(RpcRequest::GetTipAccounts, Value::Null).await
}
pub async fn send<T>(&self, request: RpcRequest, params: Value) -> ClientResult<T>
where
T: serde::de::DeserializeOwned,
{
assert!(params.is_array() || params.is_null());
let response = self
.sender
.send(request, params)
.await
.map_err(|err| err.into_with_request(request))?;
serde_json::from_value(response)
.map_err(|err| ClientError::new_with_request(err.into(), request))
}
pub fn get_transport_stats(&self) -> RpcTransportStats {
self.sender.get_transport_stats()
}
}
fn serialize_and_encode<T>(input: &T, encoding: UiTransactionEncoding) -> ClientResult<String>
where
T: serde::ser::Serialize,
{
let serialized = serialize(input)
.map_err(|e| ClientErrorKind::Custom(format!("Serialization failed: {e}")))?;
let encoded = match encoding {
UiTransactionEncoding::Base58 => bs58::encode(serialized).into_string(),
_ => {
return Err(ClientErrorKind::Custom(format!(
"unsupported encoding: {encoding}. Supported encodings: base58"
))
.into())
}
};
Ok(encoded)
}
// Sample tests. Can use these as a reference point on how to use the api and what to expect
#[cfg(test)]
mod rpc_client_tests {
use solana_program::hash::Hash;
use solana_sdk::{
pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, system_transaction,
transaction::VersionedTransaction,
};
use crate::jsonrpc_client::rpc_client::RpcClient;
// Use the proxy server url here.
const SERVER_URL: &str = "http://0.0.0.0:8080/api/v1/bundles";
#[tokio::test]
pub async fn get_tip_accounts() {
// Let's try the same with the rpc client
let rpc_client = RpcClient::new(SERVER_URL.to_owned());
let tip_accounts = rpc_client.get_tip_accounts().await;
// Sample output. Pick only randomly to not have contention
// ["9ttgPBBhRYFuQccdR1DSnb7hydsWANoDsV3P9kaGMCEh",
// "EoW3SUQap7ZeynXQ2QJ847aerhxbPVr843uMeTfc9dxM",
// "4xgEmT58RwTNsF5xm2RMYCnR1EVukdK8a1i2qFjnJFu3",
// "B1mrQSpdeMU9gCvkJ6VsXVVoYjRGkNA7TtjMyqxrhecH",
// "aTtUk2DHgLhKZRDjePq6eiHRKC1XXFMBiSUfQ2JNDbN",
// "9n3d1K5YD2vECAbRFhFFGYNNjiXtHXJWn9F31t89vsAV",
// "ARTtviJkLLt6cHGQDydfo1Wyk6M4VGZdKZ2ZhdnJL336",
// "E2eSqe33tuhAHKTrwky5uEjaVqnb2T9ns6nHHUrN8588"]
println!("{:?}", tip_accounts);
}
#[tokio::test]
pub async fn send_bundle() {
let rpc_client = RpcClient::new(SERVER_URL.to_owned());
// Use your own keypair to sign
let signer_keypair = Keypair::new();
// Get the latest blockhash from solana cluster. Can use https://docs.solana.com/api/http#getlatestblockhash
let recent_blockhash = Hash::new_unique();
// Use the get_tip_accounts to randomly select a tip account to send tips to
let tip_account = Pubkey::try_from("DCN82qDxJAQuSqHhv2BJuAgi41SPeKZB5ioBCTMNDrCC").unwrap();
let mut bundle: Vec<_> = vec![VersionedTransaction::from(system_transaction::transfer(
&signer_keypair,
&signer_keypair.pubkey(),
10000,
recent_blockhash,
))];
// Add the tip
bundle.push(VersionedTransaction::from(system_transaction::transfer(
&signer_keypair,
&tip_account,
10000,
recent_blockhash,
)));
let response = rpc_client.send_bundle(&bundle).await;
// If successful, the bundle_id can be retrieved. Else, an error code will be provided
println!("{:?}", response);
}
#[tokio::test]
pub async fn get_bundle_statuses() {
let rpc_client = RpcClient::new(SERVER_URL.to_owned());
// Use the bundle id you got from send_bundle
let bundle_id =
"6e4b90284778a40633b56e4289202ea79e62d2296bb3d45398bb93f6c9ec083d".to_owned();
let response = rpc_client.get_bundle_statuses(&[bundle_id]).await;
// Sample success output:
// Response {
// context: RpcResponseContext {
// slot: 0, api_version: None },
// value: [Object {
// "bundle_id": String("6e4b90284778a40633b56e4289202ea79e62d2296bb3d45398bb93f6c9ec083d"),
// "transactions": Array [String("4DGCuaKc2oue4Z8YC6mBwyg3oPAFG64BfxDtMbqDU3Du9zr26oVSuZcjSnJqTnHnKYFJ4AdPuq5kUrWKwTFLKtW6"),
// String("srrgfKABYeaKazZjBmpuPKySJ8qgqezYaCdDnB9nhED5CFhviZ1wgcs5vEKnAK9L2ytRauWG9czGoKRxajpZ1YR")],
// "slot": Number(240632575),
// "confirmation_status": String("finalized"),
// "err": Object {"Ok": Null}}] }
//
// Sample retryable error output:
// Response {
// context: RpcResponseContext {
// slot: 0, api_version: None },
// value: [Object {
// "bundle_id": String("6e4b90284778a40633b56e4289202ea79e62d2296bb3d45398bb93f6c9ec083d"),
// "transactions": Array [String("4DGCuaKc2oue4Z8YC6mBwyg3oPAFG64BfxDtMbqDU3Du9zr26oVSuZcjSnJqTnHnKYFJ4AdPuq5kUrWKwTFLKtW6"),
// String("srrgfKABYeaKazZjBmpuPKySJ8qgqezYaCdDnB9nhED5CFhviZ1wgcs5vEKnAK9L2ytRauWG9czGoKRxajpZ1YR")],
// "slot": Number(612529),
// "confirmation_status": Null,
// "err": Object {"Err": Object {"Retryable": String("Failed to retrieve information from solana cluster")}}}] }
// If unknown bundle, the response would be null
println!("{:?}", response);
}
}

View File

@@ -0,0 +1,21 @@
use async_trait::async_trait;
use solana_rpc_client::rpc_sender::RpcTransportStats;
use crate::jsonrpc_client::{client_error::Result, request::RpcRequest};
/// A transport for RPC calls.
///
/// `RpcSender` implements the underlying transport of requests to, and
/// responses from, a Solana node, and is used primarily by [`RpcClient`].
///
/// [`RpcClient`]: crate::rpc_client::RpcClient
#[async_trait]
pub trait RpcSender {
async fn send(
&self,
request: RpcRequest,
params: serde_json::Value,
) -> Result<serde_json::Value>;
fn get_transport_stats(&self) -> RpcTransportStats;
fn url(&self) -> String;
}

View File

@@ -0,0 +1 @@
pub mod jsonrpc_client;

View File

@@ -0,0 +1 @@
{"rustc_fingerprint":16894786775884202872,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.1 (4eb161250 2025-03-15)\nbinary: rustc\ncommit-hash: 4eb161250e340c8f48f66e2b929ef4a5bed7c181\ncommit-date: 2025-03-15\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.1\nLLVM version: 19.1.7\n","stderr":""},"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}

View File

@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

View File

@@ -0,0 +1 @@
/pumpfun_indexer/json-rpc-client/target/release/libjito_block_engine_json_rpc_client.rlib: /pumpfun_indexer/json-rpc-client/src/jsonrpc_client/client_error.rs /pumpfun_indexer/json-rpc-client/src/jsonrpc_client/http_sender.rs /pumpfun_indexer/json-rpc-client/src/jsonrpc_client/mod.rs /pumpfun_indexer/json-rpc-client/src/jsonrpc_client/request.rs /pumpfun_indexer/json-rpc-client/src/jsonrpc_client/rpc_client.rs /pumpfun_indexer/json-rpc-client/src/jsonrpc_client/rpc_sender.rs /pumpfun_indexer/json-rpc-client/src/lib.rs

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
[package]
name = "pump_interface"
version = "0.1.0"
edition = "2021"
[dependencies.borsh]
version = "^0.10"
[dependencies.num-derive]
version = "^0.3"
[dependencies.num-traits]
version = "^0.2"
[dependencies.serde]
optional = true
version = "^1.0"
[dependencies.solana-program]
version = "^2.1.7"
[dependencies.thiserror]
version = "^1.0"
[dependencies.strum]
version = "0.26.3"
[dependencies.strum_macros]
version = "0.26.4"
[dependencies.Inflector]
version = "=0.11.4"

View File

@@ -0,0 +1,84 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::pubkey::Pubkey;
pub const GLOBAL_ACCOUNT_DISCM: [u8; 8] = [167, 232, 232, 177, 200, 108, 114, 127];
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Global {
pub initialized: bool,
pub authority: Pubkey,
pub fee_recipient: Pubkey,
pub initial_virtual_token_reserves: u64,
pub initial_virtual_sol_reserves: u64,
pub initial_real_token_reserves: u64,
pub token_total_supply: u64,
pub fee_basis_points: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GlobalAccount(pub Global);
impl GlobalAccount {
pub fn deserialize(buf: &[u8]) -> std::io::Result<Self> {
use std::io::Read;
let mut reader = buf;
let mut maybe_discm = [0u8; 8];
reader.read_exact(&mut maybe_discm)?;
if maybe_discm != GLOBAL_ACCOUNT_DISCM {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"discm does not match. Expected: {:?}. Received: {:?}",
GLOBAL_ACCOUNT_DISCM, maybe_discm
),
));
}
Ok(Self(Global::deserialize(&mut reader)?))
}
pub fn serialize<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
writer.write_all(&GLOBAL_ACCOUNT_DISCM)?;
self.0.serialize(&mut writer)
}
pub fn try_to_vec(&self) -> std::io::Result<Vec<u8>> {
let mut data = Vec::new();
self.serialize(&mut data)?;
Ok(data)
}
}
pub const BONDING_CURVE_ACCOUNT_DISCM: [u8; 8] = [23, 183, 248, 55, 96, 216, 172, 96];
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BondingCurve {
pub virtual_token_reserves: u64,
pub virtual_sol_reserves: u64,
pub real_token_reserves: u64,
pub real_sol_reserves: u64,
pub token_total_supply: u64,
pub complete: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BondingCurveAccount(pub BondingCurve);
impl BondingCurveAccount {
pub fn deserialize(buf: &[u8]) -> std::io::Result<Self> {
use std::io::Read;
let mut reader = buf;
let mut maybe_discm = [0u8; 8];
reader.read_exact(&mut maybe_discm)?;
if maybe_discm != BONDING_CURVE_ACCOUNT_DISCM {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"discm does not match. Expected: {:?}. Received: {:?}",
BONDING_CURVE_ACCOUNT_DISCM, maybe_discm
),
));
}
Ok(Self(BondingCurve::deserialize(&mut reader)?))
}
pub fn serialize<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
writer.write_all(&BONDING_CURVE_ACCOUNT_DISCM)?;
self.0.serialize(&mut writer)
}
pub fn try_to_vec(&self) -> std::io::Result<Vec<u8>> {
let mut data = Vec::new();
self.serialize(&mut data)?;
Ok(data)
}
}

View File

@@ -0,0 +1,48 @@
#![allow(non_local_definitions)]
use solana_program::{
decode_error::DecodeError,
msg,
program_error::{PrintProgramError, ProgramError},
};
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, Error, num_derive::FromPrimitive, PartialEq)]
pub enum PumpError {
#[error("The given account is not authorized to execute this instruction.")]
NotAuthorized = 6000,
#[error("The program is already initialized.")]
AlreadyInitialized = 6001,
#[error("slippage: Too much SOL required to buy the given amount of tokens.")]
TooMuchSolRequired = 6002,
#[error("slippage: Too little SOL received to sell the given amount of tokens.")]
TooLittleSolReceived = 6003,
#[error("The mint does not match the bonding curve.")]
MintDoesNotMatchBondingCurve = 6004,
#[error("The bonding curve has completed and liquidity migrated to raydium.")]
BondingCurveComplete = 6005,
#[error("The bonding curve has not completed.")]
BondingCurveNotComplete = 6006,
#[error("The program is not initialized.")]
NotInitialized = 6007,
}
impl From<PumpError> for ProgramError {
fn from(e: PumpError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl<T> DecodeError<T> for PumpError {
fn type_of() -> &'static str {
"PumpError"
}
}
impl PrintProgramError for PumpError {
fn print<E>(&self)
where
E: 'static
+ std::error::Error
+ DecodeError<E>
+ PrintProgramError
+ num_traits::FromPrimitive,
{
msg!(&self.to_string());
}
}

View File

@@ -0,0 +1,134 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::pubkey::Pubkey;
pub const CREATE_EVENT_EVENT_DISCM: [u8; 8] = [27, 114, 169, 77, 222, 235, 99, 118];
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct CreateEvent {
name: String,
symbol: String,
uri: String,
mint: Pubkey,
bonding_curve: Pubkey,
user: Pubkey,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CreateEventEvent(pub CreateEvent);
impl BorshSerialize for CreateEventEvent {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
CREATE_EVENT_EVENT_DISCM.serialize(writer)?;
self.0.serialize(writer)
}
}
impl CreateEventEvent {
pub fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
let maybe_discm = <[u8; 8]>::deserialize(buf)?;
if maybe_discm != CREATE_EVENT_EVENT_DISCM {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"discm does not match. Expected: {:?}. Received: {:?}",
CREATE_EVENT_EVENT_DISCM, maybe_discm
),
));
}
Ok(Self(CreateEvent::deserialize(buf)?))
}
}
pub const TRADE_EVENT_EVENT_DISCM: [u8; 8] = [189, 219, 127, 211, 78, 230, 97, 238];
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct TradeEvent {
mint: Pubkey,
sol_amount: u64,
token_amount: u64,
is_buy: bool,
user: Pubkey,
timestamp: i64,
virtual_sol_reserves: u64,
virtual_token_reserves: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TradeEventEvent(pub TradeEvent);
impl BorshSerialize for TradeEventEvent {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
TRADE_EVENT_EVENT_DISCM.serialize(writer)?;
self.0.serialize(writer)
}
}
impl TradeEventEvent {
pub fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
let maybe_discm = <[u8; 8]>::deserialize(buf)?;
if maybe_discm != TRADE_EVENT_EVENT_DISCM {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"discm does not match. Expected: {:?}. Received: {:?}",
TRADE_EVENT_EVENT_DISCM, maybe_discm
),
));
}
Ok(Self(TradeEvent::deserialize(buf)?))
}
}
pub const COMPLETE_EVENT_EVENT_DISCM: [u8; 8] = [95, 114, 97, 156, 212, 46, 152, 8];
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct CompleteEvent {
user: Pubkey,
mint: Pubkey,
bonding_curve: Pubkey,
timestamp: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CompleteEventEvent(pub CompleteEvent);
impl BorshSerialize for CompleteEventEvent {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
COMPLETE_EVENT_EVENT_DISCM.serialize(writer)?;
self.0.serialize(writer)
}
}
impl CompleteEventEvent {
pub fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
let maybe_discm = <[u8; 8]>::deserialize(buf)?;
if maybe_discm != COMPLETE_EVENT_EVENT_DISCM {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"discm does not match. Expected: {:?}. Received: {:?}",
COMPLETE_EVENT_EVENT_DISCM, maybe_discm
),
));
}
Ok(Self(CompleteEvent::deserialize(buf)?))
}
}
pub const SET_PARAMS_EVENT_EVENT_DISCM: [u8; 8] = [223, 195, 159, 246, 62, 48, 143, 131];
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct SetParamsEvent {
fee_recipient: Pubkey,
initial_virtual_token_reserves: u64,
initial_virtual_sol_reserves: u64,
initial_real_token_reserves: u64,
token_total_supply: u64,
fee_basis_points: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SetParamsEventEvent(pub SetParamsEvent);
impl BorshSerialize for SetParamsEventEvent {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
SET_PARAMS_EVENT_EVENT_DISCM.serialize(writer)?;
self.0.serialize(writer)
}
}
impl SetParamsEventEvent {
pub fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
let maybe_discm = <[u8; 8]>::deserialize(buf)?;
if maybe_discm != SET_PARAMS_EVENT_EVENT_DISCM {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"discm does not match. Expected: {:?}. Received: {:?}",
SET_PARAMS_EVENT_EVENT_DISCM, maybe_discm
),
));
}
Ok(Self(SetParamsEvent::deserialize(buf)?))
}
}

View File

@@ -0,0 +1,9 @@
solana_program::declare_id!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
pub mod accounts;
pub use accounts::*;
pub mod instructions;
pub use instructions::*;
pub mod errors;
pub use errors::*;
pub mod events;
pub use events::*;

View File

@@ -0,0 +1,37 @@
// src/serializer.rs
#[cfg(feature = "serde")]
use serde::Deserialize;
#[cfg(feature = "serde")]
pub fn serialize_u128_as_string<S>(value: &u128, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&value.to_string())
}
#[cfg(feature = "serde")]
pub fn deserialize_u128_as_string<'de, D>(deserializer: D) -> Result<u128, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse::<u128>().map_err(serde::de::Error::custom)
}
#[cfg(feature = "serde")]
pub fn serialize_i128_as_string<S>(value: &i128, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&value.to_string())
}
#[cfg(feature = "serde")]
pub fn deserialize_i128_as_string<'de, D>(deserializer: D) -> Result<i128, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse::<i128>().map_err(serde::de::Error::custom)
}

View File

@@ -0,0 +1,140 @@
#[cfg(feature = "serde")]
use crate::serializer::{
deserialize_i128_as_string, deserialize_u128_as_string, serialize_i128_as_string,
serialize_u128_as_string,
};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::pubkey::Pubkey;
#[derive(Default, Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OpenPositionBumps {
pub position_bump: u8,
}
#[derive(Default, Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OpenPositionWithMetadataBumps {
pub position_bump: u8,
pub metadata_bump: u8,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PositionRewardInfo {
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serialize_u128_as_string",
deserialize_with = "deserialize_u128_as_string"
)
)]
pub growth_inside_checkpoint: u128,
pub amount_owed: u64,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tick {
pub initialized: bool,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serialize_i128_as_string",
deserialize_with = "deserialize_i128_as_string"
)
)]
pub liquidity_net: i128,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serialize_u128_as_string",
deserialize_with = "deserialize_u128_as_string"
)
)]
pub liquidity_gross: u128,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serialize_u128_as_string",
deserialize_with = "deserialize_u128_as_string"
)
)]
pub fee_growth_outside_a: u128,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serialize_u128_as_string",
deserialize_with = "deserialize_u128_as_string"
)
)]
pub fee_growth_outside_b: u128,
pub reward_growths_outside: [u128; 3],
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WhirlpoolRewardInfo {
pub mint: Pubkey,
pub vault: Pubkey,
pub authority: Pubkey,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serialize_u128_as_string",
deserialize_with = "deserialize_u128_as_string"
)
)]
pub emissions_per_second_x64: u128,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serialize_u128_as_string",
deserialize_with = "deserialize_u128_as_string"
)
)]
pub growth_global_x64: u128,
}
#[derive(Default, Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WhirlpoolBumps {
pub whirlpool_bump: u8,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RemainingAccountsSlice {
pub accounts_type: AccountsType,
pub length: u8,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RemainingAccountsInfo {
pub slices: Vec<RemainingAccountsSlice>,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CurrIndex {
Below,
Inside,
Above,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TickLabel {
Upper,
Lower,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Direction {
Left,
Right,
}
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AccountsType {
TransferHookA,
TransferHookB,
TransferHookReward,
TransferHookInput,
TransferHookIntermediate,
TransferHookOutput,
SupplementalTickArrays,
SupplementalTickArraysOne,
SupplementalTickArraysTwo,
}

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
# ─── 1) jump to scripts own dir ───
cd "$(dirname "$0")"
# ─── 2) build in release (skips if up-to-date) ───
cargo build --release
# ─── 3) where our binary really lives ───
BIN="$(pwd)/target/release/grpc-raydium-pool-monitoring-rust"
# ─── 4) if PIN_CORES is set, pin there; otherwise run normally ───
if [[ -n "${PIN_CORES:-}" ]]; then
echo "📌 pinning to cores: $PIN_CORES"
exec taskset -c "$PIN_CORES" "$BIN" "$@"
else
echo "▶️ running on all available cores"
exec "$BIN" "$@"
fi

View File

@@ -0,0 +1,15 @@
[package]
name = "shared_state"
version = "0.1.0"
authors = ["Mephisto"]
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["sync"] }
lazy_static = "1.4"
serde = { version = "1.0", features = ["derive"] }
once_cell = "1.21.3"
solana-sdk = "2.1.7"
[lib]
path = "src/lib.rs"

View File

@@ -0,0 +1 @@
pub mod state;

View File

@@ -0,0 +1,269 @@
// shared_state/src/lib.rs
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use solana_sdk::pubkey::Pubkey;
use std::collections::HashMap;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::AtomicUsize;
use std::time::Instant;
use tokio::sync::Mutex;
/// A request to sell a mint for a given amount, marking if it's urgent.
#[derive(Debug, Clone)]
pub struct SellOrder {
pub mint: String,
pub amount: u64,
pub use_jito: bool, // Indicates if Jito should be used for this sell
pub urgent: bool,
}
#[derive(Debug, Clone)]
pub struct BuyOrder {
pub mint: String,
pub creator: Pubkey,
pub use_jito: bool,
pub urgent: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BoughtTokenInfo {
mint: String,
ata: Option<String>,
amount: u64,
fallback_amount: u64,
timestamp: Instant,
last_activity: Instant,
signature: Option<String>,
follow_up_buys: u32,
sell_triggered: bool,
first_sell_detected: bool,
sol_inflow: f64,
sol_outflow: f64,
creator_vault: Option<Pubkey>,
unique_buyers: std::collections::HashSet<String>,
suspected_creator: Option<String>,
mint_detected_at: Instant,
buy_executed_at: Option<Instant>,
sell_triggered_at: Option<Instant>,
ata_balance_zeroed: bool,
pub sell_issued_slot: u64,
sell_confirmed: bool,
pub buy_slot: u64,
pub sell_slot: Option<u64>,
pub sell_signature: Option<String>,
sell_executed_at: Option<std::time::Instant>,
sell_retry_count: u8,
}
impl BoughtTokenInfo {
pub fn new(
mint: String,
ata: Option<String>,
fallback_amount: u64,
timestamp: Instant,
signature: Option<String>,
buy_slot: u64,
creator_vault: Option<Pubkey>, // new param
) -> Self {
Self {
mint,
ata,
amount: fallback_amount,
fallback_amount,
timestamp,
last_activity: timestamp,
signature,
follow_up_buys: 0,
sell_triggered: false,
first_sell_detected: false,
sol_inflow: 0.0,
sol_outflow: 0.0,
unique_buyers: std::collections::HashSet::new(),
suspected_creator: None,
mint_detected_at: timestamp,
buy_executed_at: None,
sell_triggered_at: None,
ata_balance_zeroed: false,
buy_slot,
sell_slot: None,
sell_signature: None,
sell_issued_slot: 0,
sell_confirmed: false,
creator_vault, // initialize the creator vault
sell_executed_at: None,
sell_retry_count: 0,
}
}
pub fn creator_vault(&self) -> Option<&Pubkey> {
self.creator_vault.as_ref()
}
pub fn mint(&self) -> &String {
&self.mint
}
pub fn buy_slot(&self) -> u64 {
self.buy_slot
}
pub fn set_buy_slot(&mut self, slot: u64) {
self.buy_slot = slot;
}
pub fn sell_slot(&self) -> Option<u64> {
self.sell_slot
}
pub fn set_sell_slot(&mut self, slot: u64) {
self.sell_slot = Some(slot);
}
pub fn set_buy_executed_at(&mut self, when: Instant) {
self.buy_executed_at = Some(when);
}
pub fn buy_executed_at(&self) -> Option<Instant> {
self.buy_executed_at
}
pub fn record_sell(&mut self, sig: String, slot: u64) {
self.sell_signature = Some(sig);
self.sell_issued_slot = slot;
self.sell_slot = Some(slot);
self.sell_confirmed = false;
}
pub fn confirm_sell(&mut self) {
self.sell_confirmed = true;
}
pub fn sell_confirmed(&self) -> bool {
self.sell_confirmed
}
pub fn sell_issued_slot(&self) -> u64 {
self.sell_issued_slot
}
pub fn set_sell_triggered_at(&mut self, when: Instant) {
self.sell_triggered_at = Some(when);
}
pub fn sell_triggered_at(&self) -> Option<Instant> {
self.sell_triggered_at
}
pub fn set_suspected_creator(&mut self, creator: String) {
self.suspected_creator = Some(creator);
}
pub fn suspected_creator(&self) -> Option<&String> {
self.suspected_creator.as_ref()
}
pub fn ata_balance_zeroed(&self) -> bool {
self.ata_balance_zeroed
}
pub fn set_ata_balance_zeroed(&mut self, value: bool) {
self.ata_balance_zeroed = value;
}
pub fn sol_inflow(&self) -> f64 {
self.sol_inflow
}
pub fn add_sol_inflow(&mut self, amount: f64) {
self.sol_inflow += amount;
self.update_last_activity();
}
pub fn sol_outflow(&self) -> f64 {
self.sol_outflow
}
pub fn add_sol_outflow(&mut self, amount: f64) {
self.sol_outflow += amount;
self.update_last_activity();
}
pub fn ata(&self) -> Option<&String> {
self.ata.as_ref()
}
pub fn amount(&self) -> u64 {
self.amount
}
pub fn set_amount(&mut self, amt: u64) {
self.amount = amt;
}
pub fn fallback_amount(&self) -> u64 {
self.fallback_amount
}
pub fn timestamp(&self) -> Instant {
self.timestamp
}
pub fn last_activity(&self) -> Instant {
self.last_activity
}
pub fn update_last_activity(&mut self) {
self.last_activity = Instant::now();
}
pub fn signature(&self) -> Option<&String> {
self.signature.as_ref()
}
pub fn set_sell_signature(&mut self, sig: String) {
self.sell_signature = Some(sig);
}
pub fn sell_signature(&self) -> Option<&String> {
self.sell_signature.as_ref()
}
pub fn follow_up_buys(&self) -> u32 {
self.follow_up_buys
}
pub fn increment_follow_up_buys(&mut self) {
self.follow_up_buys += 1;
self.update_last_activity();
}
pub fn sell_triggered(&self) -> bool {
self.sell_triggered
}
pub fn set_sell_triggered(&mut self, v: bool) {
self.sell_triggered = v;
}
pub fn first_sell_detected(&self) -> bool {
self.first_sell_detected
}
pub fn mark_first_sell_detected(&mut self) {
self.first_sell_detected = true;
self.update_last_activity();
}
pub fn zero_amount(&mut self) {
self.amount = 0;
}
pub fn unique_buyers(&self) -> &std::collections::HashSet<String> {
&self.unique_buyers
}
pub fn add_unique_buyer(&mut self, addr: String) -> bool {
let ins = self.unique_buyers.insert(addr);
if ins {
self.update_last_activity();
}
ins
}
pub fn sell_executed_at(&self) -> Option<Instant> {
self.sell_executed_at
}
pub fn set_sell_executed_at(&mut self, t: Instant) {
self.sell_executed_at = Some(t);
}
pub fn retry_count(&self) -> u8 {
self.sell_retry_count
}
pub fn increment_retry_count(&mut self) {
self.sell_retry_count = self.sell_retry_count.saturating_add(1);
}
pub fn reset_retry_count(&mut self) {
self.sell_retry_count = 0;
}
}
lazy_static! {
pub static ref BOUGHT_TOKENS: Mutex<HashMap<String, BoughtTokenInfo>> =
Mutex::new(HashMap::new());
}
pub static CURRENT_SLOT: Lazy<AtomicU64> = Lazy::new(|| AtomicU64::new(0));
pub static PENDING_MINTS: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));

View File

@@ -0,0 +1,21 @@
// shared_state/src/lib.rs
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use tokio::sync::Mutex;
use lazy_static::lazy_static;
#[derive(Debug, Clone)]
pub struct TokenTrackingState {
pub mint: String,
pub first_buyer: Option<String>,
pub first_buy_amount: f64,
pub suspected_creators: HashSet<String>,
pub sell_detected: bool,
pub sell_split_ratio: usize,
}
lazy_static! {
pub static ref TRACKED_TOKENS: Arc<Mutex<HashMap<String, TokenTrackingState>>> =
Arc::new(Mutex::new(HashMap::new()));
}

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# 2) Set any necessary environment variables
export RUST_LOG=info
export BLOCK_ENGINE_URL="https://mainnet.block-engine.jito.wtf"
# Path to your approved Jito auth keypair
export AUTH_KEYPAIR="/home/ubuntu/shreds_sniper/shredstream-proxy/validator-keypair.json"
# Comma-separated Jito regions
export DESIRED_REGIONS="tokyo, frankfurt, amsterdam, ny, london, singapore, slc"
# Comma-separated list of destination IP:UDP_PORT entries
# (example: forward to localhost:8001 and a remote at 10.0.1.5:8002)
export DEST_IP_PORTS="127.0.0.1:8001"
# 3) Run the proxy binary
# If you built with `cargo build --release`, the binary lives under `target/release/`
exec /home/ubuntu/shreds_sniper/shredstream-proxy/target/release/jito-shredstream-proxy shredstream \
--block-engine-url "${BLOCK_ENGINE_URL}" \
--auth-keypair "${AUTH_KEYPAIR}" \
--desired-regions "${DESIRED_REGIONS}" \
--dest-ip-ports "${DEST_IP_PORTS}" \
--grpc-service-port 50051 \

View File

@@ -0,0 +1,86 @@
struct TradeDispatcher {
db: Database,
bot: AutoSend<Bot>,
user_ids: Vec<i64>, // all whitelisted telegram IDs
next_idx: AtomicUsize, // for round-robin
}
impl TradeDispatcher {
pub async fn new(db: Database, bot: AutoSend<Bot>) -> Self {
let ids = db
.users
.find(doc! { "is_whitelisted": true }, None)
.await
.unwrap()
.try_collect::<Vec<UserRecord>>()
.await
.unwrap()
.into_iter()
.map(|u| u.telegram_id)
.collect();
TradeDispatcher { db, bot, user_ids: ids, next_idx: AtomicUsize::new(0) }
}
/// Round-robin pick your next telegram user
fn pop_next_user(&self) -> i64 {
let len = self.user_ids.len();
let i = self.next_idx.fetch_add(1, Ordering::Relaxed) % len;
self.user_ids[i]
}
pub async fn dispatch_buy(&self, buy: BuyOrder) {
// 1) pick user
let telegram_id = self.pop_next_user();
let user = self.db.get_user(telegram_id).await.unwrap()
.expect("should exist and be whitelisted");
// 2) decrypt & load their Keypair
let keypair = decrypt_keypair(&user
.wallets
.iter()
.find(|w| w.id == user.trading_wallet_id.unwrap())
.unwrap()
.private_key_enc
);
// 3) build per-user SwapConfig
let mut cfg = SwapConfig::default();
if let Some(sl) = user.slippage_pct { cfg.slippage_bps = (sl * 100.0) as u16 }
if let Some(tip) = user.tip_amount { cfg.use_priority_tip = tip > 0 }
if let Some(min_tok) = user.min_tokens_to_buy { cfg.min_tokens = Some(min_tok as u64) }
// 4) hand off to pump_swap
let input = /* build your SwapInput exactly as before */;
let result = pump_swap(
AppState {
rpc_client: /* shared */,
rpc_nonblocking_client: /* shared */,
wallet: Arc::new(keypair),
},
input,
"buy",
buy.use_jito,
buy.urgent,
)
.await;
// 5) notify on Telegram
let msg = match result {
Ok(sigs) => format!("✅ [{}] buy for `{}` succeeded: {}", telegram_id, buy.mint, sigs[0]),
Err(e) => format!("❌ [{}] buy for `{}` failed: {}", telegram_id, buy.mint, e),
};
let _ = self.bot.send_message(telegram_id, msg).await;
}
pub async fn dispatch_sell(&self, sell: SellOrder) {
// Look up who originally bought this mint
let user_id = {
let tokens = shared_state::BOUGHT_TOKENS.lock().await;
let info = tokens.get(&sell.mint).unwrap();
info.telegram_id().unwrap()
};
// same as buy: fetch user, decrypt, build cfg, call pump_swap("sell", ...) and notify
/**/
}
}

View File

@@ -0,0 +1,239 @@
// src/commands.rs
use teloxide::prelude::*;
use teloxide::utils::command::BotCommand;
use mongodb::bson::oid::ObjectId;
use crate::db::Database;
use anyhow::Result;
#[derive(BotCommand, Clone)]
#[command(rename = "lowercase", description = "These commands are supported:")]
pub enum Command {
#[command(description = "display this text.")]
Help,
#[command(description = "start the bot.")]
Start,
#[command(description = "create a new wallet: /create_wallet <name> <private_key_or_mnemonic>")]
CreateWallet { name: String, key: String },
#[command(description = "list your wallets.")]
ListWallets,
#[command(description = "remove a wallet: /remove_wallet <wallet_id>")]
RemoveWallet { wallet_id: String },
#[command(description = "set trading wallet: /set_trading_wallet <wallet_id>")]
SetTradingWallet { wallet_id: String },
#[command(description = "set min tokens to buy: /set_min_tokens <amount>")]
SetMinTokens { amount: f64 },
#[command(description = "set slippage percent: /set_slippage <percent>")]
SetSlippage { percent: f64 },
#[command(description = "set tip amount: /set_tip <amount>")]
SetTip { amount: u64 },
#[command(description = "withdraw: /withdraw <wallet_id> <dest_address> <amount>")]
Withdraw { wallet_id: String, dest: String, amount: f64 },
#[command(description = "transfer: /transfer <wallet_id> <dest_address> <amount>")]
Transfer { wallet_id: String, dest: String, amount: f64 },
// Admin only:
#[command(description = "whitelist a user: /whitelist <telegram_id>")]
Whitelist { telegram_id: i64 },
#[command(description = "remove from whitelist: /unwhitelist <telegram_id>")]
Unwhitelist { telegram_id: i64 },
}
pub async fn handle_command(
bot: Bot,
msg: Message,
cmd: Command,
db: Database,
admin_ids: Vec<i64>,
) -> Result<()> {
let chat_id = msg.chat.id;
let user_id = msg.from().map(|u| u.id.0 as i64).unwrap_or(0);
match cmd {
Command::Help => {
bot.send_message(chat_id, Command::descriptions()).await?;
}
Command::Start => {
// Check whitelist
let is_whitelisted = db.is_whitelisted(user_id).await?;
if !is_whitelisted {
bot.send_message(chat_id, "You are not whitelisted. Please contact admin.").await?;
} else {
bot.send_message(chat_id, "Welcome! Use /help to see commands.").await?;
}
}
Command::CreateWallet { name, key } => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
// TODO: encrypt `key` before storing, e.g. AES with ENCRYPTION_KEY
let private_enc = encrypt_key(&key)?;
match db.add_wallet(user_id, name.clone(), derive_address(&key)?, private_enc).await {
Ok(entry) => {
bot.send_message(chat_id, format!("Wallet created with ID: {}", entry.id)).await?;
}
Err(e) => {
bot.send_message(chat_id, format!("Error creating wallet: {}", e)).await?;
}
}
}
Command::ListWallets => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
let wallets = db.list_wallets(user_id).await?;
if wallets.is_empty() {
bot.send_message(chat_id, "No wallets. Use /create_wallet.").await?;
} else {
let mut text = String::from("Your wallets:\n");
for w in wallets {
text.push_str(&format!("- ID: {}, name: {}, address: {}\n", w.id, w.name, w.address));
}
bot.send_message(chat_id, text).await?;
}
}
Command::RemoveWallet { wallet_id } => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
match ObjectId::parse_str(&wallet_id) {
Ok(oid) => {
if let Err(e) = db.remove_wallet(user_id, oid).await {
bot.send_message(chat_id, format!("Error removing wallet: {}", e)).await?;
} else {
bot.send_message(chat_id, "Wallet removed.").await?;
}
}
Err(_) => {
bot.send_message(chat_id, "Invalid wallet ID format.").await?;
}
}
}
Command::SetTradingWallet { wallet_id } => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
match ObjectId::parse_str(&wallet_id) {
Ok(oid) => {
if let Err(e) = db.set_trading_wallet(user_id, oid).await {
bot.send_message(chat_id, format!("Error: {}", e)).await?;
} else {
bot.send_message(chat_id, "Trading wallet set.").await?;
}
}
Err(_) => {
bot.send_message(chat_id, "Invalid wallet ID.").await?;
}
}
}
Command::SetMinTokens { amount } => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
if let Err(e) = db.update_setting_min_tokens(user_id, amount).await {
bot.send_message(chat_id, format!("Error: {}", e)).await?;
} else {
bot.send_message(chat_id, format!("min_tokens_to_buy set to {}", amount)).await?;
}
}
Command::SetSlippage { percent } => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
if let Err(e) = db.update_setting_slippage(user_id, percent).await {
bot.send_message(chat_id, format!("Error: {}", e)).await?;
} else {
bot.send_message(chat_id, format!("slippage_pct set to {}%", percent)).await?;
}
}
Command::SetTip { amount } => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
if let Err(e) = db.update_setting_tip(user_id, amount).await {
bot.send_message(chat_id, format!("Error: {}", e)).await?;
} else {
bot.send_message(chat_id, format!("tip_amount set to {}", amount)).await?;
}
}
Command::Withdraw { wallet_id, dest, amount } |
Command::Transfer { wallet_id, dest, amount } => {
if !db.is_whitelisted(user_id).await? {
bot.send_message(chat_id, "Not whitelisted.").await?;
return Ok(());
}
// Parse wallet ID
let oid = match ObjectId::parse_str(&wallet_id) {
Ok(o) => o,
Err(_) => {
bot.send_message(chat_id, "Invalid wallet ID.").await?;
return Ok(());
}
};
// Fetch user & wallet
if let Some(user) = db.get_user(user_id).await? {
if let Some(wallet) = user.wallets.iter().find(|w| w.id == oid) {
// Decrypt private key:
let private_key = decrypt_key(&wallet.private_key_enc)?;
// Then implement withdraw/transfer logic, e.g., call chain RPC:
// withdraw: send transaction moving `amount` tokens from wallet.address to dest.
// Here just stub:
bot.send_message(chat_id, format!(
"{} from wallet {} to {} of amount {}: not implemented yet",
if matches!(cmd, Command::Withdraw {..}) { "Withdraw" } else { "Transfer" },
wallet.name, dest, amount
)).await?;
} else {
bot.send_message(chat_id, "Wallet not found.").await?;
}
} else {
bot.send_message(chat_id, "User not found.").await?;
}
}
Command::Whitelist { telegram_id: target } => {
if !admin_ids.contains(&user_id) {
bot.send_message(chat_id, "You are not admin.").await?;
return Ok(());
}
if let Err(e) = db.set_whitelist(target, true).await {
bot.send_message(chat_id, format!("Error whitelisting: {}", e)).await?;
} else {
bot.send_message(chat_id, format!("User {} whitelisted.", target)).await?;
}
}
Command::Unwhitelist { telegram_id: target } => {
if !admin_ids.contains(&user_id) {
bot.send_message(chat_id, "You are not admin.").await?;
return Ok(());
}
if let Err(e) = db.set_whitelist(target, false).await {
bot.send_message(chat_id, format!("Error removing whitelist: {}", e)).await?;
} else {
bot.send_message(chat_id, format!("User {} unwhitelisted.", target)).await?;
}
}
}
Ok(())
}
// Placeholder encryption/decryption. In real use, implement AES or other.
fn encrypt_key(raw: &str) -> Result<String> {
// e.g. base64 or AES-encrypt with env key
Ok(base64::encode(raw))
}
fn decrypt_key(enc: &str) -> Result<String> {
let bytes = base64::decode(enc)?;
Ok(String::from_utf8(bytes)?)
}
// Placeholder derive address from private key/mnemonic: implement per-chain.
fn derive_address(key: &str) -> Result<String> {
// stub: in Solana, parse keypair and extract pubkey:
// ...
Ok("public_address_stub".to_string())
}

View File

@@ -0,0 +1,90 @@
use chrono::Local;
use serde_json::json;
use std::{
fs::OpenOptions,
io::Write,
sync::{Arc, Mutex},
};
const LOG_LEVEL: &str = "LOG";
#[derive(Clone)]
pub struct Logger {
prefix: String,
date_format: String,
file: Arc<Mutex<std::fs::File>>,
}
impl Logger {
// Constructor to create a new logger that writes to "logs.json"
pub fn new(prefix: String) -> Self {
let file = OpenOptions::new()
.create(true)
.append(true)
.open("logs.json")
.expect("Failed to open logs.json for writing");
Logger {
prefix,
date_format: String::from("%Y-%m-%d %H:%M:%S"),
file: Arc::new(Mutex::new(file)),
}
}
pub fn log(&self, message: String) -> String {
let full_log = format!("{} {}", self.prefix_with_date(), message);
println!("{}", full_log);
self.write_json("INFO", &message);
full_log
}
pub fn debug(&self, message: String) -> String {
let full_log = format!("{} [{}] {}", self.prefix_with_date(), "DEBUG", message);
if LogLevel::new().is_debug() {
println!("{}", full_log);
self.write_json("DEBUG", &message);
}
full_log
}
pub fn error(&self, message: String) -> String {
let full_log = format!("{} [{}] {}", self.prefix_with_date(), "ERROR", message);
println!("{}", full_log);
self.write_json("ERROR", &message);
full_log
}
fn prefix_with_date(&self) -> String {
let date = Local::now();
format!("[{}] {}", date.format(&self.date_format), self.prefix)
}
fn write_json(&self, level: &str, message: &str) {
let now = Local::now();
let entry = json!({
"timestamp": now.to_rfc3339(),
"level": level,
"prefix": self.prefix,
"message": message,
});
if let Ok(mut file) = self.file.lock() {
if let Err(e) = writeln!(file, "{}", entry) {
eprintln!("Failed to write to log file: {}", e);
}
}
}
}
struct LogLevel<'a> {
level: &'a str,
}
impl LogLevel<'_> {
fn new() -> Self {
LogLevel { level: LOG_LEVEL }
}
fn is_debug(&self) -> bool {
self.level.to_lowercase() == "debug"
}
}

View File

@@ -0,0 +1,3 @@
pub mod logger;
pub mod rpc;
pub mod utils;

View File

@@ -0,0 +1,131 @@
use anchor_lang::AccountDeserialize;
use anyhow::Result;
use base64::{prelude::BASE64_STANDARD, Engine};
use solana_account_decoder::UiAccountEncoding;
use solana_client::{
rpc_client::RpcClient,
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig},
rpc_filter::RpcFilterType,
rpc_request::RpcRequest,
rpc_response::{RpcResult, RpcSimulateTransactionResult},
};
use solana_sdk::{
account::Account, commitment_config::CommitmentConfig, instruction::Instruction,
message::Message, pubkey::Pubkey, signature::Signature, signer::signers::Signers,
transaction::Transaction,
};
use solana_transaction_status::UiTransactionEncoding;
pub fn build_txn(
client: &RpcClient,
instructions: &[Instruction],
fee_payer: &Pubkey,
signing_keypairs: &dyn Signers,
) -> Result<Transaction> {
let blockhash = client.get_latest_blockhash().unwrap();
let message = Message::new_with_blockhash(instructions, Some(fee_payer), &blockhash);
let mut transaction = Transaction::new_unsigned(message);
transaction
.try_partial_sign(signing_keypairs, blockhash)
.unwrap();
Ok(transaction)
}
pub fn send_txn(client: &RpcClient, txn: &Transaction, skip_preflight: bool) -> Result<Signature> {
Ok(client.send_and_confirm_transaction_with_spinner_and_config(
txn,
CommitmentConfig::confirmed(),
RpcSendTransactionConfig {
skip_preflight,
..RpcSendTransactionConfig::default()
},
)?)
}
pub fn simulate_transaction(
client: &RpcClient,
transaction: &Transaction,
sig_verify: bool,
cfg: CommitmentConfig,
) -> RpcResult<RpcSimulateTransactionResult> {
let serialized = bincode::serialize(transaction)
.map_err(|e| (format!("Serialization failed: {e}")))
.unwrap();
let serialized_encoded = BASE64_STANDARD.encode(serialized);
println!("{}", serialized_encoded);
client.send(
RpcRequest::SimulateTransaction,
serde_json::json!([serialized_encoded, {
"sigVerify": sig_verify, "commitment": cfg.commitment, "encoding": Some(UiTransactionEncoding::Base64)
}]),
)
}
pub fn send_without_confirm_txn(client: &RpcClient, txn: &Transaction) -> Result<Signature> {
Ok(client.send_transaction_with_config(
txn,
RpcSendTransactionConfig {
skip_preflight: true,
..RpcSendTransactionConfig::default()
},
)?)
}
pub fn get_account(client: &RpcClient, addr: &Pubkey) -> Result<Option<Vec<u8>>> {
if let Some(account) = client
.get_account_with_commitment(addr, CommitmentConfig::processed())?
.value
{
let account_data = account.data;
Ok(Some(account_data))
} else {
Ok(None)
}
}
pub fn get_anchor_account<T: AccountDeserialize>(
client: &RpcClient,
addr: &Pubkey,
) -> Result<Option<T>> {
if let Some(account) = client
.get_account_with_commitment(addr, CommitmentConfig::processed())?
.value
{
let mut data: &[u8] = &account.data;
let ret = T::try_deserialize(&mut data).unwrap();
Ok(Some(ret))
} else {
Ok(None)
}
}
pub fn get_multiple_accounts(
client: &RpcClient,
pubkeys: &[Pubkey],
) -> Result<Vec<Option<Account>>> {
Ok(client.get_multiple_accounts(pubkeys)?)
}
pub fn get_program_accounts_with_filters(
client: &RpcClient,
program: Pubkey,
filters: Option<Vec<RpcFilterType>>,
) -> Result<Vec<(Pubkey, Account)>> {
let accounts = client
.get_program_accounts_with_config(
&program,
RpcProgramAccountsConfig {
filters,
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64Zstd),
..RpcAccountInfoConfig::default()
},
with_context: Some(false),
sort_results: None, // <-- add this line
},
)
.unwrap();
Ok(accounts)
}

View File

@@ -0,0 +1,107 @@
// utils.rs
use crate::engine::swap::SwapDirection;
use anyhow::Result;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
use std::{env, sync::Arc};
#[derive(Clone)]
pub struct AppState {
pub rpc_client: Arc<solana_client::rpc_client::RpcClient>,
pub rpc_nonblocking_client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
pub wallet: Arc<Keypair>,
}
pub struct ParseTx {
pub type_tx: String,
pub direction: Option<String>,
pub amount_in: f64,
pub amount_out: f64,
pub mint: String,
}
pub fn import_env_var(key: &str) -> String {
env::var(key).unwrap_or_else(|_| panic!("Environment variable {} is not set", key))
}
pub fn create_rpc_client() -> Result<Arc<solana_client::rpc_client::RpcClient>> {
let rpc_https = import_env_var("RPC_ENDPOINT");
let rpc_client = solana_client::rpc_client::RpcClient::new_with_commitment(
rpc_https,
CommitmentConfig::processed(),
);
Ok(Arc::new(rpc_client))
}
pub async fn create_nonblocking_rpc_client(
) -> Result<Arc<solana_client::nonblocking::rpc_client::RpcClient>> {
let rpc_https = import_env_var("RPC_ENDPOINT");
let rpc_client = solana_client::nonblocking::rpc_client::RpcClient::new_with_commitment(
rpc_https,
CommitmentConfig::processed(),
);
Ok(Arc::new(rpc_client))
}
pub fn import_wallet() -> Result<Arc<Keypair>> {
let priv_key = import_env_var("PRIVATE_KEY");
let wallet: Keypair = Keypair::from_base58_string(priv_key.as_str());
Ok(Arc::new(wallet))
}
#[derive(Copy, Clone, Debug, Default)]
pub enum ComputeUnitLimits {
#[default]
Dynamic,
Fixed(u64),
}
#[derive(Copy, Clone, Debug)]
pub enum PriorityFeeConfig {
DynamicMultiplier(u64),
FixedCuPrice(u64),
JitoTip(u64),
}
#[derive(Clone, Debug)]
pub struct SwapConfig {
pub priority_fee: Option<PriorityFeeConfig>,
pub cu_limits: Option<ComputeUnitLimits>,
pub wrap_and_unwrap_sol: Option<bool>,
pub as_legacy_transaction: Option<bool>,
pub slippage: u64,
pub swap_direction: SwapDirection,
pub use_jito: bool,
pub use_priority_tip: bool,
}
#[derive(Clone, Debug, Default)]
pub struct SwapConfigOverrides {
pub priority_fee: Option<PriorityFeeConfig>,
pub cu_limits: Option<ComputeUnitLimits>,
pub wrap_and_unwrap_sol: Option<bool>,
pub destination_token_account: Option<Pubkey>,
pub as_legacy_transaction: Option<bool>,
}
#[derive(Copy, Clone, Debug)]
pub struct SwapInput {
pub input_token_mint: Pubkey,
pub output_token_mint: Pubkey,
pub slippage_bps: u16,
pub amount: u64,
pub mode: SwapExecutionMode,
pub market: Option<Pubkey>,
pub creator_vault: Option<Pubkey>,
}
#[derive(Copy, Clone, Debug)]
pub enum SwapExecutionMode {
ExactIn,
ExactOut,
}
impl SwapExecutionMode {
pub fn amount_specified_is_input(&self) -> bool {
matches!(self, SwapExecutionMode::ExactIn)
}
}

View File

@@ -0,0 +1,2 @@
pub mod token;
pub mod tx;

View File

@@ -0,0 +1,117 @@
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use spl_token_2022::{
extension::StateWithExtensionsOwned,
state::{Account, Mint},
};
use spl_token_client::{
client::{ProgramClient, ProgramRpcClient, ProgramRpcClientSendTransaction},
token::{Token, TokenError, TokenResult},
};
use std::sync::Arc;
use tracing::info;
pub async fn get_token_balance(
client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
keypair: Arc<Keypair>,
mint: &Pubkey,
owner: &Pubkey,
) -> Result<u64, TokenError> {
let ata = get_associated_token_address(client.clone(), keypair.clone(), mint, owner);
info!(
"🔎 Fetching token balance for ATA: {} | Mint: {} | Owner: {}",
ata, mint, owner
);
match get_account_info(client.clone(), keypair, mint, &ata).await {
Ok(account_info) => {
info!(
"✅ Token balance found: {} tokens (raw amount)",
account_info.base.amount
);
Ok(account_info.base.amount)
}
Err(e) => {
info!("❌ Failed to fetch token balance for ATA {}: {:?}", ata, e);
Err(e)
}
}
}
pub fn get_associated_token_address(
client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
keypair: Arc<Keypair>,
address: &Pubkey,
owner: &Pubkey,
) -> Pubkey {
let token_client = Token::new(
Arc::new(ProgramRpcClient::new(
client.clone(),
ProgramRpcClientSendTransaction,
)),
&spl_token::ID,
address,
None,
Arc::new(Keypair::from_bytes(&keypair.to_bytes()).expect("failed to copy keypair")),
);
token_client.get_associated_token_address(owner)
}
pub async fn get_account_info(
client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
_keypair: Arc<Keypair>,
address: &Pubkey,
account: &Pubkey,
) -> TokenResult<StateWithExtensionsOwned<Account>> {
let program_client = Arc::new(ProgramRpcClient::new(
client.clone(),
ProgramRpcClientSendTransaction,
));
let account = program_client
.get_account(*account)
.await
.map_err(TokenError::Client)?
.ok_or(TokenError::AccountNotFound)
.inspect_err(|err| println!("get_account_info: {} {}: mint {}", account, err, address))?;
if account.owner != spl_token::ID {
return Err(TokenError::AccountInvalidOwner);
}
let account = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
if account.base.mint != *address {
return Err(TokenError::AccountInvalidMint);
}
Ok(account)
}
pub async fn get_mint_info(
client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
_keypair: Arc<Keypair>,
address: &Pubkey,
) -> TokenResult<StateWithExtensionsOwned<Mint>> {
let program_client = Arc::new(ProgramRpcClient::new(
client.clone(),
ProgramRpcClientSendTransaction,
));
let account = program_client
.get_account(*address)
.await
.map_err(TokenError::Client)?
.ok_or(TokenError::AccountNotFound)
.inspect_err(|err| println!("{} {}: mint {}", address, err, address))?;
if account.owner != spl_token::ID {
return Err(TokenError::AccountInvalidOwner);
}
let mint_result = StateWithExtensionsOwned::<Mint>::unpack(account.data).map_err(Into::into);
let decimals: Option<u8> = None;
if let (Ok(mint), Some(decimals)) = (&mint_result, decimals) {
if decimals != mint.base.decimals {
return Err(TokenError::InvalidDecimals);
}
}
mint_result
}

View File

@@ -0,0 +1,224 @@
// tx.rs
use crate::{
common::{logger::Logger, rpc},
services::{jito, nextblock},
};
use anyhow::{anyhow, Result};
use base64;
use once_cell::sync::Lazy;
use reqwest::Client as HttpClient;
use serde_json::json;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction, hash::Hash, instruction::Instruction,
signature::Keypair, signer::Signer, system_instruction, transaction::Transaction,
};
use spl_token::ui_amount_to_amount;
use std::{env, sync::Arc, time::Duration};
use tokio::{spawn, sync::Mutex, time::Instant};
//——————————————————————————————————————————————————
// 1) One global HTTP client for all blockengine calls
//——————————————————————————————————————————————————
static HTTP_CLIENT: Lazy<HttpClient> = Lazy::new(|| {
HttpClient::builder()
.pool_max_idle_per_host(0)
.build()
.expect("failed to build HTTP client")
});
//——————————————————————————————————————————————————
// 2) Cached blockhash, refreshed every 500 ms in the background
//——————————————————————————————————————————————————
pub static LATEST_BLOCKHASH: Lazy<Mutex<Hash>> = Lazy::new(|| Mutex::new(Hash::default()));
/// Spawn this once at startup to keep our blockhash fresh.
pub fn spawn_blockhash_refresher(rpc: Arc<RpcClient>) {
spawn(async move {
loop {
if let Ok(hash) = rpc.get_latest_blockhash() {
*LATEST_BLOCKHASH.lock().await = hash;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
});
}
//——————————————————————————————————————————————————
// 3) One-time init of tip account & tip value (Jito vs NextBlock)
//——————————————————————————————————————————————————
static TIP_ACCOUNT: Lazy<Mutex<solana_sdk::pubkey::Pubkey>> =
Lazy::new(|| Mutex::new(solana_sdk::pubkey::Pubkey::default()));
static TIP_VALUE: Lazy<Mutex<f64>> = Lazy::new(|| Mutex::new(0.0));
/// Call this **once** at startup (before any swaps).
/// `use_nextblock` comes from e.g. `env("BLOCK_ENGINE_PROVIDER") == "nextblock"`,
/// `use_priority_tip` from your config.
pub async fn init_tip_state(use_nextblock: bool, use_priority_tip: bool) -> Result<()> {
if use_nextblock {
nextblock::init_tip_accounts().await?;
let base = nextblock::get_tip_value().await?;
let mut acct = TIP_ACCOUNT.lock().await;
let mut val = TIP_VALUE.lock().await;
*acct = nextblock::get_tip_account().await?;
*val = if use_priority_tip { base * 2.0 } else { base };
} else {
jito::init_tip_accounts().await?;
let base = jito::get_tip_value().await?;
let mut acct = TIP_ACCOUNT.lock().await;
let mut val = TIP_VALUE.lock().await;
*acct = jito::get_tip_account().await?;
*val = if use_priority_tip { base * 5.0 } else { base };
}
Ok(())
}
//——————————————————————————————————————————————————
// 4) Pre-built compute-budget instructions
//——————————————————————————————————————————————————
fn unit_price() -> u64 {
env::var("UNIT_PRICE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1)
}
fn unit_limit() -> u32 {
env::var("UNIT_LIMIT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(300_000)
}
static BUDGET_INSTRUCTIONS: Lazy<Vec<Instruction>> = Lazy::new(|| {
vec![
ComputeBudgetInstruction::set_compute_unit_price(unit_price()),
ComputeBudgetInstruction::set_compute_unit_limit(unit_limit()),
]
});
//——————————————————————————————————————————————————
// 5) The refactored sendtx function (handles RPC, Jito & NextBlock)
//——————————————————————————————————————————————————
#[allow(deprecated, unused_variables)]
pub async fn new_signed_and_send(
client: &RpcClient,
keypair: &Keypair,
mut instructions: Vec<Instruction>,
use_priority_engine: bool, // e.g. swap_config.use_jito
use_priority_tip: bool, // e.g. swap_config.use_priority_tip
logger: &Logger,
) -> Result<Vec<String>> {
// a) If *not* using a blockengine, prepend computebudget ixs
if !use_priority_engine {
instructions.splice(0..0, BUDGET_INSTRUCTIONS.iter().cloned());
}
// b) Grab our cached blockhash
let blockhash = *LATEST_BLOCKHASH.lock().await;
// c) Dispatch
let start = Instant::now();
let mut sigs = Vec::new();
if use_priority_engine {
// i) build tiptransfer ix
let tip_account = *TIP_ACCOUNT.lock().await;
let tip_value = *TIP_VALUE.lock().await;
let lamports = ui_amount_to_amount(tip_value.min(0.1), spl_token::native_mint::DECIMALS);
let tip_ix = system_instruction::transfer(&keypair.pubkey(), &tip_account, lamports);
// ii) weave it into a fresh txn
let mut pe_ixs = Vec::with_capacity(instructions.len() + 1);
pe_ixs.push(tip_ix);
pe_ixs.extend(instructions);
let pe_tx = Transaction::new_signed_with_payer(
&pe_ixs,
Some(&keypair.pubkey()),
&[keypair],
blockhash,
);
// iii) encode + HTTPPOST to NextBlock or Jito
let encoded = base64::encode(bincode::serialize(&pe_tx)?);
let payload = json!({
"transaction": { "content": encoded },
"frontRunningProtection": true,
});
let engine_is_nextblock = env::var("BLOCK_ENGINE_PROVIDER")
.unwrap_or_default()
.to_lowercase()
== "nextblock";
if engine_is_nextblock {
// — NextBlock —
let auth = env::var("NEXTBLOCK_AUTH").unwrap_or_default();
let resp = HTTP_CLIENT
.post("https://ny.nextblock.io/api/v2/submit")
.header("Content-Type", "application/json")
.header("Authorization", auth)
.json(&payload)
.send()
.await?;
if !resp.status().is_success() {
return Err(anyhow!(
"NextBlock error {}: {}",
resp.status(),
resp.text().await?
));
}
let body = resp.json::<serde_json::Value>().await?;
if let Some(sig) = body.get("signature").and_then(|v| v.as_str()) {
sigs.push(sig.to_string());
} else {
return Err(anyhow!("Missing signature in NextBlock response"));
}
} else {
// — Jito —
let url = format!("{}/api/v1/transactions", *jito::BLOCK_ENGINE_URL);
let rpc_resp = HTTP_CLIENT
.post(&url)
.header("Content-Type", "application/json")
.json(&json!({
"id": 1,
"jsonrpc": "2.0",
"method": "sendTransaction",
"params": [encoded, { "encoding": "base64" }]
}))
.send()
.await?;
if !rpc_resp.status().is_success() {
return Err(anyhow!(
"Jito error {}: {}",
rpc_resp.status(),
rpc_resp.text().await?
));
}
let body = rpc_resp.json::<serde_json::Value>().await?;
if let Some(sig) = body.get("result").and_then(|v| v.as_str()) {
sigs.push(sig.to_string());
} else {
return Err(anyhow!("Missing signature in Jito response"));
}
}
logger.log(format!("✅ Sent via blockengine in {:?}", start.elapsed()));
} else {
// — standard RPC path —
let std_tx = Transaction::new_signed_with_payer(
&instructions,
Some(&keypair.pubkey()),
&[keypair],
blockhash,
);
let sig = rpc::send_txn(client, &std_tx, true)?;
sigs.push(sig.to_string());
logger.log("✅ Sent via standard RPC".into());
}
Ok(sigs)
}

View File

@@ -0,0 +1,170 @@
// src/db.rs
use mongodb::{Client, Collection, options::ClientOptions};
use mongodb::bson::{doc, oid::ObjectId};
use crate::models::{UserRecord, WalletEntry};
use anyhow::{Result, Context};
use futures::TryStreamExt;
pub struct Database {
users: Collection<UserRecord>,
}
impl Database {
/// Initialize MongoDB client and get `users` collection.
pub async fn new(mongo_uri: &str, db_name: &str) -> Result<Self> {
let mut client_options = ClientOptions::parse(mongo_uri).await?;
client_options.app_name = Some("TelegramWalletBot".to_string());
let client = Client::with_options(client_options)?;
let db = client.database(db_name);
let users = db.collection::<UserRecord>("users");
// Ensure index on telegram_id
users.create_index(
mongodb::IndexModel::builder()
.keys(doc! { "telegram_id": 1 })
.options(Some(mongodb::options::IndexOptions::builder().unique(true).build()))
.build(),
None
).await?;
Ok(Self { users })
}
/// Fetch or create a user record. If not exist, create with is_whitelisted=false.
pub async fn get_or_create_user(&self, telegram_id: i64) -> Result<UserRecord> {
if let Some(user) = self.users
.find_one(doc! { "telegram_id": telegram_id }, None)
.await?
{
Ok(user)
} else {
let new = UserRecord {
id: ObjectId::new(),
telegram_id,
is_whitelisted: false,
wallets: Vec::new(),
trading_wallet_id: None,
min_tokens_to_buy: None,
slippage_pct: None,
tip_amount: None,
};
self.users.insert_one(&new, None).await?;
Ok(new)
}
}
/// Check whitelist
pub async fn is_whitelisted(&self, telegram_id: i64) -> Result<bool> {
if let Some(user) = self.users.find_one(doc! { "telegram_id": telegram_id }, None).await? {
Ok(user.is_whitelisted)
} else {
Ok(false)
}
}
/// Set whitelist status (admin only).
pub async fn set_whitelist(&self, telegram_id: i64, status: bool) -> Result<()> {
let filter = doc! { "telegram_id": telegram_id };
let update = doc! { "$set": { "is_whitelisted": status } };
self.users.update_one(filter, update, None).await?;
Ok(())
}
/// Add a wallet (limit 5). Returns error if >5.
pub async fn add_wallet(&self, telegram_id: i64, name: String, address: String, private_enc: String) -> Result<WalletEntry> {
// Load user
let mut user = self.get_or_create_user(telegram_id).await?;
if user.wallets.len() >= 5 {
anyhow::bail!("wallet limit reached (5)");
}
// Create entry
let entry = WalletEntry {
id: ObjectId::new(),
name,
address,
private_key_enc: private_enc,
};
user.wallets.push(entry.clone());
// Update in DB
self.users.update_one(
doc! { "telegram_id": telegram_id },
doc! { "$set": { "wallets": bson::to_bson(&user.wallets)? } },
None
).await?;
Ok(entry)
}
/// List wallets
pub async fn list_wallets(&self, telegram_id: i64) -> Result<Vec<WalletEntry>> {
if let Some(user) = self.users.find_one(doc! { "telegram_id": telegram_id }, None).await? {
Ok(user.wallets)
} else {
Ok(Vec::new())
}
}
/// Set trading wallet by wallet id
pub async fn set_trading_wallet(&self, telegram_id: i64, wallet_id: ObjectId) -> Result<()> {
// Verify wallet belongs to user
if let Some(user) = self.users.find_one(doc! { "telegram_id": telegram_id }, None).await? {
if user.wallets.iter().any(|w| w.id == wallet_id) {
self.users.update_one(
doc! { "telegram_id": telegram_id },
doc! { "$set": { "trading_wallet_id": wallet_id } },
None
).await?;
Ok(())
} else {
anyhow::bail!("wallet not found");
}
} else {
anyhow::bail!("user not found");
}
}
/// Update a setting (min_tokens_to_buy, slippage_pct, tip_amount)
pub async fn update_setting_min_tokens(&self, telegram_id: i64, min_tokens: f64) -> Result<()> {
self.users.update_one(
doc! { "telegram_id": telegram_id },
doc! { "$set": { "min_tokens_to_buy": min_tokens } },
None
).await?;
Ok(())
}
pub async fn update_setting_slippage(&self, telegram_id: i64, slippage_pct: f64) -> Result<()> {
self.users.update_one(
doc! { "telegram_id": telegram_id },
doc! { "$set": { "slippage_pct": slippage_pct } },
None
).await?;
Ok(())
}
pub async fn update_setting_tip(&self, telegram_id: i64, tip: u64) -> Result<()> {
self.users.update_one(
doc! { "telegram_id": telegram_id },
doc! { "$set": { "tip_amount": tip } },
None
).await?;
Ok(())
}
/// Remove wallet by id
pub async fn remove_wallet(&self, telegram_id: i64, wallet_id: ObjectId) -> Result<()> {
// Pull from array
self.users.update_one(
doc! { "telegram_id": telegram_id },
doc! { "$pull": { "wallets": { "id": wallet_id } } },
None
).await?;
// If trading_wallet_id was this, clear it
self.users.update_one(
doc! { "telegram_id": telegram_id, "trading_wallet_id": wallet_id },
doc! { "$set": { "trading_wallet_id": bson::Bson::Null } },
None
).await?;
Ok(())
}
/// Fetch user record
pub async fn get_user(&self, telegram_id: i64) -> Result<Option<UserRecord>> {
Ok(self.users.find_one(doc! { "telegram_id": telegram_id }, None).await?)
}
}

View File

@@ -0,0 +1,146 @@
pub const TEN_THOUSAND: u64 = 10000;
pub const TOKEN_PROGRAM: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
pub const RENT_PROGRAM: &str = "SysvarRent111111111111111111111111111111111";
pub const ASSOCIATED_TOKEN_PROGRAM: &str = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
pub const PUMP_GLOBAL: &str = "4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf";
pub const PUMP_FEE_RECIPIENT: &str = "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM";
pub const PUMP_PROGRAM: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
// pub const PUMP_FUN_MINT_AUTHORITY: &str = "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM";
pub const PUMP_ACCOUNT: &str = "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1";
pub const PUMP_BUY_METHOD: u64 = 16927863322537952870;
pub const PUMP_SELL_METHOD: u64 = 12502976635542562355;
pub struct Pump {
pub rpc_nonblocking_client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
pub keypair: Arc<Keypair>,
pub rpc_client: Option<Arc<solana_client::rpc_client::RpcClient>>,
}
impl Pump {
pub fn new(
rpc_nonblocking_client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
rpc_client: Arc<solana_client::rpc_client::RpcClient>,
keypair: Arc<Keypair>,
) -> Self {
Self {
rpc_nonblocking_client,
keypair,
rpc_client: Some(rpc_client),
}
}
pub async fn swap(&self, mint: &str, swap_config: SwapConfig) -> Result<Vec<String>> {
let logger = Logger::new("[SWAP IN PUMP.FUN] => ".to_string());
let slippage_bps = swap_config.slippage * 100;
let owner = self.keypair.pubkey();
let mint =
Pubkey::from_str(mint).map_err(|e| anyhow!("failed to parse mint pubkey: {}", e))?;
let program_id = spl_token::ID;
let native_mint = spl_token::native_mint::ID;
let (token_in, token_out, pump_method) = match swap_config.swap_direction {
SwapDirection::Buy => (native_mint, mint, PUMP_BUY_METHOD),
SwapDirection::Sell => (mint, native_mint, PUMP_SELL_METHOD),
};
let pump_program = Pubkey::from_str(PUMP_PROGRAM)?;
let (bonding_curve, associated_bonding_curve, bonding_curve_account) =
get_bonding_curve_account(self.rpc_client.clone().unwrap(), &mint, &pump_program)
.await?;
let in_ata = token::get_associated_token_address(
self.rpc_nonblocking_client.clone(),
self.keypair.clone(),
&token_in,
&owner,
);
let out_ata = token::get_associated_token_address(
self.rpc_nonblocking_client.clone(),
self.keypair.clone(),
&token_out,
&owner,
);
let mut create_instruction = None;
let mut close_instruction = None;
tx::new_signed_and_send(
&client,
&self.keypair,
instructions,
swap_config.use_jito,
&logger,
)
.await
}
}
impl Raydium {
pub fn new(
rpc_nonblocking_client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
rpc_client: Arc<solana_client::rpc_client::RpcClient>,
keypair: Arc<Keypair>,
) -> Self {
Self {
rpc_nonblocking_client,
keypair,
rpc_client: Some(rpc_client),
pool_id: None,
}
}
pub async fn swap(
&self,
swap_config: SwapConfig,
amm_pool_id: Pubkey,
pool_state: AmmInfo,
) -> Result<Vec<String>> {
let logger = Logger::new(format!(
"[SWAP IN RAYDIUM]({}) => ",
chrono::Utc::now().timestamp()
));
let slippage_bps = swap_config.slippage * 100;
let owner = self.keypair.pubkey();
let program_id = spl_token::ID;
let native_mint = spl_token::native_mint::ID;
let mint = pool_state.coin_vault_mint;
let (token_in, token_out, user_input_token, swap_base_in) = match (
swap_config.swap_direction.clone(),
pool_state.coin_vault_mint == native_mint,
) {
(SwapDirection::Buy, true) => (native_mint, mint, pool_state.coin_vault, true),
(SwapDirection::Buy, false) => (native_mint, mint, pool_state.pc_vault, true),
(SwapDirection::Sell, true) => (mint, native_mint, pool_state.pc_vault, true),
(SwapDirection::Sell, false) => (mint, native_mint, pool_state.coin_vault, true),
};
logger.log(format!(
"token_in:{}, token_out:{}, user_input_token:{}, swap_base_in:{}",
token_in, token_out, user_input_token, swap_base_in
));
let in_ata = get_associated_token_address(
self.rpc_nonblocking_client.clone(),
self.keypair.clone(),
&token_in,
&owner,
);
let out_ata = get_associated_token_address(
self.rpc_nonblocking_client.clone(),
self.keypair.clone(),
&token_out,
&owner,
);
let mut create_instruction = None;
let mut close_instruction = None;
tx::new_signed_and_send(
&client,
&self.keypair,
instructions,
swap_config.use_jito,
&logger,
)
.await
}
}

View File

@@ -0,0 +1 @@
pub mod pump_fun;

View File

@@ -0,0 +1,470 @@
// pump_fun.rs
use crate::common::utils::SwapInput;
use crate::{
common::{logger::Logger, utils::SwapConfig},
core::tx,
engine::swap::SwapDirection,
};
use anyhow::{anyhow, Result};
use borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use shared_state::BoughtTokenInfo;
use shared_state::BOUGHT_TOKENS;
use shared_state::CURRENT_SLOT;
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
signature::Keypair,
signer::Signer,
system_program,
};
use spl_associated_token_account::get_associated_token_address;
use spl_associated_token_account::instruction::create_associated_token_account as create_ata;
use std::collections::HashSet;
use std::sync::atomic::Ordering;
use std::time::Instant;
use std::{str::FromStr, sync::Arc, time::Duration};
use tokio::sync::Mutex;
use tracing::info;
// Define the 8-byte discriminator for the pump swap instruction.
// (These bytes must match what the onchain program expects.)
const PUMP_SWAP_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
pub const TEN_THOUSAND: u64 = 10000;
pub const TOKEN_PROGRAM: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
pub const RENT_PROGRAM: &str = "SysvarRent111111111111111111111111111111111";
pub const ASSOCIATED_TOKEN_PROGRAM: &str = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
pub const PUMP_GLOBAL: &str = "4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf";
pub const PUMP_FEE_RECIPIENT: &str = "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM";
pub const PUMP_PROGRAM: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
// pub const PUMP_FUN_MINT_AUTHORITY: &str = "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM";
pub const PUMP_ACCOUNT: &str = "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1";
pub const PUMP_BUY_METHOD: u64 = 16927863322537952870;
pub const PUMP_SELL_METHOD: u64 = 12502976635542562355;
/// This struct will be serialized (after the discriminator) as the instruction data.
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct PumpBuyInstruction {
/// The minimum number of tokens the buyer expects to receive.
pub amount: u64,
/// The maximum SOL (in lamports) the buyer is willing to spend.
pub max_sol_cost: u64,
}
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct PumpSellInstruction {
/// Amount of tokens to sell
pub amount: u64,
/// Minimum SOL to receive
pub min_sol_output: u64,
}
pub struct Pump {
pub rpc_nonblocking_client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
pub keypair: Arc<Keypair>,
pub rpc_client: Option<Arc<solana_client::rpc_client::RpcClient>>,
created_atas: Arc<Mutex<HashSet<Pubkey>>>,
}
impl Pump {
pub fn new(
rpc_nonblocking_client: Arc<solana_client::nonblocking::rpc_client::RpcClient>,
rpc_client: Arc<solana_client::rpc_client::RpcClient>,
keypair: Arc<Keypair>,
) -> Self {
Self {
rpc_nonblocking_client,
keypair,
rpc_client: Some(rpc_client),
created_atas: Arc::new(Mutex::new(HashSet::new())),
}
}
pub async fn swap(
&self,
mint: &str,
swap_config: SwapConfig,
swap_input: SwapInput,
amount_in_lamports: u64,
) -> Result<Vec<String>> {
match swap_config.swap_direction {
SwapDirection::Buy => {
self.swap_buy(mint, swap_config, &swap_input, amount_in_lamports)
.await
}
SwapDirection::Sell => self.swap_sell(mint, swap_config).await,
}
}
async fn swap_buy(
&self,
_mint: &str,
swap_config: SwapConfig,
swap_input: &SwapInput,
amount_in_lamports: u64,
) -> Result<Vec<String>> {
let creator_vault = swap_input
.creator_vault
.ok_or_else(|| anyhow!("creator_vault not provided in SwapInput"))?;
// still need a Logger for the lowerlevel TX helper
let logger = Logger::new("[BUY SWAP] => ".to_string());
let owner = self.keypair.pubkey();
let mint_pubkey = swap_input.output_token_mint;
let pump_program = Pubkey::from_str(PUMP_PROGRAM)?;
// derive PDAs
let bonding_curve = get_pda(&mint_pubkey, &pump_program)?;
let associated_bonding_curve = get_associated_token_address(&bonding_curve, &mint_pubkey);
// derive ATA locally
let out_ata =
spl_associated_token_account::get_associated_token_address(&owner, &mint_pubkey);
// 1) ensure user ATA exists via cache
let mut instructions = Vec::new();
{
let cache = self.created_atas.lock().await;
if !cache.contains(&out_ata) {
instructions.push(create_ata(&owner, &owner, &mint_pubkey, &spl_token::ID));
}
}
// 2) build the buy instruction data
let min_tokens: u64 = std::env::var("MIN_TOKENS_EXPECTED")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(8_000_000_000_000);
let mut data = PUMP_SWAP_DISCRIMINATOR.to_vec();
data.extend_from_slice(&to_vec(&PumpBuyInstruction {
amount: min_tokens,
max_sol_cost: amount_in_lamports,
})?);
// 3) assemble accounts in *exact* IDL order
let global = Pubkey::find_program_address(&[b"global"], &pump_program).0;
let fee_recipient = Pubkey::from_str(PUMP_FEE_RECIPIENT)?;
let event_authority = Pubkey::from_str(PUMP_ACCOUNT)?;
let accounts = vec![
AccountMeta::new_readonly(global, false), // 1
AccountMeta::new(fee_recipient, false), // 2
AccountMeta::new_readonly(mint_pubkey, false), // 3
AccountMeta::new(bonding_curve, false), // 4
AccountMeta::new(associated_bonding_curve, false), // 5
AccountMeta::new(out_ata, false), // 6
AccountMeta::new(owner, true), // 7
AccountMeta::new_readonly(system_program::ID, false), // 8
AccountMeta::new_readonly(spl_token::ID, false), // 9
AccountMeta::new(creator_vault, false), // 10
AccountMeta::new_readonly(event_authority, false), // 11
AccountMeta::new_readonly(pump_program, false), // 12
];
info!(
"⛓ Preparing BUY for {}: min_tokens={}, max_sol={}; accounts={:?}",
mint_pubkey,
min_tokens,
amount_in_lamports,
accounts
.iter()
.map(|m| m.pubkey.to_string())
.collect::<Vec<_>>()
);
// 4) push your swap instruction
instructions.push(Instruction {
program_id: pump_program,
accounts,
data,
});
// 5) dispatch
let tx_result = tx::new_signed_and_send(
self.rpc_client.as_ref().unwrap(),
&self.keypair,
instructions,
swap_config.use_jito,
swap_config.use_priority_tip,
&logger,
)
.await;
// 6) record on success
if let Ok(sigs) = &tx_result {
let buy_slot = CURRENT_SLOT.load(std::sync::atomic::Ordering::Relaxed);
let mut tokens = BOUGHT_TOKENS.lock().await;
let mut info = BoughtTokenInfo::new(
mint_pubkey.to_string(),
Some(out_ata.to_string()),
min_tokens,
Instant::now(),
sigs.first().cloned(),
buy_slot,
Some(creator_vault),
);
info.set_buy_executed_at(Instant::now());
tokens.insert(mint_pubkey.to_string(), info);
info!(
"✅ BUY sent for {}: sigs={:?} @ slot={}",
mint_pubkey, sigs, buy_slot
);
}
tx_result
}
async fn swap_sell(&self, mint: &str, swap_config: SwapConfig) -> Result<Vec<String>> {
// logger still used for the helper
let logger = Logger::new("[SELL SWAP] => ".to_string());
let owner = self.keypair.pubkey();
let mint_pubkey = Pubkey::from_str(mint)?;
let pump_program = Pubkey::from_str(PUMP_PROGRAM)?;
// derive PDAs
let global_pda = Pubkey::find_program_address(&[b"global"], &pump_program).0;
let fee_recipient = Pubkey::from_str(PUMP_FEE_RECIPIENT)?;
let bonding_curve = get_pda(&mint_pubkey, &pump_program)?;
let associated_bonding_curve = get_associated_token_address(&bonding_curve, &mint_pubkey);
let event_authority = Pubkey::from_str(PUMP_ACCOUNT)?;
// fetch stored info including creator_vault
let (in_ata, sell_amount, creator_vault) = {
let tokens = BOUGHT_TOKENS.lock().await;
let info = tokens
.get(mint)
.ok_or_else(|| anyhow!("Missing buy record for {}", mint))?;
let ata_str = info
.ata()
.ok_or_else(|| anyhow!("Missing ATA for {}", mint))?;
let tracked = info.amount();
let fallback = info.fallback_amount();
let sell_amount = tracked.max(fallback);
let vault = *info
.creator_vault()
.ok_or_else(|| anyhow!("Missing stored creator_vault for {}", mint))?;
(Pubkey::from_str(ata_str)?, sell_amount, vault)
};
// build instruction data
const PUMP_SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
let mut data = PUMP_SELL_DISCRIMINATOR.to_vec();
data.extend_from_slice(&to_vec(&PumpSellInstruction {
amount: sell_amount,
min_sol_output: 1_000,
})?);
// assemble accounts in IDL order
let accounts = vec![
AccountMeta::new_readonly(global_pda, false),
AccountMeta::new(fee_recipient, false),
AccountMeta::new_readonly(mint_pubkey, false),
AccountMeta::new(bonding_curve, false),
AccountMeta::new(associated_bonding_curve, false),
AccountMeta::new(in_ata, false),
AccountMeta::new(owner, true),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new(creator_vault, false),
AccountMeta::new_readonly(Pubkey::from_str(TOKEN_PROGRAM)?, false),
AccountMeta::new_readonly(event_authority, false),
AccountMeta::new_readonly(pump_program, false),
];
info!(
"⛓ Preparing SELL for {}: amount={}, creator_vault={}; accounts={:?}",
mint_pubkey,
sell_amount,
creator_vault,
accounts
.iter()
.map(|m| m.pubkey.to_string())
.collect::<Vec<_>>()
);
let ix = Instruction {
program_id: pump_program,
accounts,
data,
};
let mut last: Result<Vec<String>> = Err(anyhow!("no attempts"));
for attempt in 1..=3 {
let res = tx::new_signed_and_send(
self.rpc_client.as_ref().unwrap(),
&self.keypair,
vec![ix.clone()],
swap_config.use_jito,
swap_config.use_priority_tip,
&logger,
)
.await;
match res {
Ok(sigs) => {
let sell_slot = CURRENT_SLOT.load(Ordering::Relaxed);
info!(
"💰 SELL succeeded for {}: sigs={:?} @ slot={}",
mint_pubkey, sigs, sell_slot
);
// ** record the signature so your gRPCstream confirmation can see it **
let mut tokens = BOUGHT_TOKENS.lock().await;
if let Some(entry) = tokens.get_mut(&mint_pubkey.to_string()) {
entry.set_sell_signature(sigs[0].clone());
}
last = Ok(sigs);
break;
}
Err(err) => {
info!(
"❌ SELL attempt {} failed for {}: {}",
attempt, mint_pubkey, err
);
if attempt < 3 {
tokio::time::sleep(Duration::from_millis(600 * attempt)).await;
}
last = Err(err);
}
}
}
last
}
}
pub async fn wait_for_bonding_curve_account(
rpc_client: Arc<solana_client::rpc_client::RpcClient>,
mint: &Pubkey,
program_id: &Pubkey,
max_retries: usize,
delay_ms: u64,
) -> Result<(Pubkey, Pubkey, BondingCurveAccount)> {
let bonding_curve = get_pda(mint, program_id)?;
let associated_bonding_curve = get_associated_token_address(&bonding_curve, mint);
for attempt in 0..max_retries {
match rpc_client.get_account_data(&bonding_curve) {
Ok(data) => {
let bonding_curve_account: BondingCurveAccount =
from_slice::<BondingCurveAccount>(&data).map_err(|e| {
anyhow!("Failed to deserialize bonding curve account: {}", e)
})?;
return Ok((
bonding_curve,
associated_bonding_curve,
bonding_curve_account,
));
}
Err(err) => {
if attempt == max_retries - 1 {
return Err(anyhow!(
"Bonding curve account not found after retries: {}",
err
));
}
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
}
}
}
Err(anyhow!(
"Bonding curve account retry logic failed unexpectedly"
))
}
/// Derives the bonding curve PDA and its associated token account for a given mint.
pub async fn get_bonding_curve_account(
rpc_client: Arc<solana_client::rpc_client::RpcClient>,
mint: &Pubkey,
program_id: &Pubkey,
) -> Result<(Pubkey, Pubkey, BondingCurveAccount)> {
let bonding_curve = get_pda(mint, program_id)?;
let associated_bonding_curve =
spl_associated_token_account::get_associated_token_address(&bonding_curve, mint);
let bonding_curve_data = rpc_client
.get_account_data(&bonding_curve)
.inspect_err(|err| {
println!(
"Failed to get bonding curve account data: {}, err: {}",
bonding_curve, err
);
})?;
let bonding_curve_account =
from_slice::<BondingCurveAccount>(&bonding_curve_data).map_err(|e| {
anyhow!(
"Failed to deserialize bonding curve account: {}",
e.to_string()
)
})?;
Ok((
bonding_curve,
associated_bonding_curve,
bonding_curve_account,
))
}
/// Derives the PDA for the bonding curve from the mint and program id.
pub fn get_pda(mint: &Pubkey, program_id: &Pubkey) -> Result<Pubkey> {
let seeds = [b"bonding-curve".as_ref(), mint.as_ref()];
let (bonding_curve, _bump) = Pubkey::find_program_address(&seeds, program_id);
Ok(bonding_curve)
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct BondingCurveAccount {
pub discriminator: u64,
pub virtual_token_reserves: u64,
pub virtual_sol_reserves: u64,
pub real_token_reserves: u64,
pub real_sol_reserves: u64,
pub token_total_supply: u64,
pub complete: bool,
pub creator: Pubkey,
}
/// Retrieves pump info for a given token mint.
pub async fn get_pump_info(
rpc_client: Arc<solana_client::rpc_client::RpcClient>,
mint: &str,
) -> Result<PumpInfo> {
let mint = Pubkey::from_str(mint)?;
let program_id = Pubkey::from_str(PUMP_PROGRAM)?;
let (bonding_curve, associated_bonding_curve, bonding_curve_account) =
get_bonding_curve_account(rpc_client, &mint, &program_id).await?;
let pump_info = PumpInfo {
mint: mint.to_string(),
bonding_curve: bonding_curve.to_string(),
associated_bonding_curve: associated_bonding_curve.to_string(),
raydium_pool: None,
raydium_info: None,
complete: bonding_curve_account.complete,
virtual_sol_reserves: bonding_curve_account.virtual_sol_reserves,
virtual_token_reserves: bonding_curve_account.virtual_token_reserves,
total_supply: bonding_curve_account.token_total_supply,
};
Ok(pump_info)
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RaydiumInfo {
pub base: f64,
pub quote: f64,
pub price: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PumpInfo {
pub mint: String,
pub bonding_curve: String,
pub associated_bonding_curve: String,
pub raydium_pool: Option<String>,
pub raydium_info: Option<RaydiumInfo>,
pub complete: bool,
pub virtual_sol_reserves: u64,
pub virtual_token_reserves: u64,
pub total_supply: u64,
}

View File

@@ -0,0 +1 @@
pub mod swap;

View File

@@ -0,0 +1,85 @@
// swap.rs
use crate::common::utils::{AppState, SwapConfig, SwapInput};
use crate::dex::pump_fun::Pump;
use anyhow::Result;
use clap::ValueEnum;
use serde::Deserialize;
#[derive(ValueEnum, Copy, Debug, Clone, Deserialize, PartialEq)]
pub enum SwapDirection {
#[serde(rename = "buy")]
Buy,
#[serde(rename = "sell")]
Sell,
}
impl From<SwapDirection> for u8 {
fn from(value: SwapDirection) -> Self {
match value {
SwapDirection::Buy => 0,
SwapDirection::Sell => 1,
}
}
}
#[derive(ValueEnum, Debug, Clone, Deserialize)]
pub enum SwapInType {
#[serde(rename = "qty")]
Qty,
#[serde(rename = "pct")]
Pct,
}
pub async fn pump_swap(
state: AppState,
swap_input: SwapInput,
swap_direction: &str,
use_jito: bool,
urgent: bool,
) -> Result<Vec<String>> {
if swap_input.amount == 0 {
return Err(anyhow::anyhow!(
"Skipping swap: provided amount is 0 lamports"
));
}
// Convert the provided swap_direction string to our enum.
let swap_direction_enum = match swap_direction {
"buy" => SwapDirection::Buy,
"sell" => SwapDirection::Sell,
_ => anyhow::bail!("Invalid swap direction"),
};
// Default to the provided slippage...
let mut slippage = swap_input.slippage_bps as u64;
// 🔧 Add more slippage for sells to prevent 6003 errors
if swap_direction_enum == SwapDirection::Sell {
slippage += 50; // Add 10bps extra
}
let swap_config = SwapConfig {
slippage,
swap_direction: swap_direction_enum,
use_jito,
use_priority_tip: urgent,
cu_limits: None,
priority_fee: None,
wrap_and_unwrap_sol: Some(true),
as_legacy_transaction: Some(false),
};
let swapx = Pump::new(state.rpc_nonblocking_client, state.rpc_client, state.wallet);
let mint = if swap_direction_enum == SwapDirection::Buy {
swap_input.output_token_mint.to_string()
} else {
swap_input.input_token_mint.to_string()
};
let amount_in = swap_input.amount;
let res = swapx
.swap(&mint, swap_config, swap_input, amount_in)
.await?;
Ok(res)
}

View File

@@ -0,0 +1,151 @@
pub async fn handle_buy_and_sell_logic(
txn: &ConfirmedTransactionWithStatusMeta,
sell_sender: &Sender<(String, u64)>,
) {
let logger = Logger::new("[BUY/SELL LOGIC] => ".to_string());
let our_pubkey = env::var("WALLET_PUBKEY").unwrap_or_default();
let min_inflow: f64 = env::var("MIN_SOL_INFLOW_TRIGGER").unwrap_or("0.5".to_string()).parse().unwrap_or(0.5);
let min_outflow: f64 = env::var("MIN_SOL_OUTFLOW_TRIGGER").unwrap_or("0.5".to_string()).parse().unwrap_or(0.5);
// Extract our on-chain slot for comparisons
let current_slot = CURRENT_SLOT.load(std::sync::atomic::Ordering::Relaxed);
let TransactionWithStatusMeta::Complete(vtx) = &txn.tx_with_meta else { return };
let Some(post_balances) = &vtx.meta.post_token_balances else { return };
let Some(pre_balances) = &vtx.meta.pre_token_balances else { return };
let mut tokens = BOUGHT_TOKENS.lock().await;
for (i, post_tb) in post_balances.iter().enumerate() {
if i >= pre_balances.len() { continue; }
let pre_tb = &pre_balances[i];
let mint = &post_tb.mint;
let owner = post_tb.owner.clone();
let ata = post_tb.account_index.to_string();
let pre_amount = pre_tb.ui_token_amount.ui_amount.unwrap_or(0.0);
let post_amount = post_tb.ui_token_amount.ui_amount.unwrap_or(0.0);
let delta = post_amount - pre_amount;
if let Some(entry) = tokens.get_mut(mint) {
if entry.buy_slot() == 0 {
continue;
}
if entry.sell_triggered_at().is_some() {
continue;
}
let is_from_our_ata = Some(ata.clone()) == entry.ata().cloned();
let is_owner_ours = owner == our_pubkey;
// Time to slot delta instead of wall-clock
let slot_delta = current_slot.saturating_sub(entry.buy_slot());
if delta > 0.0 && !is_owner_ours && !is_from_our_ata {
if entry.add_unique_buyer(owner.clone()) {
logger.log(format!(
"🧍 Unique buyer for {}: {} | Total: {}",
mint,
owner,
entry.unique_buyers().len()
));
if entry.unique_buyers().len() == 1 && entry.suspected_creator().is_none() {
entry.set_suspected_creator(owner.clone());
info!("{}", format!("🧠 Suspected creator flagged: {} on {}", owner, mint));
}
}
// Early rug detection: <2 buyers after X slots
if entry.unique_buyers().len() < 10 && slot_delta > 12 {
entry.set_sell_triggered(true);
entry.set_sell_triggered_at(Instant::now());
info!("{}", format!("🛑 Rug suspected — Forcing sell on {} after {} slots", mint, slot_delta));
if sell_sender.send((mint.clone(), entry.amount())).await.is_ok() {
info!("{}", format!("📤 Sell sent for {}", mint));
}
continue;
}
// Exhausted follow-ups
if entry.unique_buyers().len() >= 5 {
entry.set_sell_triggered(true);
entry.set_sell_triggered_at(Instant::now());
info!("{}", format!("📉 Follow-up exhaustion — Selling {} after 3 buyers", mint));
if sell_sender.send((mint.clone(), entry.amount())).await.is_ok() {
info!("{}", format!("📤 Sell sent for {}", mint));
}
continue;
}
}
// SOL inflow/outflow logic (unchanged)
if let Ok(pid) = Pubkey::from_str(PUMP_PROGRAM) {
if let Ok(bonding_curve) = get_pda(&Pubkey::from_str(mint).unwrap(), &pid) {
let message = &vtx.transaction.message;
let mut keys = match message {
VersionedMessage::Legacy(m) => m.account_keys.clone(),
VersionedMessage::V0(v0) => {
let mut k = v0.account_keys.clone();
k.extend(vtx.meta.loaded_addresses.writable.clone());
k.extend(vtx.meta.loaded_addresses.readonly.clone());
k
}
};
if let Some(idx) = keys.iter().position(|k| k == &bonding_curve) {
let pre_sol = vtx.meta.pre_balances[idx];
let post_sol = vtx.meta.post_balances[idx];
let delta_sol = (post_sol as i64 - pre_sol as i64) as f64 / 1e9;
logger.log(format!(
"🔍 Mint: {} | ΔSOL: {:.6} | Slot: {}", mint, delta_sol, txn.slot
));
if delta_sol > 0.0 {
entry.add_sol_inflow(delta_sol);
} else if delta_sol < 0.0 {
entry.add_sol_outflow(-delta_sol);
if slot_delta <= 1 {
entry.set_sell_triggered(true);
entry.set_sell_triggered_at(Instant::now());
info!("{}", format!("💣 Early SOL drain! Fast sell on {}", mint));
if sell_sender.send((mint.clone(), entry.amount())).await.is_ok() {
info!("{}", format!("📤 Sell sent for {}", mint));
}
continue;
}
}
if delta_sol < -min_outflow {
entry.set_sell_triggered(true);
entry.set_sell_triggered_at(Instant::now());
info!("{}", format!("💣 Outflow sell on {} | ΔSOL: {:.6}", mint, delta_sol));
if sell_sender.send((mint.clone(), entry.amount())).await.is_ok() {
info!("{}", format!("📤 Sell sent for {}", mint));
}
continue;
}
if entry.sol_inflow() >= min_inflow {
entry.set_sell_triggered(true);
entry.set_sell_triggered_at(Instant::now());
info!("{}", format!("🚀 Inflow sell on {} | Total {:.6} SOL", mint, entry.sol_inflow()));
let mint_c = mint.clone(); let amt = entry.amount(); let tx_s = sell_sender.clone();
let target_slot = current_slot + 1;
tokio::spawn(async move {
while CURRENT_SLOT.load(Ordering::Relaxed) < target_slot {
tokio::time::sleep(Duration::from_millis(10)).await;
}
if tx_s.send((mint_c.clone(), amt)).await.is_ok() {
info!("📤 Delayed sell sent for {}", mint_c);
}
});
}
}
}
}
}
}
}

View File

@@ -0,0 +1,81 @@
use crate::serialization::serialize_pubkey;
use serde::{Deserialize, Serialize};
use solana_program::{program_error::ProgramError, pubkey::Pubkey};
use solana_sdk::instruction::AccountMeta;
#[derive(Deserialize)]
struct IdlInstruction {
name: String,
accounts: Vec<IdlAccount>,
}
#[derive(Deserialize)]
struct IdlAccount {
name: String,
#[serde(rename = "isMut")]
is_mut: bool,
#[serde(rename = "isSigner")]
is_signer: bool,
}
#[derive(Deserialize)]
pub struct Idl {
instructions: Vec<IdlInstruction>,
}
#[derive(Debug, Serialize)]
pub struct AccountMetadata {
#[serde(serialize_with = "serialize_pubkey")]
pub pubkey: Pubkey,
pub is_writable: bool,
pub is_signer: bool,
pub name: String,
}
pub trait InstructionAccountMapper<'info> {
fn map_accounts<'me>(
&self,
accounts: &[AccountMeta],
instruction_name: &str,
) -> Result<Vec<AccountMetadata>, ProgramError>;
}
impl<'info> InstructionAccountMapper<'info> for Idl {
fn map_accounts<'me>(
&self,
accounts: &[AccountMeta],
instruction_name: &str,
) -> Result<Vec<AccountMetadata>, ProgramError> {
let instruction = self
.instructions
.iter()
.find(|ix| ix.name == instruction_name)
.ok_or(ProgramError::InvalidArgument)?;
let mut account_metadata: Vec<AccountMetadata> = accounts
.iter()
.take(instruction.accounts.len())
.enumerate()
.map(|(i, account)| {
let account_info = &instruction.accounts[i];
AccountMetadata {
pubkey: account.pubkey,
is_writable: account_info.is_mut,
is_signer: account_info.is_signer,
name: account_info.name.clone(),
}
})
.collect();
for (i, account) in accounts.iter().enumerate().skip(instruction.accounts.len()) {
account_metadata.push(AccountMetadata {
pubkey: account.pubkey,
is_writable: account.is_writable,
is_signer: account.is_signer,
name: format!("Remaining accounts {}", i - instruction.accounts.len() + 1),
});
}
Ok(account_metadata)
}
}

View File

@@ -0,0 +1,3 @@
pub fn run_latency(block_time_a : i64, block_time_b: i64) -> i64 {
block_time_a - block_time_b
}

View File

@@ -0,0 +1,14 @@
use solana_client::rpc_client::RpcClient;
use solana_client::client_error::ClientError;
use anyhow::Context;
const API_KEY: &str = "api_key";
pub fn get_latest_slot() -> anyhow::Result<u64> {
let rpc_url = format!("https://rpc.va.shyft.to?api_key={}", API_KEY);
let client = RpcClient::new(rpc_url);
client.get_slot().context("Failed to fetch latest slot")
}

View File

@@ -0,0 +1,9 @@
pub mod common;
pub mod core;
pub mod dex;
pub mod engine;
pub mod services;
pub mod trading_loop;
pub use trading_loop::get_notify_handle;
pub use trading_loop::start_trading_loop;

View File

@@ -0,0 +1,90 @@
use chrono::Local;
use serde_json::json;
use std::{
fs::OpenOptions,
io::Write,
sync::{Arc, Mutex},
};
const LOG_LEVEL: &str = "LOG";
#[derive(Clone)]
pub struct Logger {
prefix: String,
date_format: String,
file: Arc<Mutex<std::fs::File>>,
}
impl Logger {
// Constructor to create a new logger that writes to "logs.json"
pub fn new(prefix: String) -> Self {
let file = OpenOptions::new()
.create(true)
.append(true)
.open("detectionlogs.json")
.expect("Failed to open logs.json for writing");
Logger {
prefix,
date_format: String::from("%Y-%m-%d %H:%M:%S"),
file: Arc::new(Mutex::new(file)),
}
}
pub fn log(&self, message: String) -> String {
let full_log = format!("{} {}", self.prefix_with_date(), message);
println!("{}", full_log);
self.write_json("INFO", &message);
full_log
}
pub fn debug(&self, message: String) -> String {
let full_log = format!("{} [{}] {}", self.prefix_with_date(), "DEBUG", message);
if LogLevel::new().is_debug() {
println!("{}", full_log);
self.write_json("DEBUG", &message);
}
full_log
}
pub fn error(&self, message: String) -> String {
let full_log = format!("{} [{}] {}", self.prefix_with_date(), "ERROR", message);
println!("{}", full_log);
self.write_json("ERROR", &message);
full_log
}
fn prefix_with_date(&self) -> String {
let date = Local::now();
format!("[{}] {}", date.format(&self.date_format), self.prefix)
}
fn write_json(&self, level: &str, message: &str) {
let now = Local::now();
let entry = json!({
"timestamp": now.to_rfc3339(),
"level": level,
"prefix": self.prefix,
"message": message,
});
if let Ok(mut file) = self.file.lock() {
if let Err(e) = writeln!(file, "{}", entry) {
eprintln!("Failed to write to log file: {}", e);
}
}
}
}
struct LogLevel<'a> {
level: &'a str,
}
impl LogLevel<'_> {
fn new() -> Self {
LogLevel { level: LOG_LEVEL }
}
fn is_debug(&self) -> bool {
self.level.to_lowercase() == "debug"
}
}

View File

@@ -0,0 +1,157 @@
#[derive(Debug, Clone, Copy)]
pub enum DetectionStrategy {
TokenCreation,
FirstBuy,
SuspectedCreatorSell,
}
pub async fn handle_detection_event(
txn: &ConfirmedTransactionWithStatusMeta,
slot: u64,
strategy: DetectionStrategy,
mint_override: Option<String>, // Only used for creation strategy
) {
match strategy {
DetectionStrategy::TokenCreation => {
if let Some(mint) = mint_override {
let creation_time = current_unix_timestamp();
let mut creations = TOKEN_CREATIONS.lock().await;
creations.insert(mint.clone(), TokenCreationInfo {
mint,
creation_time,
});
}
}
DetectionStrategy::FirstBuy => {
if TOKEN_CREATIONS.lock().await.is_empty() {
return;
}
if let TransactionWithStatusMeta::Complete(versioned_tx_with_meta) = &txn.tx_with_meta {
if let (Some(pre_token_balances), Some(post_token_balances)) =
(&versioned_tx_with_meta.meta.pre_token_balances, &versioned_tx_with_meta.meta.post_token_balances)
{
for (i, post_tb) in post_token_balances.iter().enumerate() {
if let Some(post_ui) = post_tb.ui_token_amount.ui_amount {
if i >= pre_token_balances.len() { continue; }
let pre_tb = &pre_token_balances[i];
if let Some(pre_ui) = pre_tb.ui_token_amount.ui_amount {
let delta = post_ui - pre_ui;
if delta >= 0.0 { continue; }
let buy_amount = -delta;
let mint = pre_tb.mint.clone();
let buyer = pre_tb.owner.clone();
const DEFAULT_TOTAL_SUPPLY: f64 = SUPPLY_THRESHOLD_UI;
const MIN_BUY_FRACTION: f64 = 0.000001;
const MAX_BUY_FRACTION: f64 = 0.10;
if pre_ui < DEFAULT_TOTAL_SUPPLY * 0.98 { continue; }
if buy_amount < DEFAULT_TOTAL_SUPPLY * MIN_BUY_FRACTION
|| buy_amount > DEFAULT_TOTAL_SUPPLY * MAX_BUY_FRACTION
{
continue;
}
let mut creations = TOKEN_CREATIONS.lock().await;
if !creations.contains_key(&mint) {
println!("[EVENT] No creation event for {}; skipping first buy.", mint);
continue;
}
creations.remove(&mint);
drop(creations);
let mut map = TRACKED_TOKENS.lock().await;
if let Some(entry) = map.get_mut(&mint) {
if entry.suspected_creators.insert(buyer.clone()) {
entry.sell_split_ratio = entry.suspected_creators.len();
println!(
"[FOLLOW-UP CREATOR] Mint: {} | New suspected creator: {} | Total creators: {}",
mint, buyer, entry.sell_split_ratio
);
}
} else {
let mut suspected = HashSet::new();
suspected.insert(buyer.clone());
map.insert(mint.clone(), TokenTrackingState {
mint: mint.clone(),
first_buyer: Some(buyer.clone()),
first_buy_amount: buy_amount,
suspected_creators: suspected,
sell_detected: false,
sell_split_ratio: 0,
has_bought: false,
bought_amount: None,
has_sold: false,
open_trade_permit: None,
detected_buy_slot: Some(slot),
});
println!("[NEW TOKEN TRACKED] Mint: {} | First Buyer: {} | Buy Amount: {} | Slot: {}",
mint, buyer, buy_amount, slot);
log::info!("New token tracked: {} bought by {}", mint, buyer);
let notify = get_notify_handle().await;
notify.notify_one();
}
}
}
}
}
}
}
DetectionStrategy::SuspectedCreatorSell => {
if let TransactionWithStatusMeta::Complete(versioned_tx_with_meta) = &txn.tx_with_meta {
if let (Some(pre_token_balances), Some(post_token_balances)) =
(&versioned_tx_with_meta.meta.pre_token_balances, &versioned_tx_with_meta.meta.post_token_balances)
{
for (i, post_tb) in post_token_balances.iter().enumerate() {
if let Some(post_ui) = post_tb.ui_token_amount.ui_amount {
if i < pre_token_balances.len() {
let pre_tb = &pre_token_balances[i];
if let Some(pre_ui) = pre_tb.ui_token_amount.ui_amount {
let delta = post_ui - pre_ui;
if delta >= 0.0 { continue; }
let mint = pre_tb.mint.clone();
let seller = pre_tb.owner.clone();
let mut map = TRACKED_TOKENS.lock().await;
if let Some(entry) = map.get_mut(&mint) {
if !entry.has_bought || entry.bought_amount.unwrap_or(0) == 0 {
log::info!(
"[SELL IGNORED] Mint {}: Suspected creator {} sold but we haven't bought yet",
mint, seller
);
return;
}
if entry.suspected_creators.contains(&seller) && !entry.sell_detected {
entry.sell_detected = true;
entry.sell_split_ratio = entry.suspected_creators.len();
log::info!(
"[SELL DETECTED] Mint {}: Seller {} triggered sell | Total suspected creators: {}",
mint, seller, entry.sell_split_ratio
);
let notify = get_notify_handle().await;
notify.notify_one();
}
}
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
use solana_sdk::pubkey::Pubkey;
pub fn serialize_pubkey<S>(value: &Pubkey, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&value.to_string())
}
pub fn serialize_option_pubkey<S>(value: &Option<Pubkey>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match value {
Some(pubkey) => serializer.serialize_str(&pubkey.to_string()),
None => serializer.serialize_none(),
}
}

View File

@@ -0,0 +1,180 @@
use std::{future::Future, str::FromStr, sync::LazyLock, time::Duration};
use anyhow::{anyhow, Result};
use indicatif::{ProgressBar, ProgressStyle};
use rand::{seq::IteratorRandom, thread_rng};
use serde::Deserialize;
use serde_json::Value;
use solana_sdk::pubkey::Pubkey;
use tokio::{
sync::RwLock,
time::{sleep, Instant},
};
use crate::common::utils::import_env_var;
pub static BLOCK_ENGINE_URL: LazyLock<String> =
LazyLock::new(|| import_env_var("JITO_BLOCK_ENGINE_URL"));
pub static TIP_STREAM_URL: LazyLock<String> =
LazyLock::new(|| import_env_var("JITO_TIP_STREAM_URL"));
pub static TIP_PERCENTILE: LazyLock<String> =
LazyLock::new(|| import_env_var("JITO_TIP_PERCENTILE"));
pub static TIP_ACCOUNTS: LazyLock<RwLock<Vec<String>>> = LazyLock::new(|| RwLock::new(vec![]));
#[derive(Debug)]
pub struct TipAccountResult {
pub accounts: Vec<String>,
}
pub async fn init_tip_accounts() -> Result<()> {
let accounts = TipAccountResult {
accounts: vec![
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5".to_string(),
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe".to_string(),
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY".to_string(),
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49".to_string(),
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh".to_string(),
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt".to_string(),
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL".to_string(),
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT".to_string(),
],
};
let mut tip_accounts = TIP_ACCOUNTS.write().await;
accounts
.accounts
.iter()
.for_each(|account| tip_accounts.push(account.to_string()));
Ok(())
}
pub async fn get_tip_account() -> Result<Pubkey> {
let accounts = TIP_ACCOUNTS.read().await;
let mut rng = thread_rng();
match accounts.iter().choose(&mut rng) {
Some(acc) => Ok(Pubkey::from_str(acc).inspect_err(|err| {
println!("jito: failed to parse Pubkey: {:?}", err);
})?),
None => Err(anyhow!("jito: no tip accounts available")),
}
}
// unit sol
pub async fn get_tip_value() -> Result<f64> {
// If TIP_VALUE is set, use it
if let Ok(tip_value) = std::env::var("JITO_TIP_VALUE") {
match f64::from_str(&tip_value) {
Ok(value) => Ok(value),
Err(_) => {
println!(
"Invalid JITO_TIP_VALUE in environment variable: '{}'. Falling back to percentile calculation.",
tip_value
);
Err(anyhow!("Invalid TIP_VALUE in environment variable"))
}
}
} else {
Err(anyhow!("JITO_TIP_VALUE environment variable not set"))
}
}
pub async fn fetch_bundle_status(bundle_id: String) -> Result<Vec<serde_json::Value>> {
// Example implementation using reqwest:
use reqwest::Client;
let url = format!(
"{}/bundle/{}",
crate::common::utils::import_env_var("JITO_BLOCK_ENGINE_URL"),
bundle_id
);
let client = Client::new();
let resp = client.get(&url).send().await?;
let json: Vec<serde_json::Value> = resp.json().await?;
Ok(json)
}
#[derive(Deserialize, Debug)]
pub struct BundleStatus {
pub bundle_id: String,
pub transactions: Vec<String>,
pub slot: u64,
pub confirmation_status: String,
pub err: ErrorStatus,
}
#[derive(Deserialize, Debug)]
pub struct ErrorStatus {
#[serde(rename = "Ok")]
pub ok: Option<()>,
}
pub async fn wait_for_bundle_confirmation<F, Fut>(
fetch_statuses: F,
bundle_id: String,
interval: Duration,
timeout: Duration,
) -> Result<Vec<String>>
where
F: Fn(String) -> Fut,
Fut: Future<Output = Result<Vec<Value>>>,
{
let progress_bar = new_progress_bar();
let start_time = Instant::now();
loop {
let statuses = fetch_statuses(bundle_id.clone()).await?;
if let Some(status) = statuses.first() {
let bundle_status: BundleStatus =
serde_json::from_value(status.clone()).inspect_err(|err| {
println!(
"Failed to parse JSON when get_bundle_statuses, err: {}",
err,
);
})?;
println!("{:?}", bundle_status);
match bundle_status.confirmation_status.as_str() {
"finalized" | "confirmed" => {
progress_bar.finish_and_clear();
println!(
"Finalized bundle {}: {}",
bundle_id, bundle_status.confirmation_status
);
// print tx
bundle_status
.transactions
.iter()
.for_each(|tx| println!("https://solscan.io/tx/{}", tx));
return Ok(bundle_status.transactions);
}
_ => {
progress_bar.set_message(format!(
"Finalizing bundle {}: {}",
bundle_id, bundle_status.confirmation_status
));
}
}
} else {
progress_bar.set_message(format!("Finalizing bundle {}: {}", bundle_id, "None"));
}
// check loop exceeded 1 minute,
if start_time.elapsed() > timeout {
println!("Loop exceeded {:?}, breaking out.", timeout);
return Err(anyhow!("Bundle status get timeout"));
}
// Wait for a certain duration before retrying
sleep(interval).await;
}
}
pub fn new_progress_bar() -> ProgressBar {
let progress_bar = ProgressBar::new(42);
progress_bar.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {wide_msg}")
.expect("ProgressStyle::template direct input to be correct"),
);
progress_bar.enable_steady_tick(Duration::from_millis(100));
progress_bar
}

View File

@@ -0,0 +1,3 @@
pub mod jito;
pub mod nextblock;
pub mod zeroslot;

View File

@@ -0,0 +1,75 @@
use anyhow::{anyhow, Result};
use rand::{seq::IteratorRandom, thread_rng};
use solana_sdk::pubkey::Pubkey;
use std::{str::FromStr, sync::LazyLock};
use tokio::sync::RwLock;
use crate::common::utils::import_env_var;
// Endpoint and auth token from env
pub static NEXTBLOCK_API_URL: LazyLock<String> =
LazyLock::new(|| import_env_var("NEXTBLOCK_API_URL"));
pub static NEXTBLOCK_AUTH_HEADER: LazyLock<String> =
LazyLock::new(|| import_env_var("NEXTBLOCK_AUTH_HEADER"));
pub static NEXTBLOCK_TIP_VALUE: LazyLock<String> =
LazyLock::new(|| import_env_var("NEXTBLOCK_TIP_VALUE"));
// List of hardcoded tip accounts
pub static TIP_ACCOUNTS: LazyLock<RwLock<Vec<String>>> = LazyLock::new(|| RwLock::new(vec![]));
#[derive(Debug)]
pub struct TipAccountResult {
pub accounts: Vec<String>,
}
pub async fn init_tip_accounts() -> Result<()> {
let accounts = TipAccountResult {
accounts: vec![
"NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE".to_string(),
"NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2".to_string(),
"NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X".to_string(),
"NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb".to_string(),
"neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At".to_string(),
"nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG".to_string(),
"NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid".to_string(),
"nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc".to_string(),
],
};
let mut tip_accounts = TIP_ACCOUNTS.write().await;
accounts
.accounts
.iter()
.for_each(|account| tip_accounts.push(account.to_string()));
Ok(())
}
pub async fn get_tip_account() -> Result<Pubkey> {
let accounts = TIP_ACCOUNTS.read().await;
let mut rng = thread_rng();
match accounts.iter().choose(&mut rng) {
Some(acc) => Ok(Pubkey::from_str(acc).inspect_err(|err| {
println!("nextblock: failed to parse Pubkey: {:?}", err);
})?),
None => Err(anyhow!("nextblock: no tip accounts available")),
}
}
// unit sol
pub async fn get_tip_value() -> Result<f64> {
// If TIP_VALUE is set, use it
if let Ok(tip_value) = std::env::var("NEXTBLOCK_TIP_VALUE") {
match f64::from_str(&tip_value) {
Ok(value) => Ok(value),
Err(_) => {
println!(
"Invalid NEXTBLOCK_TIP_VALUE in environment variable: '{}'. Falling back to percentile calculation.",
tip_value
);
Err(anyhow!("Invalid TIP_VALUE in environment variable"))
}
}
} else {
Err(anyhow!("NEXTBLOCK_TIP_VALUE environment variable not set"))
}
}

View File

@@ -0,0 +1,179 @@
use std::{future::Future, str::FromStr, sync::LazyLock, time::Duration};
use anyhow::{anyhow, Result};
use indicatif::{ProgressBar, ProgressStyle};
use rand::{seq::IteratorRandom, thread_rng};
use serde::Deserialize;
use serde_json::Value;
use solana_sdk::pubkey::Pubkey;
use tokio::{
sync::RwLock,
time::{sleep, Instant},
};
use crate::common::utils::import_env_var;
pub static BLOCK_ENGINE_URL: LazyLock<String> =
LazyLock::new(|| import_env_var("ZEROSLOT_BLOCK_ENGINE_URL"));
pub static TIP_STREAM_URL: LazyLock<String> =
LazyLock::new(|| import_env_var("ZEROSLOT_TIP_STREAM_URL"));
pub static TIP_PERCENTILE: LazyLock<String> =
LazyLock::new(|| import_env_var("ZEROSLOT_TIP_PERCENTILE"));
pub static TIP_ACCOUNTS: LazyLock<RwLock<Vec<String>>> = LazyLock::new(|| RwLock::new(vec![]));
#[derive(Debug)]
pub struct TipAccountResult {
pub accounts: Vec<String>,
}
pub async fn init_tip_accounts() -> Result<()> {
let accounts = TipAccountResult {
accounts: vec![
"Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3".to_string(),
"FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe".to_string(),
"ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13".to_string(),
"6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK".to_string(),
"Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr".to_string(),
],
};
let mut tip_accounts = TIP_ACCOUNTS.write().await;
accounts
.accounts
.iter()
.for_each(|account| tip_accounts.push(account.to_string()));
Ok(())
}
pub async fn get_tip_account() -> Result<Pubkey> {
let accounts = TIP_ACCOUNTS.read().await;
let mut rng = thread_rng();
match accounts.iter().choose(&mut rng) {
Some(acc) => Ok(Pubkey::from_str(acc).inspect_err(|err| {
println!("jito: failed to parse Pubkey: {:?}", err);
})?),
None => Err(anyhow!("jito: no tip accounts available")),
}
}
// unit sol
pub async fn get_tip_value() -> Result<f64> {
// If TIP_VALUE is set, use it
if let Ok(tip_value) = std::env::var("ZEROSLOT_TIP_VALUE") {
match f64::from_str(&tip_value) {
Ok(value) => Ok(value),
Err(_) => {
println!(
"Invalid ZEROSLOT_TIP_VALUE in environment variable: '{}'. Falling back to percentile calculation.",
tip_value
);
Err(anyhow!(
"Invalid ZEROSLOT_TIP_VALUE in environment variable"
))
}
}
} else {
Err(anyhow!("ZEROSLOT_TIP_VALUE environment variable not set"))
}
}
pub async fn fetch_bundle_status(bundle_id: String) -> Result<Vec<serde_json::Value>> {
// Example implementation using reqwest:
use reqwest::Client;
let url = format!(
"{}/bundle/{}",
crate::common::utils::import_env_var("JITO_BLOCK_ENGINE_URL"),
bundle_id
);
let client = Client::new();
let resp = client.get(&url).send().await?;
let json: Vec<serde_json::Value> = resp.json().await?;
Ok(json)
}
#[derive(Deserialize, Debug)]
pub struct BundleStatus {
pub bundle_id: String,
pub transactions: Vec<String>,
pub slot: u64,
pub confirmation_status: String,
pub err: ErrorStatus,
}
#[derive(Deserialize, Debug)]
pub struct ErrorStatus {
#[serde(rename = "Ok")]
pub ok: Option<()>,
}
pub async fn wait_for_bundle_confirmation<F, Fut>(
fetch_statuses: F,
bundle_id: String,
interval: Duration,
timeout: Duration,
) -> Result<Vec<String>>
where
F: Fn(String) -> Fut,
Fut: Future<Output = Result<Vec<Value>>>,
{
let progress_bar = new_progress_bar();
let start_time = Instant::now();
loop {
let statuses = fetch_statuses(bundle_id.clone()).await?;
if let Some(status) = statuses.first() {
let bundle_status: BundleStatus =
serde_json::from_value(status.clone()).inspect_err(|err| {
println!(
"Failed to parse JSON when get_bundle_statuses, err: {}",
err,
);
})?;
println!("{:?}", bundle_status);
match bundle_status.confirmation_status.as_str() {
"finalized" | "confirmed" => {
progress_bar.finish_and_clear();
println!(
"Finalized bundle {}: {}",
bundle_id, bundle_status.confirmation_status
);
// print tx
bundle_status
.transactions
.iter()
.for_each(|tx| println!("https://solscan.io/tx/{}", tx));
return Ok(bundle_status.transactions);
}
_ => {
progress_bar.set_message(format!(
"Finalizing bundle {}: {}",
bundle_id, bundle_status.confirmation_status
));
}
}
} else {
progress_bar.set_message(format!("Finalizing bundle {}: {}", bundle_id, "None"));
}
// check loop exceeded 1 minute,
if start_time.elapsed() > timeout {
println!("Loop exceeded {:?}, breaking out.", timeout);
return Err(anyhow!("Bundle status get timeout"));
}
// Wait for a certain duration before retrying
sleep(interval).await;
}
}
pub fn new_progress_bar() -> ProgressBar {
let progress_bar = ProgressBar::new(42);
progress_bar.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {wide_msg}")
.expect("ProgressStyle::template direct input to be correct"),
);
progress_bar.enable_steady_tick(Duration::from_millis(100));
progress_bar
}

View File

@@ -0,0 +1,505 @@
// src/shred_stream.rs
// Module to replace Geyser-based streams with Jito Shred-stream
use crate::apply_sol_flow_rules;
use crate::MIN_SOL_INFLOW_TRIGGER;
use crate::MIN_SOL_OUTFLOW_TRIGGER;
use crate::PENDING_MINTS;
use crate::PUMP_PROGRAM_PUBKEY;
use crate::{flatten_transaction_response, try_send_buy_if_allowed};
use anyhow::Context;
use backoff::future::retry;
use backoff::ExponentialBackoff;
use jito_protos::shredstream::{
shredstream_proxy_client::ShredstreamProxyClient, SubscribeEntriesRequest,
};
use log::{debug, info, warn};
use once_cell::sync::Lazy;
use pump_interface::PumpProgramIx;
use shared_state::CURRENT_SLOT;
use shared_state::{BuyOrder, SellOrder, BOUGHT_TOKENS};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_entry::entry::Entry as SolanaEntry;
use solana_sdk::message::VersionedMessage;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::transaction::VersionedTransaction;
use solana_transaction_status::{
ConfirmedTransactionWithStatusMeta, TransactionStatusMeta, TransactionWithStatusMeta,
VersionedTransactionWithStatusMeta,
};
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc::Sender;
use tokio::sync::Mutex;
use tokio_stream::StreamExt;
const FALLBACK_SELL_AFTER_SLOTS: u64 = 50;
struct MintTracker {
creator: Pubkey,
last_activity: Instant,
sol_inflow: f64,
sol_outflow: f64,
waiting_sw: bool,
silence_until: Instant,
}
static MINT_TRACKERS: Lazy<Mutex<HashMap<String, MintTracker>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
// Silence window duration before we begin listening for the "second wave"
static SILENCE_DURATION: Lazy<Duration> = Lazy::new(|| {
let secs = std::env::var("SW_SILENCE_SECS").unwrap_or_else(|_| "60".into());
Duration::from_secs(secs.parse().unwrap_or(60))
});
// at top of file, make sure you have:
static SECOND_WAVE_MODE: Lazy<bool> = Lazy::new(|| {
std::env::var("SECOND_WAVE_MODE").map(|v| v == "true").unwrap_or(false)
});
/// Begin tracking a newly detected mint, recording its creator
/// and setting up the silence window before secondwave sniping.
async fn start_tracking_mint(mint: String, creator: Pubkey) {
let now = Instant::now();
let silence = *SILENCE_DURATION; // from your SW_SILENCE_SECS env
let tracker = MintTracker {
creator,
last_activity: now,
sol_inflow: 0.0,
sol_outflow: 0.0,
waiting_sw: false,
silence_until: now + silence,
};
MINT_TRACKERS.lock().await.insert(mint, tracker);
}
/// Subscribe to Jito shredstream and manage newmint sniping vs. secondwave mode
pub async fn shred_subscribe_transactions(
url: String,
sell_sender: Sender<SellOrder>,
mint_sender: Sender<BuyOrder>,
) -> anyhow::Result<()> {
let backoff = ExponentialBackoff::default();
let mut client = retry(backoff, || async {
ShredstreamProxyClient::connect(url.clone())
.await
.map_err(backoff::Error::transient)
})
.await?;
let mut stream = client
.subscribe_entries(SubscribeEntriesRequest::default())
.await?
.into_inner();
while let Some(Ok(batch)) = stream.next().await {
let slot = batch.slot;
CURRENT_SLOT.store(slot, Ordering::Relaxed);
let entries: Vec<SolanaEntry> = bincode::deserialize(&batch.entries)?;
for sol_entry in entries {
for tx in sol_entry.transactions {
if let Some((mint, creator)) = detect_raw_create(&tx) {
info!("New mint {} @ slot {}", mint, slot);
// Always start the silencewindow tracker
let mint_clone = mint.clone();
tokio::spawn(async move {
start_tracking_mint(mint_clone, creator).await;
});
// Only fire the *immediate* newmint buy if NOT in secondwave mode
if !*SECOND_WAVE_MODE {
try_send_buy_if_allowed(
&mint,
creator,
true, // first_buy
true, // urgent=false
&mint_sender,
)
.await;
}
}
// offload your reconcilers (flows, confirms, sells)
let sell_tx = sell_sender.clone();
let mint_tx = mint_sender.clone();
tokio::spawn(async move {
let confirmed = confirm_from_versioned(tx, slot);
reconcile_flows(&confirmed, &sell_tx, &mint_tx).await;
reconcile_buys(&confirmed, slot).await;
reconcile_sells(&confirmed, slot, &sell_tx).await;
});
}
}
}
Ok(())
}
/// Update flows and trigger second-wave sniping, but only after our own buy has confirmed.
async fn reconcile_flows(
confirmed: &ConfirmedTransactionWithStatusMeta,
sell_sender: &Sender<SellOrder>,
mint_sender: &Sender<BuyOrder>,
) {
// only complete transactions
let vtx = match &confirmed.tx_with_meta {
TransactionWithStatusMeta::Complete(v) => v,
_ => return,
};
let now = Instant::now();
for tx_ix in flatten_transaction_response(vtx) {
// skip non-Pump instructions
if tx_ix.instruction.program_id != *PUMP_PROGRAM_PUBKEY {
continue;
}
let mint = tx_ix.instruction.accounts[2].pubkey.to_string();
// dont start counting flows until our buy has landed
{
let tokens = BOUGHT_TOKENS.lock().await;
if tokens.get(&mint).map(|e| e.buy_slot()).unwrap_or(0) == 0 {
continue;
}
}
if let Some(tracker) = MINT_TRACKERS.lock().await.get_mut(&mint) {
tracker.last_activity = now;
if let Ok(ix) = PumpProgramIx::deserialize(&tx_ix.instruction.data) {
match ix {
PumpProgramIx::Buy(buy_args) => {
let sol_in = buy_args.max_sol_cost as f64 / 1e9;
tracker.sol_inflow += sol_in;
debug!("SOL inflow +{:.6} on {}", sol_in, mint);
if tracker.waiting_sw {
info!("🚀 Second wave hit on {} → sniping", mint);
try_send_buy_if_allowed(
&mint,
tracker.creator,
true,
true,
mint_sender,
).await;
tracker.waiting_sw = false;
}
}
PumpProgramIx::Sell(sell_args) => {
let sol_out = sell_args.min_sol_output as f64 / 1e9;
tracker.sol_outflow += sol_out;
debug!("SOL outflow +{:.6} on {}", sol_out, mint);
}
_ => {}
}
}
if !tracker.waiting_sw && now >= tracker.silence_until {
tracker.waiting_sw = true;
info!("⏱ {} entered second-wave watch mode", mint);
}
}
}
}
/// Confirm our buy, then reset that mints tracker so we only count SOL flows from now on.
async fn reconcile_buys(
confirmed: &ConfirmedTransactionWithStatusMeta,
slot: u64,
) {
// pull out on-chain signature (if any)
let sig_opt = match &confirmed.tx_with_meta {
TransactionWithStatusMeta::Complete(v) => v.transaction.signatures.get(0).cloned(),
_ => None,
};
if let Some(sig) = sig_opt {
let sig_str = sig.to_string();
let mut tokens = BOUGHT_TOKENS.lock().await;
// find our minted entry by matching its stored signature
if let Some((mint, entry)) = tokens
.iter_mut()
.find(|(_, e)| e.signature() == Some(&sig_str))
{
// confirm the buy
entry.set_buy_slot(slot);
entry.set_buy_executed_at(Instant::now());
PENDING_MINTS.fetch_sub(1, Ordering::Relaxed);
info!("✅ Confirmed BUY for {} @ slot {}", mint, slot);
// re-arm second-wave tracker
if let Some(tracker) = MINT_TRACKERS.lock().await.get_mut(mint) {
let now = Instant::now();
tracker.last_activity = now;
tracker.sol_inflow = 0.0;
tracker.sol_outflow = 0.0;
tracker.waiting_sw = false;
tracker.silence_until = now + *SILENCE_DURATION;
info!("🔄 Re-armed second-wave tracker for {}", mint);
}
}
}
}
fn detect_raw_create(vtx: &VersionedTransaction) -> Option<(String, Pubkey)> {
let msg = &vtx.message;
let keys = msg.static_account_keys();
debug!(
"🔑 static keys (len={}): {:?}",
keys.len(),
keys.iter().take(4).collect::<Vec<_>>()
);
for instr in msg.instructions() {
let program_key = keys[instr.program_id_index as usize];
if program_key == *PUMP_PROGRAM_PUBKEY {
debug!(
"🧩 Found PumpProgramIx at idx={} data_prefix={:02x?}",
instr.program_id_index,
&instr.data[..4.min(instr.data.len())]
);
match PumpProgramIx::deserialize(&instr.data) {
Ok(PumpProgramIx::Create(args)) => {
let mint_key = keys[instr.accounts[0] as usize];
debug!("✅ Deserialized Create; mint = {}", mint_key);
return Some((mint_key.to_string(), args.creator));
}
Ok(other) => {
debug!(" Other PumpProgramIx::{:?}, skipping", other);
}
Err(e) => {
debug!("❌ deserialize error: {}", e);
}
}
}
}
None
}
/// Quickly wrap a VersionedTransaction in a ConfirmedTransactionWithStatusMeta
fn confirm_from_versioned(
tx: solana_sdk::transaction::VersionedTransaction,
slot: u64,
) -> ConfirmedTransactionWithStatusMeta {
// minimal meta with defaults:
let meta = TransactionStatusMeta {
status: Ok(()),
fee: 0,
pre_balances: vec![],
post_balances: vec![],
inner_instructions: None,
log_messages: None,
pre_token_balances: None,
post_token_balances: None,
rewards: None,
loaded_addresses: Default::default(),
return_data: None,
compute_units_consumed: None,
};
let vtx = VersionedTransactionWithStatusMeta {
transaction: tx,
meta,
};
ConfirmedTransactionWithStatusMeta {
slot,
tx_with_meta: TransactionWithStatusMeta::Complete(vtx),
block_time: None,
}
}
/// Decode raw bytes and slot into ConfirmedTransactionWithStatusMeta
pub fn decode_txn_from_bytes(
raw: &[u8],
slot: u64,
) -> anyhow::Result<ConfirmedTransactionWithStatusMeta> {
use bincode;
use solana_sdk::transaction::VersionedTransaction;
use solana_transaction_status::TransactionStatusMeta;
// 1. Deserialize the wire-format VersionedTransaction
let tx: VersionedTransaction = bincode::deserialize(raw)
.context("failed to deserialize VersionedTransaction from shred entry")?;
// 2. Build a minimal TransactionStatusMeta with defaults
let meta = TransactionStatusMeta {
status: Ok(()),
fee: 0,
pre_balances: vec![],
post_balances: vec![],
inner_instructions: None,
log_messages: None,
pre_token_balances: None,
post_token_balances: None,
rewards: None,
loaded_addresses: Default::default(),
return_data: None,
compute_units_consumed: None,
};
// 3. Wrap into VersionedTransactionWithStatusMeta and then into ConfirmedTransactionWithStatusMeta
let vtx = VersionedTransactionWithStatusMeta {
transaction: tx,
meta,
};
Ok(ConfirmedTransactionWithStatusMeta {
slot,
tx_with_meta: TransactionWithStatusMeta::Complete(vtx),
block_time: None,
})
}
const LOW_BUY_FREQ_WINDOW_SLOTS: u64 = 10;
const MIN_BUYS_IN_WINDOW: usize = 6;
const BUY_CONFIRM_TIMEOUT_SECS: u64 = 60;
const SELL_CONFIRM_TIMEOUT_SECS: u64 = 60;
async fn reconcile_sells(
confirmed: &ConfirmedTransactionWithStatusMeta,
slot: u64,
sell_sender: &Sender<SellOrder>,
) {
// --- 0) remove any entries that are already confirmed sells, just in case ---
{
let mut tokens = BOUGHT_TOKENS.lock().await;
let to_remove: Vec<String> = tokens
.iter()
.filter_map(|(mint, entry)| {
if entry.sell_confirmed() {
Some(mint.clone())
} else {
None
}
})
.collect();
for mint in to_remove {
tokens.remove(&mint);
}
}
// 1) On-chain confirmations
if let Some(sig) = match &confirmed.tx_with_meta {
TransactionWithStatusMeta::Complete(v) => {
v.transaction.signatures.get(0).map(|s| s.to_string())
}
_ => None,
} {
// find mint without holding a mutable borrow across removal
let mint_opt = {
let tokens = BOUGHT_TOKENS.lock().await;
tokens
.iter()
.find(|(_, e)| e.sell_signature().as_deref() == Some(&sig))
.map(|(m, _)| m.clone())
};
if let Some(mint) = mint_opt {
// confirm + slot in one lock
{
let mut tokens = BOUGHT_TOKENS.lock().await;
if let Some(entry) = tokens.get_mut(&mint) {
entry.confirm_sell();
entry.set_sell_slot(slot);
}
// guard against underflow
if PENDING_MINTS.load(Relaxed) > 0 {
PENDING_MINTS.fetch_sub(1, Relaxed);
}
}
// remove in a separate lock
{
let mut tokens = BOUGHT_TOKENS.lock().await;
tokens.remove(&mint);
}
log::info!("✅ Confirmed SELL for {} @ slot {}", mint, slot);
return;
}
}
// 2) retry logic
// build stale list under lock
let now = Instant::now();
let timeout = Duration::from_secs(SELL_CONFIRM_TIMEOUT_SECS);
let stale: Vec<String> = {
let tokens = BOUGHT_TOKENS.lock().await;
tokens
.iter()
.filter_map(|(mint, entry)| {
if entry.sell_signature().is_some()
&& !entry.sell_confirmed()
&& entry.retry_count() < 3
&& entry
.sell_executed_at()
.map(|t| now.duration_since(t) > timeout)
.unwrap_or(false)
{
Some(mint.clone())
} else {
None
}
})
.collect()
};
// drop lock, then re-send each stale sell
for mint in stale {
let mut tokens = BOUGHT_TOKENS.lock().await;
if let Some(entry) = tokens.get_mut(&mint) {
entry.increment_retry_count();
log::info!(
"⌛ Sell for {} timed out; retry #{}, re-sending…",
mint,
entry.retry_count()
);
let amount = entry.amount();
let _ = sell_sender
.send(SellOrder {
mint: mint.clone(),
amount,
use_jito: true,
urgent: true,
})
.await;
// record when we issued this retry
entry.set_sell_executed_at(Instant::now());
}
}
}
/// Find mint & creator from Create ix
fn extract_mint_and_creator(
confirmed: &ConfirmedTransactionWithStatusMeta,
) -> Option<(String, Pubkey)> {
if let TransactionWithStatusMeta::Complete(v) = &confirmed.tx_with_meta {
for tx_ix in flatten_transaction_response(v) {
if tx_ix.instruction.program_id == *crate::PUMP_PROGRAM_PUBKEY {
if let Ok(PumpProgramIx::Create(args)) =
PumpProgramIx::deserialize(&tx_ix.instruction.data)
{
// the very first account is the mint
if let Some(meta) = tx_ix.instruction.accounts.get(0) {
// return (mint_pubkey_string, creator_pubkey)
return Some((meta.pubkey.to_string(), args.creator));
}
}
}
}
}
None
}

View File

@@ -0,0 +1,179 @@
// src/solana_helpers.rs
use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or `aes_gcm::Aes256Gcm`
use aes_gcm::aead::{Aead, NewAead};
use base64::{encode as b64_encode, decode as b64_decode};
use hex::decode as hex_decode;
use rand::RngCore;
use rand::rngs::OsRng;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::pubkey::Pubkey;
use bip39::{Mnemonic, Language, Seed};
use anyhow::{Result, Context};
use serde_json::Value;
/// Load the AES-256 key from the environment variable `ENCRYPTION_KEY`.
/// The env var should be a hex-encoded 32-byte key (i.e., 64 hex chars).
fn load_encryption_key() -> Result<[u8; 32]> {
let hex = std::env::var("ENCRYPTION_KEY")
.context("ENCRYPTION_KEY env var not set")?;
let bytes = hex_decode(&hex)
.context("Failed to hex-decode ENCRYPTION_KEY")?;
if bytes.len() != 32 {
anyhow::bail!("ENCRYPTION_KEY must be 32 bytes (hex-encoded 64 chars)");
}
let mut key = [0u8; 32];
key.copy_from_slice(&bytes);
Ok(key)
}
/// Encrypt `plaintext` using AES-256-GCM. Returns a base64 string containing nonce||ciphertext.
/// Format: base64( [12-byte nonce] || ciphertext ).
pub fn encrypt_data(plaintext: &str) -> Result<String> {
let key_bytes = load_encryption_key()?;
let key = Key::from_slice(&key_bytes);
let cipher = Aes256Gcm::new(key);
// Generate a random 12-byte nonce
let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, plaintext.as_bytes())
.context("Encryption failure")?;
// Prepend nonce
let mut combined = Vec::with_capacity(12 + ciphertext.len());
combined.extend_from_slice(&nonce_bytes);
combined.extend_from_slice(&ciphertext);
Ok(b64_encode(&combined))
}
/// Decrypt a base64 string produced by `encrypt_data`, returning the plaintext.
pub fn decrypt_data(encoded: &str) -> Result<String> {
let combined = b64_decode(encoded).context("Base64 decode failed")?;
if combined.len() < 12 {
anyhow::bail!("Ciphertext too short");
}
let (nonce_bytes, ciphertext) = combined.split_at(12);
let key_bytes = load_encryption_key()?;
let key = Key::from_slice(&key_bytes);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(nonce_bytes);
let plaintext = cipher
.decrypt(nonce, ciphertext)
.context("Decryption failure")?;
let s = String::from_utf8(plaintext).context("Decrypted data not valid UTF-8")?;
Ok(s)
}
/// Derive Solana public address (Pubkey) from a JSON-array keypair string.
///
/// # Arguments
/// - `json`: a JSON string representing an array of 64 or 32 or 64 u8 values, e.g. `[12,34, ...]`.
/// - If length is 64: interpreted as full keypair bytes.
/// - If length is 32: interpreted as secret seed, but Solana Keypair::from_bytes expects 64; you may need to expand.
///
/// Returns the `Pubkey` as a base58 string.
pub fn derive_address_from_json_keypair(json: &str) -> Result<String> {
// Parse JSON array
let v: Value = serde_json::from_str(json).context("Invalid JSON")?;
let arr = v.as_array().context("Expected JSON array")?;
// Convert to Vec<u8>
let bytes: Vec<u8> = arr.iter()
.map(|val| {
val.as_u64()
.and_then(|n| Some(n as u8))
.ok_or_else(|| anyhow::anyhow!("Invalid byte in array"))
})
.collect::<Result<Vec<_>, _>>()?;
// Depending on length:
let kp = if bytes.len() == 64 {
Keypair::from_bytes(&bytes).context("Failed to parse keypair from bytes")?
} else if bytes.len() == 32 {
// interpret as secret seed: expand to keypair via `from_seed`
// Note: solana_sdk::signature::Keypair::from_seed requires `Signer` trait; but no direct from_seed in stable.
// We can use ed25519_dalek to expand, then wrap in Keypair.
// Solana Keypair::from_seed is nightly; instead:
// Use solana_sdk::signature::Keypair::from_seed (requires "ed25519-dalek" feature)
Keypair::from_seed(&bytes).context("Failed to derive keypair from 32-byte seed")?
} else {
anyhow::bail!("Keypair JSON must be 32 or 64 bytes");
};
let pubkey = kp.pubkey();
Ok(pubkey.to_string())
}
/// Derive Solana public address from a base58-encoded secret key (64-byte) string.
/// E.g., if user has a base58 of secret key bytes.
///
/// # Arguments
/// - `b58`: base58 string of 64 bytes.
/// Returns base58 pubkey string.
pub fn derive_address_from_base58_secret(b58: &str) -> Result<String> {
let data = bs58::decode(b58).into_vec().context("Invalid base58")?;
if data.len() == 64 {
let kp = Keypair::from_bytes(&data).context("Failed to parse keypair bytes")?;
Ok(kp.pubkey().to_string())
} else if data.len() == 32 {
let kp = Keypair::from_seed(&data).context("Failed to derive keypair from seed")?;
Ok(kp.pubkey().to_string())
} else {
anyhow::bail!("Base58 secret must be 32 or 64 bytes");
}
}
/// Derive Solana public address from a BIP39 mnemonic phrase.
/// Uses the standard Solana derivation: m/44'/501'/0'/0' by default, but this can be adjusted.
///
/// # Arguments
/// - `mnemonic`: the phrase, e.g. "abandon abandon ...".
/// - `passphrase`: optional passphrase for mnemonic (usually empty string).
/// - `account`: u32, default 0.
/// - `change`: u32, default 0.
///
/// Returns base58 pubkey string.
pub fn derive_address_from_mnemonic(
mnemonic: &str,
passphrase: &str,
account: u32,
change: u32,
) -> Result<String> {
// Parse mnemonic
let mn = Mnemonic::from_phrase(mnemonic, Language::English)
.context("Invalid mnemonic phrase")?;
let seed = Seed::new(&mn, passphrase);
let seed_bytes = seed.as_bytes(); // 64 bytes
// Derivation path for Solana: m/44'/501'/<account>'/<change>'
// We can use `solana_sdk::derivation_path::DerivationPath` if available, or derive via ed25519-dalek + slip10.
// For simplicity, we use `solana_sdk::derivation_path::DerivationPath` if in scope:
// If not, use `ed25519-dalek-bip32` or similar. Here we assume solana-sdk has `derive_keypair_from_seed_and_path`.
// As of solana-sdk v1.14+, there's `keypair_from_seed_and_derivation_path`.
#[cfg(feature = "solana-derive-keypair")]
{
// Pseudo-code; adjust per your solana-sdk version:
/*
use solana_sdk::derivation_path::DerivationPath;
let path = DerivationPath::new_bip44(Some(501), Some(account), Some(change));
let kp = Keypair::from_seed_with_derivation(&seed_bytes, &path)
.context("Failed deriving keypair from mnemonic")?;
Ok(kp.pubkey().to_string())
*/
unimplemented!("Adjust to your solana-sdk version's derivation API");
}
#[cfg(not(feature = "solana-derive-keypair"))]
{
// As fallback: derive the ED25519 key by directly using the seed (not recommended for multiple accounts).
// Here we just derive from seed_bytes[0..32]:
let seed32 = &seed_bytes[0..32];
let kp = Keypair::from_seed(seed32).context("Failed to derive keypair from mnemonic seed")?;
Ok(kp.pubkey().to_string())
}
}

View File

@@ -0,0 +1,44 @@
// src/tginterface.rs
use teloxide::prelude::*;
use std::env;
use dotenv::dotenv;
use crate::db::Database;
use crate::commands::{handle_command, Command};
mod db;
mod models;
mod commands;
#[tokio::main]
async fn main() {
dotenv().ok();
env_logger::init();
let bot_token = env::var("TELEGRAM_BOT_TOKEN").expect("TELEGRAM_BOT_TOKEN not set");
let mongo_uri = env::var("MONGODB_URI").expect("MONGODB_URI not set");
let mongo_db = env::var("MONGODB_DB").unwrap_or_else(|_| "telegram_wallet_bot".to_string());
let admin_ids: Vec<i64> = env::var("ADMIN_IDS")
.unwrap_or_default()
.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
let db = Database::new(&mongo_uri, &mongo_db)
.await
.expect("failed to initialize database");
let bot = Bot::new(bot_token).auto_send();
log::info!("Starting bot...");
// Use a cloned Database in handler
teloxide::commands_repl(bot.clone(), move |bot: AutoSend<Bot>, msg: Message, cmd: Command| {
let db = db.clone(); // Database should be Cloneable or use Arc
let admin_ids = admin_ids.clone();
async move {
if let Err(e) = handle_command(bot.clone().into_inner(), msg, cmd, db.clone(), admin_ids.clone()).await {
log::error!("Error handling command: {:?}", e);
}
}
}, Command::ty()).await;
}

View File

@@ -0,0 +1,200 @@
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use solana_program::{program_option::COption, pubkey::Pubkey};
use spl_token::instruction::{AuthorityType, TokenInstruction};
// Helper function to convert COption<Pubkey> to Option<Pubkey>
fn convert_coption<T>(coption: COption<T>) -> Option<T> {
match coption {
COption::Some(value) => Some(value),
COption::None => None,
}
}
fn convert_set_authority(
authority_type: AuthorityType,
new_authority: COption<Pubkey>,
) -> SerializableSetAuthority {
SerializableSetAuthority {
authority_type: match authority_type {
AuthorityType::MintTokens => "MintTokens".to_string(),
AuthorityType::FreezeAccount => "FreezeAccount".to_string(),
AuthorityType::AccountOwner => "AccountOwner".to_string(),
AuthorityType::CloseAccount => "CloseAccount".to_string(),
},
new_authority: new_authority.into(), // Convert COption<Pubkey> to Option<Pubkey>
}
}
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct SerializableInitializeMint {
decimals: u8,
#[serde_as(as = "DisplayFromStr")]
mint_authority: Pubkey,
#[serde_as(as = "Option<DisplayFromStr>")]
freeze_authority: Option<Pubkey>,
}
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct SerializableInitializeAccount3 {
#[serde_as(as = "DisplayFromStr")]
owner: Pubkey,
}
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct SerializableSetAuthority {
authority_type: String,
#[serde_as(as = "Option<DisplayFromStr>")]
new_authority: Option<Pubkey>,
}
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct SerializableTransfer {
amount: u64,
}
#[serde_as]
#[derive(Serialize, Deserialize)]
pub enum SerializableTokenInstruction {
InitializeMint(SerializableInitializeMint),
InitializeAccount,
InitializeMultisig {
m: u8,
},
Transfer(SerializableTransfer),
Approve {
amount: u64,
},
Revoke,
SetAuthority(SerializableSetAuthority),
MintTo {
amount: u64,
},
Burn {
amount: u64,
},
CloseAccount,
FreezeAccount,
ThawAccount,
TransferChecked {
amount: u64,
decimals: u8,
},
ApproveChecked {
amount: u64,
decimals: u8,
},
MintToChecked {
amount: u64,
decimals: u8,
},
BurnChecked {
amount: u64,
decimals: u8,
},
InitializeAccount2 {
#[serde_as(as = "DisplayFromStr")]
owner: Pubkey,
},
SyncNative,
InitializeAccount3(SerializableInitializeAccount3),
InitializeMultisig2 {
m: u8,
},
InitializeMint2(SerializableInitializeMint),
GetAccountDataSize,
InitializeImmutableOwner,
AmountToUiAmount {
amount: u64,
},
UiAmountToAmount {
ui_amount: String,
},
}
// Convert TokenInstruction to SerializableTokenInstruction
pub fn convert_to_serializable(ix: TokenInstruction) -> SerializableTokenInstruction {
match ix {
TokenInstruction::InitializeMint {
decimals,
mint_authority,
freeze_authority,
} => {
SerializableTokenInstruction::InitializeMint(SerializableInitializeMint {
decimals,
mint_authority,
freeze_authority: freeze_authority.into(), // Convert COption to Option
})
}
TokenInstruction::InitializeAccount => SerializableTokenInstruction::InitializeAccount,
TokenInstruction::InitializeMultisig { m } => {
SerializableTokenInstruction::InitializeMultisig { m }
}
TokenInstruction::Transfer { amount } => {
SerializableTokenInstruction::Transfer(SerializableTransfer { amount })
}
TokenInstruction::Approve { amount } => SerializableTokenInstruction::Approve { amount },
TokenInstruction::Revoke => SerializableTokenInstruction::Revoke,
TokenInstruction::SetAuthority {
authority_type,
new_authority,
} => SerializableTokenInstruction::SetAuthority(convert_set_authority(
authority_type,
new_authority,
)),
TokenInstruction::MintTo { amount } => SerializableTokenInstruction::MintTo { amount },
TokenInstruction::Burn { amount } => SerializableTokenInstruction::Burn { amount },
TokenInstruction::CloseAccount => SerializableTokenInstruction::CloseAccount,
TokenInstruction::FreezeAccount => SerializableTokenInstruction::FreezeAccount,
TokenInstruction::ThawAccount => SerializableTokenInstruction::ThawAccount,
TokenInstruction::TransferChecked { amount, decimals } => {
SerializableTokenInstruction::TransferChecked { amount, decimals }
}
TokenInstruction::ApproveChecked { amount, decimals } => {
SerializableTokenInstruction::ApproveChecked { amount, decimals }
}
TokenInstruction::MintToChecked { amount, decimals } => {
SerializableTokenInstruction::MintToChecked { amount, decimals }
}
TokenInstruction::BurnChecked { amount, decimals } => {
SerializableTokenInstruction::BurnChecked { amount, decimals }
}
TokenInstruction::InitializeAccount2 { owner } => {
SerializableTokenInstruction::InitializeAccount2 { owner }
}
TokenInstruction::SyncNative => SerializableTokenInstruction::SyncNative,
TokenInstruction::InitializeAccount3 { owner } => {
SerializableTokenInstruction::InitializeAccount3(SerializableInitializeAccount3 {
owner,
})
}
TokenInstruction::InitializeMultisig2 { m } => {
SerializableTokenInstruction::InitializeMultisig2 { m }
}
TokenInstruction::InitializeMint2 {
decimals,
mint_authority,
freeze_authority,
} => SerializableTokenInstruction::InitializeMint2(SerializableInitializeMint {
decimals,
mint_authority,
freeze_authority: freeze_authority.into(),
}),
TokenInstruction::GetAccountDataSize => SerializableTokenInstruction::GetAccountDataSize,
TokenInstruction::InitializeImmutableOwner => {
SerializableTokenInstruction::InitializeImmutableOwner
}
TokenInstruction::AmountToUiAmount { amount } => {
SerializableTokenInstruction::AmountToUiAmount { amount }
}
TokenInstruction::UiAmountToAmount { ui_amount } => {
SerializableTokenInstruction::UiAmountToAmount {
ui_amount: ui_amount.to_string(),
}
}
}
}

View File

@@ -0,0 +1,143 @@
// trade_logger.rs
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
use std::collections::HashMap;
use std::str::FromStr;
use tokio::io::AsyncWriteExt;
use tokio::sync::Mutex;
// Remove std::io::BufReader / std::fs::File imports for async loading:
use std::collections::HashSet;
use std::sync::RwLock;
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, BufReader};
static SKIP_CREATORS: Lazy<RwLock<HashSet<Pubkey>>> = Lazy::new(|| RwLock::new(HashSet::new()));
/// Full record to serialize:
#[derive(Serialize, Deserialize)]
pub struct TradeRecord {
pub mint: String,
pub creator: String,
pub buy_lamports: u64,
pub tokens_received: u64,
pub buy_slot: u64,
pub sell_lamports: u64,
pub tokens_sold: u64,
pub sell_slot: u64,
pub pnl_lamports: i128,
pub timestamp: i64, // unix secs
}
// At startup, load trade_records.jsonl and populate SKIP_CREATORS.
// Assumes each line is a JSON object matching TradeRecord.
pub async fn load_skip_creators_from_file(path: &str) -> anyhow::Result<()> {
// Use tokio::fs::File so we can await.
let file = match File::open(path).await {
Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
// No file yet: nothing to skip
return Ok(());
}
Err(e) => return Err(e.into()),
};
let reader = BufReader::new(file);
let mut lines = reader.lines();
// Acquire write lock synchronously (no `.await`).
let mut skip_set = SKIP_CREATORS.write().unwrap();
while let Some(line) = lines.next_line().await? {
let line = line.trim();
if line.is_empty() {
continue;
}
match serde_json::from_str::<TradeRecord>(line) {
Ok(rec) => {
if rec.pnl_lamports < 0 {
if let Ok(pubkey) = Pubkey::from_str(&rec.creator) {
skip_set.insert(pubkey);
}
}
}
Err(_) => {
log::warn!("Invalid trade record line: {}", line);
}
}
}
Ok(())
}
// When a new trade record is written and is negative, also update SKIP_CREATORS in-memory:
pub async fn record_trade_and_maybe_skip(path: &str, rec: &TradeRecord) -> anyhow::Result<()> {
// append to file asynchronously
let mut file = tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.await?;
let json = serde_json::to_string(rec)?;
file.write_all(json.as_bytes()).await?;
file.write_all(b"\n").await?;
if rec.pnl_lamports < 0 {
if let Ok(pubkey) = Pubkey::from_str(&rec.creator) {
// Acquire write lock synchronously:
SKIP_CREATORS.write().unwrap().insert(pubkey);
}
}
Ok(())
}
/// In-memory partials keyed by mint:
static PARTIALS: Lazy<Mutex<HashMap<String, PartialTrade>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
struct PartialTrade {
creator: String,
buy_lamports: u64,
tokens_received: u64,
buy_slot: u64,
}
/// Call when a buy is confirmed:
pub async fn record_buy(
mint: &str,
creator: &Pubkey,
buy_lamports: u64,
tokens_received: u64,
buy_slot: u64,
) {
let mut map = PARTIALS.lock().await;
map.insert(
mint.to_string(),
PartialTrade {
creator: creator.to_string(),
buy_lamports,
tokens_received,
buy_slot,
},
);
}
/// Call when a sell is confirmed:
pub async fn record_sell(mint: &str, sell_lamports: u64, tokens_sold: u64, sell_slot: u64) {
let mut map = PARTIALS.lock().await;
if let Some(partial) = map.remove(mint) {
let pnl = sell_lamports as i128 - partial.buy_lamports as i128;
let record = TradeRecord {
mint: mint.to_string(),
creator: partial.creator.clone(),
buy_lamports: partial.buy_lamports,
tokens_received: partial.tokens_received,
buy_slot: partial.buy_slot,
sell_lamports,
tokens_sold,
sell_slot,
pnl_lamports: pnl,
timestamp: chrono::Utc::now().timestamp(),
};
// Append to file and update skip-set if needed
if let Err(e) = record_trade_and_maybe_skip("trade_records.jsonl", &record).await {
log::error!("Failed to append trade record: {:?}", e);
}
}
}

View File

@@ -0,0 +1,120 @@
// trading_loop.rs
use crate::common::utils::{
create_nonblocking_rpc_client, create_rpc_client, import_env_var, import_wallet, AppState,
SwapExecutionMode, SwapInput,
};
use crate::core::tx;
use crate::dex::pump_fun::PUMP_PROGRAM;
use crate::engine::swap::pump_swap;
use dotenv::dotenv;
use shared_state::{BuyOrder, SellOrder};
use solana_sdk::{native_token, pubkey::Pubkey};
use std::{
env,
str::FromStr,
sync::{Arc, OnceLock},
};
use tokio::sync::{mpsc::Receiver, Notify, OnceCell};
static TRIGGER_NOTIFY: OnceCell<Arc<Notify>> = OnceCell::const_new();
/// Exposed so `pub use trading_loop::get_notify_handle;` still works.
pub async fn get_notify_handle() -> Arc<Notify> {
TRIGGER_NOTIFY
.get_or_init(|| async { Arc::new(Notify::new()) })
.await
.clone()
}
// Pre-parse these exactly once at program load:
static PUMP_PROGRAM_KEY: OnceLock<Pubkey> = OnceLock::new();
static LAMPORTS_PER_SOL_U64: OnceLock<u64> = OnceLock::new();
pub async fn start_trading_loop(mut mint_rx: Receiver<BuyOrder>, mut sell_rx: Receiver<SellOrder>) {
// load .env if present
dotenv().ok();
// one-time RPC & wallet setup
let rpc = create_rpc_client().unwrap();
let rpc_nonblocking = create_nonblocking_rpc_client().await.unwrap();
let wallet = import_wallet().unwrap();
let app = AppState {
rpc_client: rpc.clone(),
rpc_nonblocking_client: rpc_nonblocking.clone(),
wallet: wallet.clone(),
};
// ─── SPAWN BLOCKHASH REFRESHER ───
// This will keep a fresh recentblockhash in the background so sends never stall
// ─── ONE-TIME TIPENGINE INITIALIZATION ───
// Read your env vars just once
let block_engine = env::var("BLOCK_ENGINE_PROVIDER").unwrap_or_default();
let use_nextblock = block_engine.eq_ignore_ascii_case("nextblock");
let use_priority_tip = env::var("USE_PRIORITY_TIP")
.unwrap_or_default()
.eq_ignore_ascii_case("true");
// This will create/fetch your tipaccounts and pull the first tip value
tx::init_tip_state(use_nextblock, use_priority_tip)
.await
.expect("failed to init tip engine");
// stash env vars once
let slippage_bps = import_env_var("SLIPPAGE").parse::<u16>().unwrap_or(15);
let buy_amount_sol = import_env_var("BUY_AMOUNT_SOL")
.parse::<f64>()
.unwrap_or(0.01);
// initialize our OnceLocks
let lamports_per_sol = *LAMPORTS_PER_SOL_U64.get_or_init(|| native_token::LAMPORTS_PER_SOL);
let buy_amount_lamports = (buy_amount_sol * lamports_per_sol as f64) as u64;
let pump_program_key =
*PUMP_PROGRAM_KEY.get_or_init(|| Pubkey::from_str(PUMP_PROGRAM).unwrap());
loop {
tokio::select! {
// ───────── BUY ORDERS ─────────
Some(BuyOrder { mint, creator, use_jito, urgent }) = mint_rx.recv() => {
let mint_pk = Pubkey::from_str(&mint).unwrap();
// derive creator vault address
let (creator_vault, _) =
Pubkey::find_program_address(&[b"creator-vault", creator.as_ref()], &pump_program_key);
let input = SwapInput {
input_token_mint: spl_token::native_mint::ID,
output_token_mint: mint_pk,
slippage_bps,
amount: buy_amount_lamports,
mode: SwapExecutionMode::ExactIn,
market: None,
creator_vault: Some(creator_vault),
};
let app_clone = app.clone();
tokio::spawn(async move {
let _ = pump_swap(app_clone, input, "buy", use_jito, urgent).await;
});
}
// ───────── SELL ORDERS ────────
Some(SellOrder { mint, amount, use_jito, urgent }) = sell_rx.recv() => {
let mint_pk = Pubkey::from_str(&mint).unwrap();
let input = SwapInput {
input_token_mint: mint_pk,
output_token_mint: spl_token::native_mint::ID,
slippage_bps,
amount,
mode: SwapExecutionMode::ExactOut,
market: None,
creator_vault: None,
};
let app_clone = app.clone();
tokio::spawn(async move {
let _ = pump_swap(app_clone, input, "sell", use_jito, urgent).await;
});
}
}
}
}

View File

@@ -0,0 +1,390 @@
{"mint":"4DxH2nRs2SA4uqqddkSkpAM3mPLbhTK86q3ZahBypump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":153937741,"tokens_received":0,"buy_slot":347311946,"sell_lamports":137943497,"tokens_sold":5000000000000,"sell_slot":347311982,"pnl_lamports":-15994244,"timestamp":1750133620}
{"mint":"F6LCD6eLzLcCivZiGiQddyrvm8xJgAGS31bkTZoLpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":174649889,"tokens_received":0,"buy_slot":347316858,"sell_lamports":137297743,"tokens_sold":0,"sell_slot":347316894,"pnl_lamports":-37352146,"timestamp":1750135583}
{"mint":"3P1UMsoUntc5GDJRvnoS38py3K8wNNJyJFAyEo7apump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144623036,"tokens_received":0,"buy_slot":347316984,"sell_lamports":136639943,"tokens_sold":5000000000000,"sell_slot":347317010,"pnl_lamports":-7983093,"timestamp":1750135633}
{"mint":"5AYswZcdHVdu5BCKJPzbNyBCSzaLfTG8bXzcrQZ5pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168758193,"tokens_received":0,"buy_slot":347317104,"sell_lamports":159867553,"tokens_sold":0,"sell_slot":347317120,"pnl_lamports":-8890640,"timestamp":1750135678}
{"mint":"6jusvqh1dUm7KUCcK56hxeBQYkggAMKpmSEaaY8Ef3ZC","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":151494208,"tokens_received":0,"buy_slot":347471412,"sell_lamports":136785977,"tokens_sold":0,"sell_slot":347471446,"pnl_lamports":-14708231,"timestamp":1750197584}
{"mint":"DsSFNuRPxGX9AHhuRSTdKVf232x2Dk3TTRZNEkLUpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":145098776,"tokens_received":0,"buy_slot":347477111,"sell_lamports":137559240,"tokens_sold":0,"sell_slot":347477261,"pnl_lamports":-7539536,"timestamp":1750199923}
{"mint":"E9AdXtfabeBWR7UdJZw29DiNWutRfFb9Jro2cfq2pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144420990,"tokens_received":0,"buy_slot":347477343,"sell_lamports":136818639,"tokens_sold":0,"sell_slot":347477427,"pnl_lamports":-7602351,"timestamp":1750199993}
{"mint":"EieqGdcJMwRuqVnSbfULwTJzmYhQRq8JT9idQkCzpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":177810409,"tokens_received":0,"buy_slot":347477727,"sell_lamports":138039943,"tokens_sold":0,"sell_slot":347477745,"pnl_lamports":-39770466,"timestamp":1750200119}
{"mint":"9rNGPghfS2THjDaJQQKmiJM3o26ykyatVZn39fL4pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170546532,"tokens_received":0,"buy_slot":347479142,"sell_lamports":158050354,"tokens_sold":0,"sell_slot":347479146,"pnl_lamports":-12496178,"timestamp":1750200681}
{"mint":"9ceRs9ZwsB3mTAwFGdQ5qSGZeQmgLA8WLMniHJEApump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":171638164,"tokens_received":0,"buy_slot":347479151,"sell_lamports":139039943,"tokens_sold":5000000000000,"sell_slot":347479171,"pnl_lamports":-32598221,"timestamp":1750200691}
{"mint":"CDTDTqqJmL1ft8a3gcPuYBvMXEA9zXNWhoFVEDaipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":146002227,"tokens_received":0,"buy_slot":347487368,"sell_lamports":141102292,"tokens_sold":5000000000000,"sell_slot":347487523,"pnl_lamports":-4899935,"timestamp":1750204042}
{"mint":"Dqp6ats2iFAwXHZrBpV2NsXuo5hW41FLLD8NNRdWpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":163592683,"tokens_received":0,"buy_slot":347488212,"sell_lamports":136639951,"tokens_sold":0,"sell_slot":347488244,"pnl_lamports":-26952732,"timestamp":1750204329}
{"mint":"8Hc3xZydnjDWnmicCiDn57kbLSJFM45EdxZPhSZipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":185930736,"tokens_received":0,"buy_slot":347488323,"sell_lamports":177546077,"tokens_sold":0,"sell_slot":347488380,"pnl_lamports":-8384659,"timestamp":1750204384}
{"mint":"DLWq4iKhQintex7j8kghjrFE4H6SPp95aeBRHW9ppump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":148677265,"tokens_received":0,"buy_slot":347488678,"sell_lamports":141744160,"tokens_sold":0,"sell_slot":347488831,"pnl_lamports":-6933105,"timestamp":1750204567}
{"mint":"34wqHmwFKdC671bBmE5weWNeXCWQeHHXBaumgnuBpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347491416,"sell_lamports":0,"tokens_sold":0,"sell_slot":347491421,"pnl_lamports":-5000,"timestamp":1750205606}
{"mint":"5Q4QYSeRtVeqoKKv2i7jABuhiMiYJNSrSzHqKmmrpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":148754165,"tokens_received":0,"buy_slot":347491652,"sell_lamports":141819538,"tokens_sold":5000000000000,"sell_slot":347491808,"pnl_lamports":-6934627,"timestamp":1750205760}
{"mint":"CALrEKfSxko4gTgtpxxhjuHJbA35k6MzudADcUteo6xM","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":180453395,"tokens_received":0,"buy_slot":347491892,"sell_lamports":157616043,"tokens_sold":0,"sell_slot":347491903,"pnl_lamports":-22837352,"timestamp":1750205797}
{"mint":"GoLqWvc1DqQGDNpbjx5XFXRqdapcdStXR9CUnvAXKRak","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":147230609,"tokens_received":0,"buy_slot":347491991,"sell_lamports":140785927,"tokens_sold":5000000000000,"sell_slot":347492016,"pnl_lamports":-6444682,"timestamp":1750205843}
{"mint":"DnC6p9dHZnFwGtPy8FwkSaifswVCDVTBQS5zKoqcpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347492108,"sell_lamports":0,"tokens_sold":0,"sell_slot":347492127,"pnl_lamports":-5000,"timestamp":1750205887}
{"mint":"ERy1mi86Yj5bvwkj6ZcPTENqKtMaMz9JiQMHLwbEPpDU","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":185361594,"tokens_received":0,"buy_slot":347492217,"sell_lamports":138039950,"tokens_sold":5000000000000,"sell_slot":347492349,"pnl_lamports":-47321644,"timestamp":1750205976}
{"mint":"4z8vxGoPS2HMnNz61WpqqCT2Grpb5exVqRDbsZwCpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":156439422,"tokens_received":0,"buy_slot":347493397,"sell_lamports":136039952,"tokens_sold":0,"sell_slot":347493422,"pnl_lamports":-20399470,"timestamp":1750206407}
{"mint":"5d1sEe8zsjkssyQtYau6ALpCgtiAEiptvAMFPdZppump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":148842226,"tokens_received":0,"buy_slot":347493500,"sell_lamports":137945459,"tokens_sold":0,"sell_slot":347493653,"pnl_lamports":-10896767,"timestamp":1750206501}
{"mint":"2xQeszcoYrKsMmnobE8hQYR1AD5EDKBfUcXZFUtCpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":153189012,"tokens_received":0,"buy_slot":347496103,"sell_lamports":134460553,"tokens_sold":5000000000000,"sell_slot":347496508,"pnl_lamports":-18728459,"timestamp":1750207644}
{"mint":"AsFHiuqHUU4mLDE6xUii4UBekk7eXacgR15TXienpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":198747842,"tokens_received":0,"buy_slot":347496774,"sell_lamports":181218546,"tokens_sold":0,"sell_slot":347496777,"pnl_lamports":-17529296,"timestamp":1750207752}
{"mint":"BpaSVkodWrV1ZbxRpGDu5E4VMvXiZjpcykmvYf6ypump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":157954942,"tokens_received":0,"buy_slot":347496856,"sell_lamports":142917329,"tokens_sold":5000000000000,"sell_slot":347496862,"pnl_lamports":-15037613,"timestamp":1750207788}
{"mint":"Fu6PgDj4RkjhyvhG5zVpjQNHPx3A4bUo52CCB8UndmAk","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":186653950,"tokens_received":0,"buy_slot":347496943,"sell_lamports":171048041,"tokens_sold":5000000000000,"sell_slot":347496944,"pnl_lamports":-15605909,"timestamp":1750207821}
{"mint":"5oUFJFHK84hZfMZqPF2BRfWgEh1PfaToM1PzADmepump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":148978986,"tokens_received":0,"buy_slot":347497258,"sell_lamports":134166999,"tokens_sold":0,"sell_slot":347497662,"pnl_lamports":-14811987,"timestamp":1750208108}
{"mint":"H7jqp3g6uTcLydmWQBCnrRnRpzf1H9CkPu4sVURqpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154580474,"tokens_received":0,"buy_slot":347501008,"sell_lamports":135885940,"tokens_sold":0,"sell_slot":347501020,"pnl_lamports":-18694534,"timestamp":1750209465}
{"mint":"EyNzeVDoFPPXfGhDh9LcCrgt2oPjjh9qGcALGzcopump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347502059,"sell_lamports":0,"tokens_sold":0,"sell_slot":347502062,"pnl_lamports":-5000,"timestamp":1750209879}
{"mint":"BuEKpmaJiXgnjZzH8PckLMzNGkFBkZDv1DekgJofpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":165885512,"tokens_received":0,"buy_slot":347502067,"sell_lamports":136684668,"tokens_sold":5000000000000,"sell_slot":347502071,"pnl_lamports":-29200844,"timestamp":1750209883}
{"mint":"AYR3GRRJ5VRwPo591GdmHdFkSyaAfwYHvMZU6cmYpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":161639469,"tokens_received":0,"buy_slot":347502077,"sell_lamports":138479030,"tokens_sold":5000000000000,"sell_slot":347502085,"pnl_lamports":-23160439,"timestamp":1750209888}
{"mint":"DRVqp9uBWfScfjxC2wj3ADbK8K8Ar1WuC6HKA5Aipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":161000817,"tokens_received":0,"buy_slot":347502087,"sell_lamports":134039952,"tokens_sold":0,"sell_slot":347502159,"pnl_lamports":-26960865,"timestamp":1750209919}
{"mint":"H9oC98SM2MnFhHxujKZbayjecf7YM8qiPkd111KJpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":164582588,"tokens_received":0,"buy_slot":347502521,"sell_lamports":136819798,"tokens_sold":0,"sell_slot":347502533,"pnl_lamports":-27762790,"timestamp":1750210066}
{"mint":"5sq6xDoiqnTEXToLpA4ZavJW3UmwuUXeR9GgzeHdpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":185047505,"tokens_received":0,"buy_slot":347502738,"sell_lamports":152106430,"tokens_sold":0,"sell_slot":347502784,"pnl_lamports":-32941075,"timestamp":1750210166}
{"mint":"4ZPs2t1TfaXX5QN6Y6KhoKwbs2hZsUFTTYFJWmTQpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":162337130,"tokens_received":0,"buy_slot":347503132,"sell_lamports":152857570,"tokens_sold":0,"sell_slot":347503148,"pnl_lamports":-9479560,"timestamp":1750210310}
{"mint":"aG8kT6wwLZMrrJ55iQ9HanphmrMnstd597QRyCrpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":177645442,"tokens_received":0,"buy_slot":347503403,"sell_lamports":162997634,"tokens_sold":5000000000000,"sell_slot":347503404,"pnl_lamports":-14647808,"timestamp":1750210414}
{"mint":"E5rzqJzBvFDx4zq6mTTEyV5ou8YQg92dj1CeTW9ipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154851919,"tokens_received":0,"buy_slot":347503408,"sell_lamports":140151244,"tokens_sold":5000000000000,"sell_slot":347503585,"pnl_lamports":-14700675,"timestamp":1750210487}
{"mint":"8Jbe8VgaLEVpmE8DRYAsi8c8Rm7WxSfaqYRwykt1pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":180767390,"tokens_received":0,"buy_slot":347503589,"sell_lamports":241765820,"tokens_sold":5000000000000,"sell_slot":347503597,"pnl_lamports":60998430,"timestamp":1750210492}
{"mint":"3BoomWazAARD3yLrtUfvqmkja23GEonVzs6NoJecpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":164766466,"tokens_received":0,"buy_slot":347504654,"sell_lamports":169873045,"tokens_sold":5000000000000,"sell_slot":347504658,"pnl_lamports":5106579,"timestamp":1750210914}
{"mint":"CHeFZPQZ6X4rL6sTvQZVxk7atywWXgv9uq51Uyivpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":169334341,"tokens_received":0,"buy_slot":347504662,"sell_lamports":162480810,"tokens_sold":0,"sell_slot":347504703,"pnl_lamports":-6853531,"timestamp":1750210932}
{"mint":"HhELATZZia8u2KRRrZYLuQjCweCHGf7LS4pMcNmEpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347504706,"sell_lamports":0,"tokens_sold":0,"sell_slot":347504709,"pnl_lamports":-5000,"timestamp":1750210936}
{"mint":"FvaqGvRv4mCm8yRrLe3mwXceUzcubVsc4qbMWXtTpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":177139433,"tokens_received":0,"buy_slot":347504722,"sell_lamports":301752412,"tokens_sold":5000000000000,"sell_slot":347504728,"pnl_lamports":124612979,"timestamp":1750210943}
{"mint":"GPpZtiJ7LEhTjERnuKLaNC2G5i2hXdBJCsjvGGQ4pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":164021069,"tokens_received":0,"buy_slot":347504737,"sell_lamports":139266269,"tokens_sold":5000000000000,"sell_slot":347504743,"pnl_lamports":-24754800,"timestamp":1750210949}
{"mint":"G8zfcGneV7bX3zfme9AfZeYvYHLe3ayMGgyn8y1Tpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":165029947,"tokens_received":0,"buy_slot":347504760,"sell_lamports":162676058,"tokens_sold":5000000000000,"sell_slot":347504776,"pnl_lamports":-2353889,"timestamp":1750210963}
{"mint":"HAVgh5JDKdfW5S6QKUsraLTbr9YduR5YaU8TLCVcpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":179279906,"tokens_received":0,"buy_slot":347504795,"sell_lamports":170518209,"tokens_sold":5000000000000,"sell_slot":347504796,"pnl_lamports":-8761697,"timestamp":1750210970}
{"mint":"AZjq4U89GDYfoD1cSdMccoQE54476KM298M2aPQoyc4H","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347504808,"sell_lamports":0,"tokens_sold":0,"sell_slot":347504814,"pnl_lamports":-5000,"timestamp":1750210978}
{"mint":"9rmEegGhmcipQ91mSdUHLs9JgNHYqhieHXcjW4Gqpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":175512577,"tokens_received":0,"buy_slot":347504817,"sell_lamports":138039951,"tokens_sold":5000000000000,"sell_slot":347504859,"pnl_lamports":-37472626,"timestamp":1750210996}
{"mint":"79WYGn7ANggpawF9QZtFY9XXkfyJwkkZ5jzSNhPMpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":169977975,"tokens_received":0,"buy_slot":347504873,"sell_lamports":146010644,"tokens_sold":0,"sell_slot":347504892,"pnl_lamports":-23967331,"timestamp":1750211010}
{"mint":"GiXb7UQjBjS412uWhbPwBi7MrGmgdqxxcidvNW8dpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":175206230,"tokens_received":0,"buy_slot":347504896,"sell_lamports":169400726,"tokens_sold":0,"sell_slot":347504903,"pnl_lamports":-5805504,"timestamp":1750211014}
{"mint":"CVkEbMhDHe6JawXvmx8eanHHYcZkekHtd13QBpQKpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":160712566,"tokens_received":0,"buy_slot":347504908,"sell_lamports":138662517,"tokens_sold":0,"sell_slot":347505164,"pnl_lamports":-22050049,"timestamp":1750211119}
{"mint":"Aq1wu7GJ4yKGe3SSNvQQnSrU2v1aCs3NSpsRHvocpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":162002474,"tokens_received":0,"buy_slot":347505175,"sell_lamports":147392215,"tokens_sold":5000000000000,"sell_slot":347505196,"pnl_lamports":-14610259,"timestamp":1750211132}
{"mint":"92TWc7kEjprezUTZ2VViz6Aw37fRxJsfLhjRDGVZpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170819668,"tokens_received":0,"buy_slot":347505209,"sell_lamports":159948483,"tokens_sold":0,"sell_slot":347505295,"pnl_lamports":-10871185,"timestamp":1750211172}
{"mint":"8jHMSkXEQiSchfURmxRBBuk6bjXhgSCkfk5nTjRjpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":163799593,"tokens_received":0,"buy_slot":347505300,"sell_lamports":177434405,"tokens_sold":0,"sell_slot":347505321,"pnl_lamports":13634812,"timestamp":1750211181}
{"mint":"1Tp3k5DRKBdQ3B5RYm7CvKLGwZNSFUVUEFBGiC1YFKc","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":175654766,"tokens_received":0,"buy_slot":347506777,"sell_lamports":177981016,"tokens_sold":5000000000000,"sell_slot":347506789,"pnl_lamports":2326250,"timestamp":1750211770}
{"mint":"CDUcTUQMNiYhX3E9vjLc94uau6XdojLQPkE9sYtwC3v9","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":147277924,"tokens_received":0,"buy_slot":347506795,"sell_lamports":138039952,"tokens_sold":5000000000000,"sell_slot":347506857,"pnl_lamports":-9237972,"timestamp":1750211796}
{"mint":"2QmaWwjheohrLUusSB1Zz1fmMcgwSPrfcdjn6sDspump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":167349441,"tokens_received":0,"buy_slot":347507551,"sell_lamports":157050364,"tokens_sold":5000000000000,"sell_slot":347507573,"pnl_lamports":-10299077,"timestamp":1750212084}
{"mint":"9VpmheJu714qmwbWabkWaB3nDCzSjKsvXNduYsaTpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347507578,"sell_lamports":0,"tokens_sold":0,"sell_slot":347507583,"pnl_lamports":-5000,"timestamp":1750212087}
{"mint":"3xP6Ju9pWJ8sRLSc8PxNR1L328vUjC6zYLEjtCkppump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":180576243,"tokens_received":0,"buy_slot":347507587,"sell_lamports":142543755,"tokens_sold":5000000000000,"sell_slot":347507591,"pnl_lamports":-38032488,"timestamp":1750212090}
{"mint":"9q5bCnBW6TKNZZeMEW58cLKNVQses6Z7nGMaABCLPXBn","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":178158085,"tokens_received":0,"buy_slot":347507593,"sell_lamports":191063682,"tokens_sold":5000000000000,"sell_slot":347507604,"pnl_lamports":12905597,"timestamp":1750212096}
{"mint":"FWQUcSoU2eGshPGc2hs2MG4sqs2FoT1qXzcoFV2rpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168587994,"tokens_received":0,"buy_slot":347507614,"sell_lamports":183743684,"tokens_sold":0,"sell_slot":347507617,"pnl_lamports":15155690,"timestamp":1750212102}
{"mint":"9ft48QMXug18oo7KghoYcixXFVVVae1Bjhfzgdc8pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":162562531,"tokens_received":0,"buy_slot":347507626,"sell_lamports":149700840,"tokens_sold":5000000000000,"sell_slot":347507641,"pnl_lamports":-12861691,"timestamp":1750212111}
{"mint":"AykmqoiLDHioQxJcgREdThzt6uL2t4kgMjwAhqsvak1N","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347509026,"sell_lamports":0,"tokens_sold":0,"sell_slot":347509036,"pnl_lamports":-5000,"timestamp":1750212674}
{"mint":"FeHNSjVCDiJuDV8ZZdnKTvciz53CWwepUW84XSwjpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":326240564,"tokens_received":0,"buy_slot":347509039,"sell_lamports":453783733,"tokens_sold":10000000000000,"sell_slot":347509043,"pnl_lamports":127543169,"timestamp":1750212676}
{"mint":"9kXVDAQdyiD1shTMgPLXEkQ3fJPVSb2D3ZtnEz4Fsrtz","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":302587330,"tokens_received":0,"buy_slot":347509044,"sell_lamports":278694782,"tokens_sold":10000000000000,"sell_slot":347509061,"pnl_lamports":-23892548,"timestamp":1750212683}
{"mint":"1dpF1Zirs9asqxuXQCFW9Pk7SgbQS1ZkgCdGnWQxFLg","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288086615,"tokens_received":0,"buy_slot":347509081,"sell_lamports":278411315,"tokens_sold":10000000000000,"sell_slot":347509483,"pnl_lamports":-9675300,"timestamp":1750212852}
{"mint":"2fRJApP8puJzynzYihgp2G1CLeHVZrGN4TczDxQ1pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":327159115,"tokens_received":0,"buy_slot":347509487,"sell_lamports":278392945,"tokens_sold":10000000000000,"sell_slot":347509535,"pnl_lamports":-48766170,"timestamp":1750212873}
{"mint":"vkTRLDaFZ8o5NAoE3pD49sCLP8gAjQw6bipp7V9pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":358376754,"tokens_received":0,"buy_slot":347509540,"sell_lamports":351989860,"tokens_sold":10000000000000,"sell_slot":347509541,"pnl_lamports":-6386894,"timestamp":1750212876}
{"mint":"9P9zJWnEVzjfHeiLZVKA7sm4i3xDM7V31RGtgkgpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":311611639,"tokens_received":0,"buy_slot":347509544,"sell_lamports":301452110,"tokens_sold":10000000000000,"sell_slot":347509551,"pnl_lamports":-10159529,"timestamp":1750212880}
{"mint":"7x4KXgQ3h9tULjdQe9jz6dWN2tDZ9uuMzHogASe9pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":337528122,"tokens_received":0,"buy_slot":347509560,"sell_lamports":278392928,"tokens_sold":0,"sell_slot":347509610,"pnl_lamports":-59135194,"timestamp":1750212905}
{"mint":"CU7px7F94bgQP1M4N2pZ1NTf3Wy39eLGGz1bpDaq1aEg","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":391627824,"tokens_received":0,"buy_slot":347509627,"sell_lamports":278392942,"tokens_sold":0,"sell_slot":347509704,"pnl_lamports":-113234882,"timestamp":1750212942}
{"mint":"AputqVucipkN6SKtKSJH1Gt3gd5Xcr5GYCcxgJEVpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":178512891,"tokens_received":0,"buy_slot":347511703,"sell_lamports":138039943,"tokens_sold":5000000000000,"sell_slot":347511745,"pnl_lamports":-40472948,"timestamp":1750213761}
{"mint":"FuSuvNP31ugJ7a3M6j2dxDA686ANEWdLrNtYgCDMpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":191562495,"tokens_received":0,"buy_slot":347511757,"sell_lamports":209372956,"tokens_sold":0,"sell_slot":347511761,"pnl_lamports":17810461,"timestamp":1750213768}
{"mint":"Bx6GthauvET9wPzEX9dhYZFQZREQaZuG2yQNB9jepump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":169450415,"tokens_received":0,"buy_slot":347511780,"sell_lamports":144967615,"tokens_sold":5000000000000,"sell_slot":347511966,"pnl_lamports":-24482800,"timestamp":1750213850}
{"mint":"FYiszCW3gFAG8D1hwU4jKyhdWRwkJaynVnv7KXgVpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168313880,"tokens_received":0,"buy_slot":347512476,"sell_lamports":138039951,"tokens_sold":0,"sell_slot":347512547,"pnl_lamports":-30273929,"timestamp":1750214084}
{"mint":"5Qw1dS2AMFPUcmvgC7xbpaa9LAewHJSNePdSP1Wf2jqJ","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":174767935,"tokens_received":0,"buy_slot":347512555,"sell_lamports":160765646,"tokens_sold":5000000000000,"sell_slot":347512584,"pnl_lamports":-14002289,"timestamp":1750214098}
{"mint":"DeW5KqFmH4mBe4nSgpiQm47c139GfJSsUVX4RumApump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":175458276,"tokens_received":0,"buy_slot":347512593,"sell_lamports":138039951,"tokens_sold":5000000000000,"sell_slot":347512657,"pnl_lamports":-37418325,"timestamp":1750214126}
{"mint":"FoFq5ffiipdCoFEejKGkJHDSHKPUPd2N1wrTjFUDoHLt","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":185361583,"tokens_received":0,"buy_slot":347512941,"sell_lamports":174028315,"tokens_sold":0,"sell_slot":347512964,"pnl_lamports":-11333268,"timestamp":1750214249}
{"mint":"NNT2Q5cpnKSBbhFSyTyQtZekKyE8sk6bCNfApiipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":166320932,"tokens_received":0,"buy_slot":347512976,"sell_lamports":167804891,"tokens_sold":0,"sell_slot":347512986,"pnl_lamports":1483959,"timestamp":1750214258}
{"mint":"8veaQPsWZ2mDwphJQEs2RbZMh8eUiN7FJGriUkFUpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":180360057,"tokens_received":0,"buy_slot":347512998,"sell_lamports":185120939,"tokens_sold":0,"sell_slot":347513010,"pnl_lamports":4760882,"timestamp":1750214267}
{"mint":"GZk7aLb6fwysRZwHFvNRuDzpTbSaazfStEMmarC8pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154829405,"tokens_received":0,"buy_slot":347513857,"sell_lamports":138711380,"tokens_sold":0,"sell_slot":347513894,"pnl_lamports":-16118025,"timestamp":1750214623}
{"mint":"61SBrndyLQweFvYz1RUC6C9rSwLUcdhDrXspQqz5pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168080857,"tokens_received":0,"buy_slot":347513906,"sell_lamports":157050364,"tokens_sold":5000000000000,"sell_slot":347513924,"pnl_lamports":-11030493,"timestamp":1750214636}
{"mint":"Fp833QMb3YRWHrDoyGnndSq8GoPjz1ZMQeNgwJQY4ynL","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":196274018,"tokens_received":0,"buy_slot":347513929,"sell_lamports":138080471,"tokens_sold":0,"sell_slot":347514014,"pnl_lamports":-58193547,"timestamp":1750214671}
{"mint":"134tbGFATvhQwDUYfp7Dpju79KxmTCYZq2cpGypWpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":175425825,"tokens_received":0,"buy_slot":347514019,"sell_lamports":138085938,"tokens_sold":5000000000000,"sell_slot":347514332,"pnl_lamports":-37339887,"timestamp":1750214800}
{"mint":"6ygocSg36t7su8b52NZqgwbbLHcVG5vii8saSJJvpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":190116418,"tokens_received":0,"buy_slot":347515425,"sell_lamports":194304301,"tokens_sold":5000000000000,"sell_slot":347515426,"pnl_lamports":4187883,"timestamp":1750215240}
{"mint":"FjTSrGt8N1PTWC8ywWjKjuChFGKtaJ3euuNo6qRxpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":167309527,"tokens_received":0,"buy_slot":347515428,"sell_lamports":138039951,"tokens_sold":0,"sell_slot":347515479,"pnl_lamports":-29269576,"timestamp":1750215260}
{"mint":"EAayig1Cqk1eNH9RYJZAsEfEdAcMb3K4ZiXDLuD6pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":174767935,"tokens_received":0,"buy_slot":347515485,"sell_lamports":167318194,"tokens_sold":0,"sell_slot":347515513,"pnl_lamports":-7449741,"timestamp":1750215274}
{"mint":"Gi4HkMfywxGwHVJWCDTJsKb1FcKJKPwEQdjozBknpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347515518,"sell_lamports":0,"tokens_sold":0,"sell_slot":347515532,"pnl_lamports":-5000,"timestamp":1750215281}
{"mint":"GYP4aTcDaVkKm7YeDDLmcA2Qni247trgsWt9RiQ8pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":193516428,"tokens_received":0,"buy_slot":347515544,"sell_lamports":224002490,"tokens_sold":0,"sell_slot":347515550,"pnl_lamports":30486062,"timestamp":1750215288}
{"mint":"3gABeNGS5Fj6FEsNwEZtawXCvrrsYHRfABwJEwHqwbDW","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":159118273,"tokens_received":0,"buy_slot":347515563,"sell_lamports":145974801,"tokens_sold":5000000000000,"sell_slot":347515579,"pnl_lamports":-13143472,"timestamp":1750215299}
{"mint":"DCGuj3YVS3dY9BmxMQBn9ahPVn9pp33wDZp53XHcpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168321400,"tokens_received":0,"buy_slot":347515584,"sell_lamports":138039951,"tokens_sold":5000000000000,"sell_slot":347515618,"pnl_lamports":-30281449,"timestamp":1750215315}
{"mint":"GXUHcwp9HDrwoqDHiBPtQeaEpKuL5TUeBiRYGUp8M4Ve","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154536409,"tokens_received":0,"buy_slot":347515624,"sell_lamports":147487282,"tokens_sold":5000000000000,"sell_slot":347515702,"pnl_lamports":-7049127,"timestamp":1750215349}
{"mint":"H3uqskXom9Yo7A5qBLyK55aLxc3nzgsC2tZ4cXC2pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":145838296,"tokens_received":0,"buy_slot":347516120,"sell_lamports":139017358,"tokens_sold":5000000000000,"sell_slot":347516151,"pnl_lamports":-6820938,"timestamp":1750215527}
{"mint":"4z7EMkVbcgwjBjrCmB2W7e1xbdhpTU4EW4M5fwiTf8rn","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347516155,"sell_lamports":0,"tokens_sold":0,"sell_slot":347516166,"pnl_lamports":-5000,"timestamp":1750215533}
{"mint":"43DatYePiw5wAx6S5yXHEwcyqtLKMDhQL9ZpNMmJdiqJ","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":196274018,"tokens_received":0,"buy_slot":347516172,"sell_lamports":138039950,"tokens_sold":0,"sell_slot":347516245,"pnl_lamports":-58234068,"timestamp":1750215566}
{"mint":"2BNFmyfcnSjnYgwiRMMwNvrMEHG4EMFRkPJSP8fwpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":164292683,"tokens_received":0,"buy_slot":347516251,"sell_lamports":157050364,"tokens_sold":0,"sell_slot":347516328,"pnl_lamports":-7242319,"timestamp":1750215599}
{"mint":"Epwg2GGZ4ccNeFCUudqUjtxzzWFcQn3W6vz1pgEiLCAg","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":185361583,"tokens_received":0,"buy_slot":347516337,"sell_lamports":138039950,"tokens_sold":0,"sell_slot":347516388,"pnl_lamports":-47321633,"timestamp":1750215623}
{"mint":"4resdanSkUpY7Eru1jXbUDpn9XqsEkJRCoLUANzGpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":190977591,"tokens_received":0,"buy_slot":347516390,"sell_lamports":191111142,"tokens_sold":0,"sell_slot":347516392,"pnl_lamports":133551,"timestamp":1750215625}
{"mint":"Buwa3feVdgVZG1sSYYeY4yZhPtEdw72YLS7WqtrFpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":192382339,"tokens_received":0,"buy_slot":347516404,"sell_lamports":167931074,"tokens_sold":5000000000000,"sell_slot":347516409,"pnl_lamports":-24451265,"timestamp":1750215632}
{"mint":"DCR7ZZoFM5KDWjePbVSGXHEK2X7G9JL5y3EkJqVrpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":159901727,"tokens_received":0,"buy_slot":347516437,"sell_lamports":138189808,"tokens_sold":0,"sell_slot":347516440,"pnl_lamports":-21711919,"timestamp":1750215644}
{"mint":"E5Y7atNeq6s4FX2R9Nw2XktMoVPtiFss5HNycQMKpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":160596506,"tokens_received":0,"buy_slot":347516458,"sell_lamports":147392214,"tokens_sold":5000000000000,"sell_slot":347516466,"pnl_lamports":-13204292,"timestamp":1750215655}
{"mint":"CKvzjdVm5jfBymN3LHUZsNQi9fhExaFznwZYTAd9pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144992082,"tokens_received":0,"buy_slot":347516767,"sell_lamports":138251038,"tokens_sold":5000000000000,"sell_slot":347516793,"pnl_lamports":-6741044,"timestamp":1750215786}
{"mint":"4uyLURYRqZHSgWz8xesi7u258QsK2QeojU6mwcgppump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":180560153,"tokens_received":0,"buy_slot":347516795,"sell_lamports":225265367,"tokens_sold":5000000000000,"sell_slot":347516801,"pnl_lamports":44705214,"timestamp":1750215790}
{"mint":"3rpp1AkGnSiL2Sn8LMhtm128V9JnDZNj7ktb3aqupump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":165817451,"tokens_received":0,"buy_slot":347516802,"sell_lamports":167423757,"tokens_sold":5000000000000,"sell_slot":347516851,"pnl_lamports":1606306,"timestamp":1750215811}
{"mint":"H1BCkUB7UtjESVcHXDwUAnR4mR3aXp7q4V7JmXAipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":176992067,"tokens_received":0,"buy_slot":347517155,"sell_lamports":176243609,"tokens_sold":0,"sell_slot":347517157,"pnl_lamports":-748458,"timestamp":1750215931}
{"mint":"769QBKCMDFEhyA4kxacJ9VPYiZpVk1y76LGVgN9bpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168128579,"tokens_received":0,"buy_slot":347517173,"sell_lamports":148493513,"tokens_sold":0,"sell_slot":347517178,"pnl_lamports":-19635066,"timestamp":1750215940}
{"mint":"BgBiSYYZ3YC7xzkVaCrx4cWpr2iqrsXFt2N66VYMpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154439412,"tokens_received":0,"buy_slot":347517185,"sell_lamports":139228417,"tokens_sold":5000000000000,"sell_slot":347517190,"pnl_lamports":-15210995,"timestamp":1750215944}
{"mint":"24rpR3wxbd6PHNiXVzYk82Z1KVkyybctmRA98DXjpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154829492,"tokens_received":0,"buy_slot":347517203,"sell_lamports":138217201,"tokens_sold":0,"sell_slot":347517210,"pnl_lamports":-16612291,"timestamp":1750215953}
{"mint":"8qJ2z1Hi4maiTHyP9a6dzsTNPwvnJ8pvvoshpUk1Sgny","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":174552930,"tokens_received":0,"buy_slot":347517218,"sell_lamports":187522131,"tokens_sold":5000000000000,"sell_slot":347517220,"pnl_lamports":12969201,"timestamp":1750215957}
{"mint":"DcDnAQqjLR8g5sj3tDTFtjxFXzsaYVYyNmfpZueFpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":176861169,"tokens_received":0,"buy_slot":347517222,"sell_lamports":155874037,"tokens_sold":5000000000000,"sell_slot":347517225,"pnl_lamports":-20987132,"timestamp":1750215960}
{"mint":"CN6b4XxEaHCyUb2Ey2gpV1DY7AHzWrUCtuopMZkm3mGK","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":174024140,"tokens_received":0,"buy_slot":347517227,"sell_lamports":160251850,"tokens_sold":5000000000000,"sell_slot":347517237,"pnl_lamports":-13772290,"timestamp":1750215965}
{"mint":"DFQfq3YxQAfFg5BUcLKKJ2TnjHMNGuYCdgsYKtC2pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":151914813,"tokens_received":0,"buy_slot":347517240,"sell_lamports":142488375,"tokens_sold":0,"sell_slot":347517318,"pnl_lamports":-9426438,"timestamp":1750215997}
{"mint":"GaAAvMGR8TNECm9LN7BhHJGHsMSZE9H2bFR5eeFvpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":166320932,"tokens_received":0,"buy_slot":347517333,"sell_lamports":159232051,"tokens_sold":0,"sell_slot":347517344,"pnl_lamports":-7088881,"timestamp":1750216007}
{"mint":"Dh3uEWobcyERQJZGFdavTdNPxgKjCWrBj91YWUv6d7xM","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170619703,"tokens_received":0,"buy_slot":347517889,"sell_lamports":163252094,"tokens_sold":0,"sell_slot":347517891,"pnl_lamports":-7367609,"timestamp":1750216229}
{"mint":"69uHdZNRzPYFJvRifAhfUqnd7UePPfEhfgqzEbehpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":166320932,"tokens_received":0,"buy_slot":347517893,"sell_lamports":437980310,"tokens_sold":5000000000000,"sell_slot":347517899,"pnl_lamports":271659378,"timestamp":1750216232}
{"mint":"6bFKPWTUD6Xep3M3BWdVxGrRM7kLoe5uSmJgBHK9pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":151717366,"tokens_received":0,"buy_slot":347517908,"sell_lamports":142497725,"tokens_sold":0,"sell_slot":347517986,"pnl_lamports":-9219641,"timestamp":1750216267}
{"mint":"8dpmBfMGcsFdNRT3B3k112X8QnpgPGeTjVcEwuF9pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":150175534,"tokens_received":0,"buy_slot":347518199,"sell_lamports":141266384,"tokens_sold":5000000000000,"sell_slot":347518257,"pnl_lamports":-8909150,"timestamp":1750216376}
{"mint":"9aDtA41UDhBUnts6V1pGR4uM2zAnewWG4FxuXGPBpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":177686389,"tokens_received":0,"buy_slot":347518267,"sell_lamports":179128076,"tokens_sold":0,"sell_slot":347518277,"pnl_lamports":1441687,"timestamp":1750216385}
{"mint":"DoBuLF4oGf4c7pYYupuXxWTfu8ZhrKexUUPSF1o2pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":153471252,"tokens_received":0,"buy_slot":347518284,"sell_lamports":138039943,"tokens_sold":5000000000000,"sell_slot":347518329,"pnl_lamports":-15431309,"timestamp":1750216406}
{"mint":"5vHnVZRetzwVoGq7rkXtgxWpH6YANDeAaaRa7bx4pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":184101706,"tokens_received":0,"buy_slot":347518339,"sell_lamports":269163656,"tokens_sold":5000000000000,"sell_slot":347518344,"pnl_lamports":85061950,"timestamp":1750216412}
{"mint":"85ArwijitJ8pUqpQNiP6zrrWgxUYebyRzVDyWoX5pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":189889436,"tokens_received":0,"buy_slot":347518349,"sell_lamports":152248365,"tokens_sold":0,"sell_slot":347518354,"pnl_lamports":-37641071,"timestamp":1750216416}
{"mint":"C1NdJvJmxnunUPUWqPG9AcjRsH68XmkYDPBTfpJjpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170507468,"tokens_received":0,"buy_slot":347518366,"sell_lamports":148344253,"tokens_sold":0,"sell_slot":347518370,"pnl_lamports":-22163215,"timestamp":1750216422}
{"mint":"7RMQre4vQMtQdjWJt5SkRGYHBby4EKxpgEhQrWW3pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":202376021,"tokens_received":0,"buy_slot":347518379,"sell_lamports":193518242,"tokens_sold":5000000000000,"sell_slot":347518384,"pnl_lamports":-8857779,"timestamp":1750216428}
{"mint":"A9Tabone9NJ3sK193DjSJvT7ShGKTxpmXktFW5aYpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":158382562,"tokens_received":0,"buy_slot":347518401,"sell_lamports":147392215,"tokens_sold":0,"sell_slot":347518409,"pnl_lamports":-10990347,"timestamp":1750216438}
{"mint":"5mrnPovjnpypbebb7mvUoVmCEmyzpHG5kufvnaSZpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170609809,"tokens_received":0,"buy_slot":347518442,"sell_lamports":153843546,"tokens_sold":0,"sell_slot":347518452,"pnl_lamports":-16766263,"timestamp":1750216455}
{"mint":"DtrrKkCah7iXtwPUYokDymTCRgyycANwipAFvXEjFpLE","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154536400,"tokens_received":0,"buy_slot":347518932,"sell_lamports":147487282,"tokens_sold":0,"sell_slot":347519010,"pnl_lamports":-7049118,"timestamp":1750216678}
{"mint":"5rjvHdcF2B1ArjmFB9RJiVxP2tCVPN4bSdrZGWrkpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":181826373,"tokens_received":0,"buy_slot":347519017,"sell_lamports":144277064,"tokens_sold":0,"sell_slot":347519022,"pnl_lamports":-37549309,"timestamp":1750216682}
{"mint":"4YobXovMLVeEqVGzs3oPAe3JQYEywKsgG2CQu1XEpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154738234,"tokens_received":0,"buy_slot":347519031,"sell_lamports":138045462,"tokens_sold":5000000000000,"sell_slot":347519040,"pnl_lamports":-16692772,"timestamp":1750216690}
{"mint":"7Q4FJRc8vgAMSJAdgUtA5os4MYEjEBA8mydvTCQbpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347519050,"sell_lamports":0,"tokens_sold":0,"sell_slot":347519063,"pnl_lamports":-5000,"timestamp":1750216699}
{"mint":"7zT8ZNqJtMmSXgAhmCPRXuiTyQEToHYWcicecbiypump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":180873334,"tokens_received":0,"buy_slot":347519352,"sell_lamports":175450475,"tokens_sold":0,"sell_slot":347519353,"pnl_lamports":-5422859,"timestamp":1750216817}
{"mint":"6DrHbBvBPQTyLcwwWZFYiHnAbcqfwR7bx9iUK8eXpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":174767935,"tokens_received":0,"buy_slot":347519355,"sell_lamports":142699951,"tokens_sold":0,"sell_slot":347519360,"pnl_lamports":-32067984,"timestamp":1750216820}
{"mint":"HGMCJkXHyJDEd1MzJxCf6i7hSr6kKo6YCy1tWVsgpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":169365309,"tokens_received":0,"buy_slot":347519373,"sell_lamports":162022550,"tokens_sold":0,"sell_slot":347519375,"pnl_lamports":-7342759,"timestamp":1750216826}
{"mint":"7CyK5ZK3GAg8zedHGr5wCy5ptpVTshYQsm5Cz4rTpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":154407936,"tokens_received":0,"buy_slot":347519380,"sell_lamports":149430944,"tokens_sold":0,"sell_slot":347519389,"pnl_lamports":-4976992,"timestamp":1750216831}
{"mint":"Gt8XznFv5hN2K6YZyyiwF5d5UJowRKqDdzBvpt9upump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":160154094,"tokens_received":0,"buy_slot":347519392,"sell_lamports":164607744,"tokens_sold":0,"sell_slot":347519393,"pnl_lamports":4453650,"timestamp":1750216832}
{"mint":"3jGDUnFJj2tFUts4t9RLK9bLhrYE4ZZ1rRUEnLECpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":152664321,"tokens_received":0,"buy_slot":347519401,"sell_lamports":142299640,"tokens_sold":0,"sell_slot":347519480,"pnl_lamports":-10364681,"timestamp":1750216867}
{"mint":"8r6VZKDbJjTTBf6mcmfY2qAAWFRm8KsQmSr9gcyzpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":185466956,"tokens_received":0,"buy_slot":347519770,"sell_lamports":179967098,"tokens_sold":0,"sell_slot":347519776,"pnl_lamports":-5499858,"timestamp":1750216987}
{"mint":"37KSUe76NCu4pUWUDX6nNpLhmmanx6Rst7MhuivPpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347519792,"sell_lamports":0,"tokens_sold":0,"sell_slot":347519797,"pnl_lamports":-5000,"timestamp":1750216995}
{"mint":"AgpMKK3Q8FBYD4CfCbFZZoUfv9kgwRprATUYcwqepump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168464311,"tokens_received":0,"buy_slot":347519836,"sell_lamports":138039951,"tokens_sold":0,"sell_slot":347519848,"pnl_lamports":-30424360,"timestamp":1750217016}
{"mint":"BHfyTvcM89GvgoqbJ8EVs2WvLtvRtYR6y2fF7Chypump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347519856,"sell_lamports":0,"tokens_sold":0,"sell_slot":347519863,"pnl_lamports":-5000,"timestamp":1750217021}
{"mint":"Ds7Mcxg9x8mPAkFXvGyP2LKHZ2LFR3CQdFnRPxR154Jc","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":182110735,"tokens_received":0,"buy_slot":347519876,"sell_lamports":174516121,"tokens_sold":5000000000000,"sell_slot":347519878,"pnl_lamports":-7594614,"timestamp":1750217027}
{"mint":"8YyvwW6eB2gZSNH7pFX1sQ8TthFukDSeYE3JNXJspump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168716762,"tokens_received":0,"buy_slot":347519885,"sell_lamports":161059462,"tokens_sold":0,"sell_slot":347519886,"pnl_lamports":-7657300,"timestamp":1750217031}
{"mint":"7e8EjeHCbSzvPXuxQwsF8fpWrXYZnJDG7mdSD7mRpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":156404624,"tokens_received":0,"buy_slot":347519891,"sell_lamports":147392215,"tokens_sold":0,"sell_slot":347519909,"pnl_lamports":-9012409,"timestamp":1750217040}
{"mint":"GSbFoKiHuWSuNRRMXW6c8ExGN8XpXwzqVujbgHh5pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":194272644,"tokens_received":0,"buy_slot":347519917,"sell_lamports":186436673,"tokens_sold":5000000000000,"sell_slot":347519918,"pnl_lamports":-7835971,"timestamp":1750217044}
{"mint":"91HjAWvPBhDZADUySKK4JKaeBVJsoCDChT3QhHncpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":150442710,"tokens_received":0,"buy_slot":347519942,"sell_lamports":143667871,"tokens_sold":5000000000000,"sell_slot":347519943,"pnl_lamports":-6774839,"timestamp":1750217054}
{"mint":"4jsQHqshVcfJ5wy3xBREKy5VbkXK4eCyWPT8zXicpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":149598851,"tokens_received":0,"buy_slot":347519956,"sell_lamports":141744160,"tokens_sold":0,"sell_slot":347519958,"pnl_lamports":-7854691,"timestamp":1750217060}
{"mint":"B2MMargytCabncwmDfz7JzrcGXcUhYnNPx1BWdvcpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":165817461,"tokens_received":0,"buy_slot":347519975,"sell_lamports":157050364,"tokens_sold":0,"sell_slot":347519982,"pnl_lamports":-8767097,"timestamp":1750217070}
{"mint":"8VQd57yZoTNESX2MhyAuoK5YYV2Jdfij8QTJW9Vwpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":185466953,"tokens_received":0,"buy_slot":347519987,"sell_lamports":179967095,"tokens_sold":5000000000000,"sell_slot":347519988,"pnl_lamports":-5499858,"timestamp":1750217072}
{"mint":"G9ETocYppB3Ek6ew4Xy32RuhMq63UxtqRyMLsSTmpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":150167351,"tokens_received":0,"buy_slot":347520013,"sell_lamports":143204750,"tokens_sold":0,"sell_slot":347520018,"pnl_lamports":-6962601,"timestamp":1750217084}
{"mint":"4xrTSPjXxYoVE4UU1Bfpds9SvFB2ub9rdxgytmrjpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":157523807,"tokens_received":0,"buy_slot":347520019,"sell_lamports":151468252,"tokens_sold":5000000000000,"sell_slot":347520020,"pnl_lamports":-6055555,"timestamp":1750217085}
{"mint":"ErfaH7fMh1QgDbY3JpqgHt2seygUkATcwDsoX38yr2Mz","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347520036,"sell_lamports":0,"tokens_sold":0,"sell_slot":347520040,"pnl_lamports":-5000,"timestamp":1750217092}
{"mint":"BoA5ib3xAUxyMrdHkZBscY2id4q6P5ifjbW8CsZSpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":165416759,"tokens_received":0,"buy_slot":347520057,"sell_lamports":164583479,"tokens_sold":0,"sell_slot":347520067,"pnl_lamports":-833280,"timestamp":1750217103}
{"mint":"F9nXCqW5u1ZH3jS35U9UcuC3HNumEqEWfx7mrdAgpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":173429492,"tokens_received":0,"buy_slot":347520089,"sell_lamports":166006254,"tokens_sold":5000000000000,"sell_slot":347520090,"pnl_lamports":-7423238,"timestamp":1750217112}
{"mint":"D4umoMz2nTrm9L2XqdbpjrJsctaZyYpvaNpQSMvbpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":172910222,"tokens_received":0,"buy_slot":347520104,"sell_lamports":166001070,"tokens_sold":0,"sell_slot":347520110,"pnl_lamports":-6909152,"timestamp":1750217121}
{"mint":"DLDZ88ZFDVm25RuqqbsSCa2NxVde2yCdC4YfmVY5pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":163410441,"tokens_received":0,"buy_slot":347520130,"sell_lamports":153130600,"tokens_sold":0,"sell_slot":347520153,"pnl_lamports":-10279841,"timestamp":1750217139}
{"mint":"79u2TnaXemucTGGtxrWmte8isX19u6Uebx96tz9npump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":163909844,"tokens_received":0,"buy_slot":347520168,"sell_lamports":156675114,"tokens_sold":0,"sell_slot":347520171,"pnl_lamports":-7234730,"timestamp":1750217147}
{"mint":"2zMCWe2Y79hJutHmbYwy47UUPF7LN8cmQ6LQayXPpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":165773028,"tokens_received":0,"buy_slot":347520186,"sell_lamports":158501404,"tokens_sold":0,"sell_slot":347520187,"pnl_lamports":-7271624,"timestamp":1750217153}
{"mint":"EzvRjRBQoLSfiLJXuAfK2Lwj8ve6QdCg7xx2LQdupump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":163589942,"tokens_received":0,"buy_slot":347520208,"sell_lamports":158655803,"tokens_sold":5000000000000,"sell_slot":347520209,"pnl_lamports":-4934139,"timestamp":1750217163}
{"mint":"E4PzfTZo5NY4Ug9oDTJJ2YZVT8yP7Y8eZx22moqUpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":158382552,"tokens_received":0,"buy_slot":347520226,"sell_lamports":151257273,"tokens_sold":0,"sell_slot":347520227,"pnl_lamports":-7125279,"timestamp":1750217169}
{"mint":"3EaWwNWkWHJFxGH5c82uxE8aAxzmtAapozi7Ch8Xpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":181872778,"tokens_received":0,"buy_slot":347520229,"sell_lamports":175205215,"tokens_sold":0,"sell_slot":347520232,"pnl_lamports":-6667563,"timestamp":1750217171}
{"mint":"5kxqRb3g6s7DYzpMN72LK8D4rW4pByZPE2z8ZW2DKH7n","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":179139900,"tokens_received":0,"buy_slot":347520241,"sell_lamports":171603585,"tokens_sold":0,"sell_slot":347520242,"pnl_lamports":-7536315,"timestamp":1750217176}
{"mint":"BJ5qNX9rt3cYa71u1w6vsebYhgrJMSA69qCmMFLPpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":160281832,"tokens_received":0,"buy_slot":347520722,"sell_lamports":153118943,"tokens_sold":5000000000000,"sell_slot":347520727,"pnl_lamports":-7162889,"timestamp":1750217368}
{"mint":"EpY1rVa2FPAD1rFQvGcdVysdETBNurFy5tBQRD9ipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":164798544,"tokens_received":0,"buy_slot":347520739,"sell_lamports":147088682,"tokens_sold":0,"sell_slot":347520752,"pnl_lamports":-17709862,"timestamp":1750217378}
{"mint":"66cJUuYv69g6i9PyViWHojX7vPXraFuFcKdVyc94pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170144542,"tokens_received":0,"buy_slot":347520762,"sell_lamports":142764913,"tokens_sold":5000000000000,"sell_slot":347520763,"pnl_lamports":-27379629,"timestamp":1750217382}
{"mint":"DoREj57VkS1jrAyMYCdzY5qDSEk6jhSU5Hxm8nkapump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":168341659,"tokens_received":0,"buy_slot":347520768,"sell_lamports":159018702,"tokens_sold":5000000000000,"sell_slot":347520789,"pnl_lamports":-9322957,"timestamp":1750217392}
{"mint":"2RaQQ7k8tHCV7GApk1ZorLA9KrwBc4EmQArXbkHjDHGM","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":196274029,"tokens_received":0,"buy_slot":347520822,"sell_lamports":138039950,"tokens_sold":5000000000000,"sell_slot":347520898,"pnl_lamports":-58234079,"timestamp":1750217436}
{"mint":"7ZZJbeCPkQm6nZ56DtNLGs1c2GFUjPuxcTZc5vJapump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":147697046,"tokens_received":0,"buy_slot":347520909,"sell_lamports":172967400,"tokens_sold":0,"sell_slot":347520962,"pnl_lamports":25270354,"timestamp":1750217461}
{"mint":"C1NKN93pkT3zTtM7JecwnuYrtVEorUptXzTS4Csapump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":178904976,"tokens_received":0,"buy_slot":347521102,"sell_lamports":173808497,"tokens_sold":5000000000000,"sell_slot":347521104,"pnl_lamports":-5096479,"timestamp":1750217517}
{"mint":"2Sk19LDFmd9bnQyg2KDyk6N7tvNU2cBVfjQ2zNnnpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":182813629,"tokens_received":0,"buy_slot":347521106,"sell_lamports":177549144,"tokens_sold":5000000000000,"sell_slot":347521107,"pnl_lamports":-5264485,"timestamp":1750217518}
{"mint":"9k8BFvvtMzRqtovmodjSbi7wMcnGEDdRCsCi9Wt6pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":176452042,"tokens_received":0,"buy_slot":347521109,"sell_lamports":168968941,"tokens_sold":0,"sell_slot":347521110,"pnl_lamports":-7483101,"timestamp":1750217519}
{"mint":"F85S3X1wEhbrAzPCTagcNUngCVNuY7dFtGuuQs7kpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170144542,"tokens_received":0,"buy_slot":347521131,"sell_lamports":162786354,"tokens_sold":5000000000000,"sell_slot":347521133,"pnl_lamports":-7358188,"timestamp":1750217529}
{"mint":"99dYt5XHu7gJ7Q4KsuJ2xn1EqyodbCzhMBVECMBVMTMK","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347521147,"sell_lamports":0,"tokens_sold":0,"sell_slot":347521153,"pnl_lamports":-5000,"timestamp":1750217537}
{"mint":"HaaHpJJXw3vWorMP5yABnJNBXUDHTQoPmiRg8wr2Jw5C","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":188535477,"tokens_received":0,"buy_slot":347521157,"sell_lamports":180813111,"tokens_sold":0,"sell_slot":347521159,"pnl_lamports":-7722366,"timestamp":1750217539}
{"mint":"2TkEpjRjDT9GmxnyEA6GVuXHEd351XvVtMTuB3Rmpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":153171339,"tokens_received":0,"buy_slot":347521163,"sell_lamports":146142931,"tokens_sold":0,"sell_slot":347521184,"pnl_lamports":-7028408,"timestamp":1750217549}
{"mint":"AppAUHfC2USYs8eoDjbg91AbGdMGbJhtaSRj3UvLpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":163410441,"tokens_received":0,"buy_slot":347521187,"sell_lamports":162039233,"tokens_sold":0,"sell_slot":347521200,"pnl_lamports":-1371208,"timestamp":1750217556}
{"mint":"CRLwGR99o9HJyNbjn34gd5oc7RSMjGUYyZ4pzoXMg99E","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347521249,"sell_lamports":0,"tokens_sold":0,"sell_slot":347521262,"pnl_lamports":-5000,"timestamp":1750217582}
{"mint":"3pCkxnvU9vX5vp2o8LYtpwDYJxaq8sKSDxrCDTqKpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":174612538,"tokens_received":0,"buy_slot":347521269,"sell_lamports":167165865,"tokens_sold":5000000000000,"sell_slot":347521270,"pnl_lamports":-7446673,"timestamp":1750217585}
{"mint":"6tqLq2FK5cXbxDTqHBcqeqvAdrQhczGshNkqqgeTpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":173978664,"tokens_received":0,"buy_slot":347521275,"sell_lamports":161945257,"tokens_sold":5000000000000,"sell_slot":347521281,"pnl_lamports":-12033407,"timestamp":1750217589}
{"mint":"85Xm7XbRaP4nWZwhg3gFzKdJRZHgeC66WsNcHuuapump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144916986,"tokens_received":0,"buy_slot":347521297,"sell_lamports":138131808,"tokens_sold":0,"sell_slot":347521302,"pnl_lamports":-6785178,"timestamp":1750217598}
{"mint":"DuvPcmGHwkXcrF5MxwxatU2AjtY8HhM2sKxPVzmGpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347522916,"sell_lamports":0,"tokens_sold":0,"sell_slot":347522921,"pnl_lamports":-5000,"timestamp":1750218248}
{"mint":"5xpTAv5mEYJKk5KgMPi1xuXwnr8qPAjH4DdQtSBupump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":152418032,"tokens_received":0,"buy_slot":347522992,"sell_lamports":142488375,"tokens_sold":0,"sell_slot":347523070,"pnl_lamports":-9929657,"timestamp":1750218308}
{"mint":"CgofhQEowd5bU5j5vjcKTmTrRLSLPtqzjVNKCJrdpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523088,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523093,"pnl_lamports":-5000,"timestamp":1750218317}
{"mint":"DQvmGbuchtC9PevTzpu4Pt6bYxEkhAoXEL2AogtWpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":145085980,"tokens_received":0,"buy_slot":347523095,"sell_lamports":138251351,"tokens_sold":5000000000000,"sell_slot":347523106,"pnl_lamports":-6834629,"timestamp":1750218322}
{"mint":"72fxuwMXixzJgkpGqA8vW3Dp381QRdPER2tfwuwBpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523118,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523136,"pnl_lamports":-5000,"timestamp":1750218335}
{"mint":"R5daazaATfx5vaA2axFHdvpZJ3uKspSteSaRJRRpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523161,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523167,"pnl_lamports":-5000,"timestamp":1750218347}
{"mint":"FnEbgV2fy9WwcTxN85DUujxHYjzQnRpq8V2Kc4STpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":145838304,"tokens_received":0,"buy_slot":347523181,"sell_lamports":139146182,"tokens_sold":5000000000000,"sell_slot":347523241,"pnl_lamports":-6692122,"timestamp":1750218377}
{"mint":"4ryoNE6jq92dpLrDSAoVUea3NC7fYznKsRit7Rtkpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523268,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523273,"pnl_lamports":-5000,"timestamp":1750218390}
{"mint":"5YNJDStETkwmjq4uhmgQ5Qw3ZCrd3XyDYYQPnCq9pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523281,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523288,"pnl_lamports":-5000,"timestamp":1750218396}
{"mint":"8H1UiWLhPEqXBN4bCyVfu8Dbd1gptYo2ULzb32VSpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523302,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523307,"pnl_lamports":-5000,"timestamp":1750218403}
{"mint":"6jrnjH8MgohX9NdtLMFEpW7ERnZjRn6W6CchrWE2pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523310,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523318,"pnl_lamports":-5000,"timestamp":1750218408}
{"mint":"R5jY9aieD42yJr5ebagtF9eUmZSabqWHgF3Pq3ppump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523352,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523364,"pnl_lamports":-5000,"timestamp":1750218426}
{"mint":"2JzgWCs4sBCNfhrBLKg2i6TGLJMak2FzhgLfwA7Mpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523367,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523379,"pnl_lamports":-5000,"timestamp":1750218432}
{"mint":"7wyNUAerada8d2biycudofwPTaz3zQmibn7Y4h4NFLV9","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":147532319,"tokens_received":0,"buy_slot":347523401,"sell_lamports":140628344,"tokens_sold":5000000000000,"sell_slot":347523408,"pnl_lamports":-6903975,"timestamp":1750218444}
{"mint":"7UVFcESedP2bqHH3vogpTSMjvbXoc76vPpu2tTqY4Tok","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144898214,"tokens_received":0,"buy_slot":347523412,"sell_lamports":166418629,"tokens_sold":5000000000000,"sell_slot":347523422,"pnl_lamports":21520415,"timestamp":1750218449}
{"mint":"R6Uzwg7ekTd2gUW1MZ8p8r1GNMEhvsDUG4H3aFDpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523428,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523435,"pnl_lamports":-5000,"timestamp":1750218454}
{"mint":"GdEh9vmcGNXzfqBtuuLqtZTDvCN6bw4SgZJQ4CjZekDh","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523451,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523453,"pnl_lamports":-5000,"timestamp":1750218462}
{"mint":"8NDjtwUV9Hcdi36xjn7PggJfxGhoKESJdeY5hti8pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":155218132,"tokens_received":0,"buy_slot":347523472,"sell_lamports":148361381,"tokens_sold":5000000000000,"sell_slot":347523477,"pnl_lamports":-6856751,"timestamp":1750218472}
{"mint":"H4qiPNnbpBn7DrtrMWdTZFDNZ1crakZioE9LQSSJpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523486,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523507,"pnl_lamports":-5000,"timestamp":1750218484}
{"mint":"5WuTwnqNa1GcocTpXmdwbgW5hrMrUt1ANqysr2pMpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":149963610,"tokens_received":0,"buy_slot":347523522,"sell_lamports":141390129,"tokens_sold":5000000000000,"sell_slot":347523526,"pnl_lamports":-8573481,"timestamp":1750218492}
{"mint":"R6mbY1YUufeS1cpGEA1zrgXmNaURF2EB269vyFWpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523535,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523546,"pnl_lamports":-5000,"timestamp":1750218500}
{"mint":"ewJSLCqhrcZq3V3EZ8JE1SbPFtEpqLxa4o5jSzfpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523556,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523567,"pnl_lamports":-5000,"timestamp":1750218508}
{"mint":"HTdPEk7uqWJayi4k9Uasy5vK6QiQQA7ZMMmS4sLipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523581,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523588,"pnl_lamports":-5000,"timestamp":1750218517}
{"mint":"v87i1Tmpm9eppeKqW1LJccvyU9tCAHi8Y7px1oEpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523611,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523616,"pnl_lamports":-5000,"timestamp":1750218529}
{"mint":"Da1v3xajSxzAszjWGkAPg9G9WA6rCLkr6UA1Y9p1pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":147318183,"tokens_received":0,"buy_slot":347523627,"sell_lamports":141364787,"tokens_sold":5000000000000,"sell_slot":347523629,"pnl_lamports":-5953396,"timestamp":1750218534}
{"mint":"3jeSGwURoNdTaQFqgXuY2Ab2bjhqiDo1dsFND6cTpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523654,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523678,"pnl_lamports":-5000,"timestamp":1750218554}
{"mint":"5iSjy2hiBXQiQWnerf2j841pvUDNte4oNhhCDnZypump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144992082,"tokens_received":0,"buy_slot":347523680,"sell_lamports":138159846,"tokens_sold":5000000000000,"sell_slot":347523683,"pnl_lamports":-6832236,"timestamp":1750218556}
{"mint":"6dnA1DmnGEgBHsN4AhNmhkxzVx2KWxeztGnBefwnpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523686,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523690,"pnl_lamports":-5000,"timestamp":1750218559}
{"mint":"R6nWKdqBatoS6BnzeCBkcoGgwQSfpsXzDTVqrHwpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523696,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523703,"pnl_lamports":-5000,"timestamp":1750218564}
{"mint":"CPxLWQWxojneW2yiu5ypFtdgMZD2638aRmFNSNVqpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523717,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523720,"pnl_lamports":-5000,"timestamp":1750218571}
{"mint":"GMG83dGQapxy8gZjCSZQRRnEnXuQAAvQNfPGWpVwstfZ","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523729,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523775,"pnl_lamports":-5000,"timestamp":1750218592}
{"mint":"9Ybv8kCJnVCDgkiYvTDyRuToMm2xL9DstaoMrq6XNckT","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523779,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523782,"pnl_lamports":-5000,"timestamp":1750218595}
{"mint":"5sAAvfFmp1VPZrkZe4a8JjhAHLgYJMXygweFyyFekXaJ","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523791,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523799,"pnl_lamports":-5000,"timestamp":1750218602}
{"mint":"bnUshhvY3LAthpDgvdNJpTfPRoTkoTxuBqpn1kXHNGk","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347523815,"sell_lamports":0,"tokens_sold":0,"sell_slot":347523823,"pnl_lamports":-5000,"timestamp":1750218611}
{"mint":"GTguetbZkuH5dBHQ2bE7YxZRG8jbWukbcweQKMhtpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":33686594,"tokens_received":0,"buy_slot":347651814,"sell_lamports":22383525,"tokens_sold":0,"sell_slot":347651819,"pnl_lamports":-11303069,"timestamp":1750269868}
{"mint":"DjeTfNx91DzBHXQYJSX1Kmddy9VC2HivFS92QeUDpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":31314865,"tokens_received":0,"buy_slot":347651828,"sell_lamports":26724424,"tokens_sold":0,"sell_slot":347651907,"pnl_lamports":-4590441,"timestamp":1750269904}
{"mint":"Cxr6B2E1KuEDzcs9JaexX1FwFpWu59Cs5H1mijupump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":103977367,"tokens_received":0,"buy_slot":347651912,"sell_lamports":91412904,"tokens_sold":0,"sell_slot":347651925,"pnl_lamports":-12564463,"timestamp":1750269911}
{"mint":"E4fniLy4uk2c6qpWHhcamCAK5TbMbLHcecrbY7MhH5Am","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":41010893,"tokens_received":0,"buy_slot":347651927,"sell_lamports":26871354,"tokens_sold":0,"sell_slot":347651942,"pnl_lamports":-14139539,"timestamp":1750269917}
{"mint":"DTCZd58vQ6UBsQjGCtyZSjwErWCt7oMSvJbS6RQfpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":41394187,"tokens_received":0,"buy_slot":347651954,"sell_lamports":32864512,"tokens_sold":1000000000000,"sell_slot":347651962,"pnl_lamports":-8529675,"timestamp":1750269925}
{"mint":"HVqKyb8qzKjdMT5cm196ZKugJe4QoTJ8qe3yPP9Rpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":35568697,"tokens_received":0,"buy_slot":347651970,"sell_lamports":23875372,"tokens_sold":1000000000000,"sell_slot":347651972,"pnl_lamports":-11693325,"timestamp":1750269929}
{"mint":"CMKt348vhbRbSRoeRnUqGexKMTmQkVLdmjygqs3Vpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":36813604,"tokens_received":0,"buy_slot":347651977,"sell_lamports":25504182,"tokens_sold":0,"sell_slot":347651988,"pnl_lamports":-11309422,"timestamp":1750269936}
{"mint":"8eY3nivHeQpofx4zCnxrLdBZG6M9xGxvV2BK56WKpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":33636182,"tokens_received":0,"buy_slot":347651992,"sell_lamports":19718522,"tokens_sold":0,"sell_slot":347652000,"pnl_lamports":-13917660,"timestamp":1750269941}
{"mint":"CPkAyHABqZYxnj83tuL6AB3C6xkiuSrqDzopaXwppump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":31309207,"tokens_received":0,"buy_slot":347652002,"sell_lamports":19705766,"tokens_sold":1000000000000,"sell_slot":347652009,"pnl_lamports":-11603441,"timestamp":1750269944}
{"mint":"EFfQuW5n8PxTCDSQg7znVFr6nWs4M43j9zHyPU1zpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":31309209,"tokens_received":0,"buy_slot":347652032,"sell_lamports":19714901,"tokens_sold":1000000000000,"sell_slot":347652064,"pnl_lamports":-11594308,"timestamp":1750269965}
{"mint":"2ZNttFyToZn4xzGAWPiecgJMCmGw3YiqwbX7LcDkpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144973597,"tokens_received":0,"buy_slot":347657917,"sell_lamports":135113842,"tokens_sold":0,"sell_slot":347657931,"pnl_lamports":-9859755,"timestamp":1750272320}
{"mint":"FTaje9uPJ3goHcSHmx6qGfrYyzfHcJRKtNpYhqktpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":159277773,"tokens_received":0,"buy_slot":347657947,"sell_lamports":144392215,"tokens_sold":0,"sell_slot":347657976,"pnl_lamports":-14885558,"timestamp":1750272339}
{"mint":"7fc1XyeSgX4bqbFprQsj83kVvSjiJsnrnNFkfUoHpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":170315856,"tokens_received":0,"buy_slot":347657995,"sell_lamports":159954265,"tokens_sold":5000000000000,"sell_slot":347658004,"pnl_lamports":-10361591,"timestamp":1750272350}
{"mint":"FCfPzzrX2RcYKBjEGQpXUxrhseLibo1nDzTfKZp3pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":186326929,"tokens_received":0,"buy_slot":347658016,"sell_lamports":222443290,"tokens_sold":0,"sell_slot":347658019,"pnl_lamports":36116361,"timestamp":1750272356}
{"mint":"4PGQ1HQH5LtGD927w4sizLWBkD8GMu8iLx4pHecppump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347658027,"sell_lamports":0,"tokens_sold":0,"sell_slot":347658036,"pnl_lamports":-5000,"timestamp":1750272363}
{"mint":"B6YJ7vKJBsWts9Ecd8mPEf9q5fPvCyEpMyJAdFzFpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":144898232,"tokens_received":0,"buy_slot":347658047,"sell_lamports":135067837,"tokens_sold":5000000000000,"sell_slot":347658049,"pnl_lamports":-9830395,"timestamp":1750272368}
{"mint":"9P8ct4q8ZARFtaA2JRYXS2BEDEXkoQUpWtwbz3Hvpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":156502233,"tokens_received":0,"buy_slot":347658091,"sell_lamports":135175315,"tokens_sold":5000000000000,"sell_slot":347658104,"pnl_lamports":-21326918,"timestamp":1750272389}
{"mint":"7Cj2BiXpNKVneyQcYDo1a2M1tpXyGkcuiwU3wc11n2z5","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":378638037,"tokens_received":0,"buy_slot":347660845,"sell_lamports":366151276,"tokens_sold":10000000000000,"sell_slot":347660847,"pnl_lamports":-12486761,"timestamp":1750273487}
{"mint":"39Dyg4x5Qns6SWVEuVYBnfpMwTbE74osrV9ekuUQpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":291249420,"tokens_received":0,"buy_slot":347660879,"sell_lamports":280493105,"tokens_sold":10000000000000,"sell_slot":347660909,"pnl_lamports":-10756315,"timestamp":1750273512}
{"mint":"AaSFnZ9c1HJb6sUncmoWYyvTu2GGHMcsVKVApAR3pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":307306228,"tokens_received":0,"buy_slot":347660911,"sell_lamports":277392946,"tokens_sold":0,"sell_slot":347660941,"pnl_lamports":-29913282,"timestamp":1750273524}
{"mint":"GjFD4BD2ZXfDj2uRiWKL2bk5Vwjw6FbQKLdRQHiCpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":319624088,"tokens_received":0,"buy_slot":347660945,"sell_lamports":319423640,"tokens_sold":0,"sell_slot":347660958,"pnl_lamports":-200448,"timestamp":1750273531}
{"mint":"29LDXaLQyMfxVMaYFN49Pkq2DN3128W5Xs1cod5gpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":321597674,"tokens_received":0,"buy_slot":347660966,"sell_lamports":310240424,"tokens_sold":0,"sell_slot":347660967,"pnl_lamports":-11357250,"timestamp":1750273534}
{"mint":"9mYZUZ6q2YYSZyb89mnqqktLBpBhNDpaNcBquSaXpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":407502529,"tokens_received":0,"buy_slot":347660969,"sell_lamports":277393061,"tokens_sold":10000000000000,"sell_slot":347660974,"pnl_lamports":-130109468,"timestamp":1750273537}
{"mint":"H7p6mqKyVjYeEQTgAsHRExy3pz3pC6eqKwaJS6xxpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":355242021,"tokens_received":0,"buy_slot":347660993,"sell_lamports":315691735,"tokens_sold":0,"sell_slot":347660997,"pnl_lamports":-39550286,"timestamp":1750273547}
{"mint":"6SP2Ft7RDu1xKtH3fpHsm94Z84SL1NN57BX4Bb4Npump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":351861219,"tokens_received":0,"buy_slot":347664296,"sell_lamports":332772812,"tokens_sold":10000000000000,"sell_slot":347664313,"pnl_lamports":-19088407,"timestamp":1750274867}
{"mint":"DLkQ8GVG7EmSjNhNDygW9sVzrcDwUXdXrabWzpU2pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":350764564,"tokens_received":0,"buy_slot":347664324,"sell_lamports":457910398,"tokens_sold":0,"sell_slot":347664327,"pnl_lamports":107145834,"timestamp":1750274873}
{"mint":"ZpoARLgzJmVTjnU1N62Nfiunyd2omcumccXjhbisL4Y","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347664339,"sell_lamports":0,"tokens_sold":0,"sell_slot":347664350,"pnl_lamports":-5000,"timestamp":1750274883}
{"mint":"7D38JZZ6C3azhFggWtxeNnGL8eoyw7tYPnePcBe1pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":351446695,"tokens_received":0,"buy_slot":347664355,"sell_lamports":276614126,"tokens_sold":0,"sell_slot":347664361,"pnl_lamports":-74832569,"timestamp":1750274888}
{"mint":"G4cccQhUouzAWCGpPWmkqXvJUbq8BBkC9HxM6xHspump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":369435393,"tokens_received":0,"buy_slot":347664377,"sell_lamports":354130840,"tokens_sold":10000000000000,"sell_slot":347664378,"pnl_lamports":-15304553,"timestamp":1750274894}
{"mint":"8QpZwfj98wi6v6sWD9c4TRjxTdH8MWCXeauhxSf6PF5w","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":472864273,"tokens_received":0,"buy_slot":347682852,"sell_lamports":506578913,"tokens_sold":0,"sell_slot":347682856,"pnl_lamports":33714640,"timestamp":1750282288}
{"mint":"BRNWNzumrc9x6rZBdwXoTgDgh8s1hehpcRGSMsUrpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":332048230,"tokens_received":0,"buy_slot":347682893,"sell_lamports":287515097,"tokens_sold":0,"sell_slot":347682900,"pnl_lamports":-44533133,"timestamp":1750282306}
{"mint":"3WsRWtCvYjf4hkQPesLSmPWU31u36MmF1TfWChUNvYAY","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347683876,"sell_lamports":0,"tokens_sold":0,"sell_slot":347683882,"pnl_lamports":-5000,"timestamp":1750282706}
{"mint":"GnTE6mHszVGDkz9QXcqKQvPNW66W7gv8fEwRbF3zBP6D","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347683903,"sell_lamports":0,"tokens_sold":0,"sell_slot":347683984,"pnl_lamports":-5000,"timestamp":1750282747}
{"mint":"jydouknW2GaN2rXZidBYfV1ozQkwUnEAXNSChctdsnN","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":479870273,"tokens_received":0,"buy_slot":347683996,"sell_lamports":481255002,"tokens_sold":0,"sell_slot":347684002,"pnl_lamports":1384729,"timestamp":1750282756}
{"mint":"H4F2vsKRzHJ8qiUT5Lp38ggE9PgB3dbx4oFMEAAh8W7j","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":5000,"tokens_received":0,"buy_slot":347684120,"sell_lamports":0,"tokens_sold":0,"sell_slot":347684123,"pnl_lamports":-5000,"timestamp":1750282803}
{"mint":"Aqof3jsSFkk19FeRtCR7HzHB9CUSn83sg7TRjCuSS3BM","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":403098867,"tokens_received":0,"buy_slot":347684174,"sell_lamports":388850105,"tokens_sold":0,"sell_slot":347684184,"pnl_lamports":-14248762,"timestamp":1750282828}
{"mint":"JALaa12J3tBoXSTSr3iwQAyugnpAzaLJGpHniZwVpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":295924374,"tokens_received":0,"buy_slot":347684224,"sell_lamports":285373372,"tokens_sold":10000000000000,"sell_slot":347684227,"pnl_lamports":-10551002,"timestamp":1750282846}
{"mint":"4tKgCaKthWAaHZoru2hazfgvtronp8RjKBTBmrK5pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":327811783,"tokens_received":0,"buy_slot":347684231,"sell_lamports":412300142,"tokens_sold":0,"sell_slot":347684236,"pnl_lamports":84488359,"timestamp":1750282849}
{"mint":"Cd64Z7G4mDs79JqMvHGq1AvUWNNqopA1CFkwnHeYpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":313154743,"tokens_received":0,"buy_slot":347684247,"sell_lamports":302994274,"tokens_sold":0,"sell_slot":347684249,"pnl_lamports":-10160469,"timestamp":1750282855}
{"mint":"5kW1YP7emJebpeFkZCJSWkM7DqkmJubecEhfBPKqpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":317753321,"tokens_received":0,"buy_slot":347684276,"sell_lamports":284052007,"tokens_sold":0,"sell_slot":347684285,"pnl_lamports":-33701314,"timestamp":1750282869}
{"mint":"8PiREhyPeRErKPqsnfQkiHHborRJpb8iiWknDxSCpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288464426,"tokens_received":0,"buy_slot":347685760,"sell_lamports":278763277,"tokens_sold":0,"sell_slot":347685838,"pnl_lamports":-9701149,"timestamp":1750283491}
{"mint":"HnjY5HJk9C66vx4j7VEi31yMBraVTg4m5iW2fQGWpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":305700847,"tokens_received":0,"buy_slot":347685924,"sell_lamports":274745221,"tokens_sold":0,"sell_slot":347685937,"pnl_lamports":-30955626,"timestamp":1750283529}
{"mint":"2XQ6wdbry1t6mhNsb6JY8XT4cy4NwYfyPmBmyeKypump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":311001924,"tokens_received":0,"buy_slot":347685951,"sell_lamports":274392928,"tokens_sold":0,"sell_slot":347686011,"pnl_lamports":-36608996,"timestamp":1750283557}
{"mint":"FdJsMFv4P71ty3CrRGEWZFCnDAhY8ELxC65gNDJvpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":344984494,"tokens_received":0,"buy_slot":347686029,"sell_lamports":435820988,"tokens_sold":10000000000000,"sell_slot":347686031,"pnl_lamports":90836494,"timestamp":1750283565}
{"mint":"7KHsbXfhQhQGfbAX5CCpV8La5BvzTyGiLhtVmxeJpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288653993,"tokens_received":0,"buy_slot":347686046,"sell_lamports":278392928,"tokens_sold":0,"sell_slot":347686128,"pnl_lamports":-10261065,"timestamp":1750283604}
{"mint":"CRFwRUrcxwAy4Up4r63NM2MCnT7tyNPVK3yAJj9vpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":305324623,"tokens_received":0,"buy_slot":347686259,"sell_lamports":275320147,"tokens_sold":0,"sell_slot":347686264,"pnl_lamports":-30004476,"timestamp":1750283657}
{"mint":"12y2Q8evxufgCmnBsAXP5L9dwEB3xvNv32DXbhuuuFnS","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":289042032,"tokens_received":0,"buy_slot":347686324,"sell_lamports":278392928,"tokens_sold":10000000000000,"sell_slot":347686405,"pnl_lamports":-10649104,"timestamp":1750283714}
{"mint":"8th48ghUL8tdmNwizWVtgvzhCy9mGEPJPZV21YZVv3ND","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":435128951,"tokens_received":0,"buy_slot":347686412,"sell_lamports":387127712,"tokens_sold":10000000000000,"sell_slot":347686418,"pnl_lamports":-48001239,"timestamp":1750283720}
{"mint":"6yRiSqPnaDPCQSB2SkSXdZELaEvEgYb41xUPkkVZpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":329175211,"tokens_received":0,"buy_slot":347686456,"sell_lamports":312419643,"tokens_sold":0,"sell_slot":347686505,"pnl_lamports":-16755568,"timestamp":1750283756}
{"mint":"4FqiHcXQJCWTE92anDMNq1KyJ5uYjN4dD4tEm1ex6H5Z","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":341956180,"tokens_received":0,"buy_slot":347686531,"sell_lamports":327195789,"tokens_sold":10000000000000,"sell_slot":347686536,"pnl_lamports":-14760391,"timestamp":1750283767}
{"mint":"9qm3muJEU1UUzv2SJKZ9qKorvFtg9jSni3FRVfxrpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":311720649,"tokens_received":0,"buy_slot":347686616,"sell_lamports":297364174,"tokens_sold":0,"sell_slot":347686622,"pnl_lamports":-14356475,"timestamp":1750283802}
{"mint":"9XZupxgDtySniAuTSxHNNW25nF3tvWsQJATg37MMpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":291310898,"tokens_received":0,"buy_slot":347686655,"sell_lamports":275674460,"tokens_sold":0,"sell_slot":347686657,"pnl_lamports":-15636438,"timestamp":1750283816}
{"mint":"VfnRfXhgaLxxgxpeEonko84ZhqT9cxVvd6PcqvZxWqM","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":435128951,"tokens_received":0,"buy_slot":347686659,"sell_lamports":387127712,"tokens_sold":0,"sell_slot":347686663,"pnl_lamports":-48001239,"timestamp":1750283819}
{"mint":"66xb6Q6AE6dFTsqRZBdXXD2FHcdXRwodWgS9bwk1pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":322282591,"tokens_received":0,"buy_slot":347686701,"sell_lamports":307911758,"tokens_sold":0,"sell_slot":347686703,"pnl_lamports":-14370833,"timestamp":1750283834}
{"mint":"8QAEP3JX1j1a11JCoDLcs2q4prDRvNxPhU4SGeicpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288088506,"tokens_received":0,"buy_slot":347686708,"sell_lamports":278394781,"tokens_sold":10000000000000,"sell_slot":347686789,"pnl_lamports":-9693725,"timestamp":1750283868}
{"mint":"Hn8TxYAYywRSMSKSfFCaSztCQG7za7qTpv4heSarpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":292534049,"tokens_received":0,"buy_slot":347686817,"sell_lamports":282752296,"tokens_sold":10000000000000,"sell_slot":347686898,"pnl_lamports":-9781753,"timestamp":1750283913}
{"mint":"7M12yzD1ASQjmBanzhSuR6DuM3ZCFDBuDFFjAA8vpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":321048816,"tokens_received":0,"buy_slot":347686905,"sell_lamports":276620856,"tokens_sold":0,"sell_slot":347686912,"pnl_lamports":-44427960,"timestamp":1750283919}
{"mint":"8f9wP7iHBA5pyGDaRUu5fbZGu4UCZmJEkZtQmegCoBHz","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":435128951,"tokens_received":0,"buy_slot":347686915,"sell_lamports":323648283,"tokens_sold":10000000000000,"sell_slot":347686920,"pnl_lamports":-111480668,"timestamp":1750283922}
{"mint":"ARqTuFKGb62WmE7vJBg7gbCX5U2yk95hpmrfowmUpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":314140763,"tokens_received":0,"buy_slot":347686922,"sell_lamports":299931173,"tokens_sold":0,"sell_slot":347686923,"pnl_lamports":-14209590,"timestamp":1750283923}
{"mint":"D8ckMoJ4ZqKaS62sPxdWrbN4wYp7snyWRX8nuEzkpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":290169595,"tokens_received":0,"buy_slot":347686955,"sell_lamports":276248915,"tokens_sold":10000000000000,"sell_slot":347686973,"pnl_lamports":-13920680,"timestamp":1750283942}
{"mint":"AbdSJ2xPafdUahWRdGdPuz4bKCvax2DVx1W5s4Q2pump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":392170980,"tokens_received":0,"buy_slot":347687017,"sell_lamports":379647746,"tokens_sold":0,"sell_slot":347687018,"pnl_lamports":-12523234,"timestamp":1750283960}
{"mint":"3VhMRcxzUmWpqgoRv71pnAUWzCoWUWTxcGgbqvXRpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288464615,"tokens_received":0,"buy_slot":347687070,"sell_lamports":274763463,"tokens_sold":10000000000000,"sell_slot":347687072,"pnl_lamports":-13701152,"timestamp":1750283982}
{"mint":"9aY4rmAvtFUAGgZ2iHKz3CuxewgYz2NH4UcoY8Tipump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":306733320,"tokens_received":0,"buy_slot":347687087,"sell_lamports":292476124,"tokens_sold":10000000000000,"sell_slot":347687098,"pnl_lamports":-14257196,"timestamp":1750283991}
{"mint":"Cb3Gpk6nqRzLZm2PvKACin8qgZqUXGaksVnNCqEupump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":291120836,"tokens_received":0,"buy_slot":347687106,"sell_lamports":275488788,"tokens_sold":10000000000000,"sell_slot":347687109,"pnl_lamports":-15632048,"timestamp":1750283997}
{"mint":"3KiYcBcBBnMzyhFgBMVAD3xytRP1tjt6ezWNSaWrpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":313967694,"tokens_received":0,"buy_slot":347687150,"sell_lamports":303369747,"tokens_sold":10000000000000,"sell_slot":347687153,"pnl_lamports":-10597947,"timestamp":1750284013}
{"mint":"8juTw5JwFTQCu48MLGWAqvw99VuxjiXqaHLJVoVepump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288407738,"tokens_received":0,"buy_slot":347687176,"sell_lamports":274522535,"tokens_sold":0,"sell_slot":347687191,"pnl_lamports":-13885203,"timestamp":1750284028}
{"mint":"9QDvRhURSoHeXiRBRegc9viGPsrUA5YbUGwBHCMXpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":329122976,"tokens_received":0,"buy_slot":347687204,"sell_lamports":284051989,"tokens_sold":10000000000000,"sell_slot":347687212,"pnl_lamports":-45070987,"timestamp":1750284036}
{"mint":"A2ehpGjMkeCryNN6ygV459bB7DyDgUuv9tGxn8UGpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":365496699,"tokens_received":0,"buy_slot":347687223,"sell_lamports":321844504,"tokens_sold":10000000000000,"sell_slot":347687227,"pnl_lamports":-43652195,"timestamp":1750284042}
{"mint":"HhsynLsfhq322wmRJ7jhEdKK8CLB8g3fEuTaWWT2yR7k","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":435128951,"tokens_received":0,"buy_slot":347687270,"sell_lamports":387127710,"tokens_sold":10000000000000,"sell_slot":347687272,"pnl_lamports":-48001241,"timestamp":1750284061}
{"mint":"ENx4DytUYunhxVDXMoQazMDzuHviKLNpQNjY7pMJpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":305324640,"tokens_received":0,"buy_slot":348530992,"sell_lamports":275320162,"tokens_sold":0,"sell_slot":348530999,"pnl_lamports":-30004478,"timestamp":1750623238}
{"mint":"3ZvCMKFVmunNzMt71M9PfrPjsbLjfTBB8ZH2mRvUpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288086634,"tokens_received":0,"buy_slot":348535865,"sell_lamports":274392966,"tokens_sold":10000000000000,"sell_slot":348535871,"pnl_lamports":-13693668,"timestamp":1750625194}
{"mint":"pVJz467uLWsEjpM8S7dhMCLZztwpFWBZtnAgo88eAVW","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":294237211,"tokens_received":0,"buy_slot":348538810,"sell_lamports":283649685,"tokens_sold":0,"sell_slot":348538819,"pnl_lamports":-10587526,"timestamp":1750626384}
{"mint":"7RV1MNLsPNnS2pw9Lxz1dJ8FtSndQtJmHAFDPxBspump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":312446438,"tokens_received":0,"buy_slot":348539436,"sell_lamports":275320162,"tokens_sold":0,"sell_slot":348539448,"pnl_lamports":-37126276,"timestamp":1750626637}
{"mint":"3o7Hty7d2Wj99FkXYYpYFjJURj9x1sRzgMg4bUNMpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":288266294,"tokens_received":0,"buy_slot":348539473,"sell_lamports":276116188,"tokens_sold":0,"sell_slot":348539545,"pnl_lamports":-12150106,"timestamp":1750626677}
{"mint":"EqDG46R2eRrhu53ywcE3St5qRFxnUPoE8UTY4xrFpump","creator":"6s86v9Scfo6FwhgMJ6gAQtecKYKi4u5y5tHnbSVqGYBM","buy_lamports":317753341,"tokens_received":0,"buy_slot":348740038,"sell_lamports":283734905,"tokens_sold":0,"sell_slot":348740047,"pnl_lamports":-34018436,"timestamp":1750707421}
{"mint":"88qNZrT2W13ytxjnCayT7j1yrM5wo8h4QQjZxC2Dpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":34822021,"tokens_received":0,"buy_slot":348749679,"sell_lamports":24711721,"tokens_sold":1000000000000,"sell_slot":348749684,"pnl_lamports":-10110300,"timestamp":1750711298}
{"mint":"B3t3RnV8Q2XGuT5vKMPso6AZgEaxu91bVAatkyxipump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33499429,"tokens_received":0,"buy_slot":348749733,"sell_lamports":24847074,"tokens_sold":1000000000000,"sell_slot":348749740,"pnl_lamports":-8652355,"timestamp":1750711320}
{"mint":"EtqDQssJeHtzHRRPuKBkDrhJyLTWknXzdTLQMNXDpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":32630843,"tokens_received":0,"buy_slot":348749764,"sell_lamports":23436892,"tokens_sold":1000000000000,"sell_slot":348749771,"pnl_lamports":-9193951,"timestamp":1750711331}
{"mint":"9bAF3JQJayKw1H4km9ZMmACpr61KapdoJYsTLTQeivqy","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":36366188,"tokens_received":0,"buy_slot":348755001,"sell_lamports":29260596,"tokens_sold":0,"sell_slot":348755004,"pnl_lamports":-7105592,"timestamp":1750713435}
{"mint":"FSbSa9Uypd2rvquFCLAo9fpYTKQurAXiPP9VF9b7pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33474832,"tokens_received":0,"buy_slot":348755009,"sell_lamports":26883483,"tokens_sold":0,"sell_slot":348755018,"pnl_lamports":-6591349,"timestamp":1750713440}
{"mint":"88rPZtRTfXtq7J1DkT697zzQg2pda3wYV1cK8aKwpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33204059,"tokens_received":0,"buy_slot":348812776,"sell_lamports":28557558,"tokens_sold":0,"sell_slot":348812779,"pnl_lamports":-4646501,"timestamp":1750736664}
{"mint":"AkCPPeaTa4CXzMDuSPWyZq13oEdUaSWWnN6nLRswpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":56033618,"tokens_received":0,"buy_slot":348813757,"sell_lamports":50817696,"tokens_sold":0,"sell_slot":348813765,"pnl_lamports":-5215922,"timestamp":1750737058}
{"mint":"7qxFW16XW1UWWY1wyGvFpPHAyE8FGk1o1TRJ2jzXpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":32250132,"tokens_received":0,"buy_slot":348814248,"sell_lamports":27078277,"tokens_sold":1000000000000,"sell_slot":348814326,"pnl_lamports":-5171855,"timestamp":1750737285}
{"mint":"i4ksN5rHgktSehPjkhbCcRCthGzbRFBKVbsX6Wxpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":31361989,"tokens_received":0,"buy_slot":348815496,"sell_lamports":26751960,"tokens_sold":1000000000000,"sell_slot":348815503,"pnl_lamports":-4610029,"timestamp":1750737756}
{"mint":"9AeRMCq3K6qkZRARQM9WxVbdamEXSgtdDaKgi16Apump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":34346346,"tokens_received":0,"buy_slot":348815629,"sell_lamports":26718522,"tokens_sold":1000000000000,"sell_slot":348815638,"pnl_lamports":-7627824,"timestamp":1750737810}
{"mint":"GHXRJqNM2cyUQc8VrvLi1Ztz6prwQeGiVYfqGMDxpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33337197,"tokens_received":0,"buy_slot":348815680,"sell_lamports":27996655,"tokens_sold":0,"sell_slot":348815686,"pnl_lamports":-5340542,"timestamp":1750737830}
{"mint":"AuGFGJwd296eMAQYuQfKQq65wWPVCBY4eeTBPQGrpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":35303726,"tokens_received":0,"buy_slot":348815713,"sell_lamports":29991635,"tokens_sold":0,"sell_slot":348815720,"pnl_lamports":-5312091,"timestamp":1750737843}
{"mint":"3J1xNU6kSPqZ1ZMs78RYNtpbGmyenfMmvRyj6339pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33011009,"tokens_received":0,"buy_slot":348816097,"sell_lamports":26851735,"tokens_sold":0,"sell_slot":348816500,"pnl_lamports":-6159274,"timestamp":1750738155}
{"mint":"ESEF31DFwc4ZWABSDB6g3KbssZduRYe6rTgbJvGrV6oA","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":31309207,"tokens_received":0,"buy_slot":348816781,"sell_lamports":26719259,"tokens_sold":1000000000000,"sell_slot":348817184,"pnl_lamports":-4589948,"timestamp":1750738431}
{"mint":"B7GTauiL3TovWhkGJ3VESZEBzdAKJdau1X8Qm5yzpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33300595,"tokens_received":0,"buy_slot":348818123,"sell_lamports":26709449,"tokens_sold":1000000000000,"sell_slot":348818213,"pnl_lamports":-6591146,"timestamp":1750738842}
{"mint":"4giCz8P46ADLqmxTyivtGnCguYqGH4EBd2CbFLGHpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":35150107,"tokens_received":0,"buy_slot":348819072,"sell_lamports":30950293,"tokens_sold":1000000000000,"sell_slot":348819077,"pnl_lamports":-4199814,"timestamp":1750739190}
{"mint":"CoMMuhRaH9iLZxHC9E6RRd3yyE7dx2yniGyixrKMXh1P","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":31309207,"tokens_received":0,"buy_slot":348831244,"sell_lamports":26818359,"tokens_sold":1000000000000,"sell_slot":348832247,"pnl_lamports":-4490848,"timestamp":1750744486}
{"mint":"64MQsQ8HWxLJkrDEHESCv12jnDyjygnvPsJ93RUgCWdq","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":31309207,"tokens_received":0,"buy_slot":348832312,"sell_lamports":26793653,"tokens_sold":1000000000000,"sell_slot":348833362,"pnl_lamports":-4515554,"timestamp":1750744938}
{"mint":"5kMazLt27ieg9STMtYJ34f1fabxNmuEYcJhrXrVh26M7","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":31309207,"tokens_received":0,"buy_slot":348833394,"sell_lamports":26700224,"tokens_sold":1000000000000,"sell_slot":348834397,"pnl_lamports":-4608983,"timestamp":1750745353}
{"mint":"DjMhGUMwo4MPbzw2DGuVvymPTKKfgTCeR6yhSfRypump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33397128,"tokens_received":0,"buy_slot":348834398,"sell_lamports":27067350,"tokens_sold":1000000000000,"sell_slot":348834408,"pnl_lamports":-6329778,"timestamp":1750745358}
{"mint":"7ssjU8pYAr2cEqwvkagzRzM2CH28RiNb5MEq78QZHP5i","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":59626914,"tokens_received":0,"buy_slot":348835300,"sell_lamports":55182365,"tokens_sold":0,"sell_slot":348835322,"pnl_lamports":-4444549,"timestamp":1750745726}
{"mint":"BYU4vT8qWQbb2wWDemv1S33Kb3Mg8oPDvtYfasuupump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":65716073,"tokens_received":0,"buy_slot":348835329,"sell_lamports":57647290,"tokens_sold":0,"sell_slot":348835336,"pnl_lamports":-8068783,"timestamp":1750745732}
{"mint":"95GgR39yenDuxWJAJmyAnSzamW7bfVxNDkMuVEdBQDc5","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":59626914,"tokens_received":0,"buy_slot":348835382,"sell_lamports":54546531,"tokens_sold":0,"sell_slot":348835435,"pnl_lamports":-5080383,"timestamp":1750745772}
{"mint":"38UdutHoZXsrt8CgYFGte2MKjWGfwnNqkfJUMt5RnYfG","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":59627292,"tokens_received":0,"buy_slot":348840114,"sell_lamports":50457557,"tokens_sold":0,"sell_slot":348840128,"pnl_lamports":-9169735,"timestamp":1750747663}
{"mint":"2s7PnQyG2bLjt68jncLx6oZMyxDqb3z2NVx1hygmpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":63930981,"tokens_received":0,"buy_slot":348840171,"sell_lamports":50824219,"tokens_sold":0,"sell_slot":348840180,"pnl_lamports":-13106762,"timestamp":1750747685}
{"mint":"3EM51q1mX12WLFpF96YcZkpRMutDsTysCg8Av3Vfpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":63643571,"tokens_received":0,"buy_slot":348840696,"sell_lamports":50551763,"tokens_sold":2000000000000,"sell_slot":348840703,"pnl_lamports":-13091808,"timestamp":1750747894}
{"mint":"2wnUBZeKbAP1FgkDKbg38zvJaJi1vfADH95Nhtyypump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":61892059,"tokens_received":0,"buy_slot":348840710,"sell_lamports":52677486,"tokens_sold":0,"sell_slot":348840711,"pnl_lamports":-9214573,"timestamp":1750747897}
{"mint":"XnvGxuhRJ5qKykyCsSCLdQ8mNtCQfDpXPgGr7VEpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":70515426,"tokens_received":0,"buy_slot":348840723,"sell_lamports":61207149,"tokens_sold":0,"sell_slot":348840724,"pnl_lamports":-9308277,"timestamp":1750747903}
{"mint":"8tx9X4zhqLtpT8qLfxY65kCfQgJLYfNGUdUFZuGjpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":62293709,"tokens_received":0,"buy_slot":348840733,"sell_lamports":53071178,"tokens_sold":2000000000000,"sell_slot":348840734,"pnl_lamports":-9222531,"timestamp":1750747907}
{"mint":"4tXD7WHgn2ccxmkbCZ3s81CKKXGEDMiEp6qTvvqXpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":154426662,"tokens_received":0,"buy_slot":348841422,"sell_lamports":139611451,"tokens_sold":5000000000000,"sell_slot":348841431,"pnl_lamports":-14815211,"timestamp":1750748189}
{"mint":"Hv66WMPFagcSbW5kNRQ37Vvudf4uTjAv7oVZQKtZpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":144899153,"tokens_received":0,"buy_slot":348841457,"sell_lamports":138040862,"tokens_sold":5000000000000,"sell_slot":348841759,"pnl_lamports":-6858291,"timestamp":1750748324}
{"mint":"DDiUg3cmaEAt2uZTAnw4hciyrC5re5ZPuofwADA5pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":156083841,"tokens_received":0,"buy_slot":348841764,"sell_lamports":139250703,"tokens_sold":5000000000000,"sell_slot":348841773,"pnl_lamports":-16833138,"timestamp":1750748329}
{"mint":"EtHhzzKgeqMJVEqj8kcMctEy9v7hMeD6VMNPGWo9pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":144992007,"tokens_received":0,"buy_slot":348841778,"sell_lamports":139088741,"tokens_sold":5000000000000,"sell_slot":348842081,"pnl_lamports":-5903266,"timestamp":1750748453}
{"mint":"5KPEoMgmhQjs5LxAN2aAt5bubQQcYoHkJrPKQPBnpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":154426682,"tokens_received":0,"buy_slot":348843341,"sell_lamports":146879709,"tokens_sold":0,"sell_slot":348843349,"pnl_lamports":-7546973,"timestamp":1750748959}
{"mint":"4JE5RU41HiWy6A1C4zPW1hCnCdANhsSgWJZGvAbsdDMS","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":144898214,"tokens_received":0,"buy_slot":348843440,"sell_lamports":138039952,"tokens_sold":0,"sell_slot":348843643,"pnl_lamports":-6858262,"timestamp":1750749078}
{"mint":"Dx6JEfVaqey9ZAAAnvrKv42AWVCWpwiJt3NJYydypump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":163277721,"tokens_received":0,"buy_slot":348843644,"sell_lamports":158473874,"tokens_sold":0,"sell_slot":348843652,"pnl_lamports":-4803847,"timestamp":1750749082}
{"mint":"ByvKVN91Jcw3rpeM8bSGX7aVG8ALkCFeqCoKZ8S7pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":5000,"tokens_received":0,"buy_slot":348843670,"sell_lamports":0,"tokens_sold":0,"sell_slot":348843677,"pnl_lamports":-5000,"timestamp":1750749091}
{"mint":"4wW6FvqqhCwpp54xXPyQnqwnCeHLYGUygfpjDCz2pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":144992082,"tokens_received":0,"buy_slot":348843696,"sell_lamports":139705382,"tokens_sold":0,"sell_slot":348843899,"pnl_lamports":-5286700,"timestamp":1750749182}
{"mint":"BywScsj9XgcxUCmizhD2ZsCTWnvm2AYPf3rLh7ggpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":313193009,"tokens_received":0,"buy_slot":348845062,"sell_lamports":296730083,"tokens_sold":0,"sell_slot":348845068,"pnl_lamports":-16462926,"timestamp":1750749649}
{"mint":"FWYeJNcKckKD5gEkofe81BqQsxXyzGqf4o86ACG3pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":313195045,"tokens_received":0,"buy_slot":348845137,"sell_lamports":296731976,"tokens_sold":10000000000000,"sell_slot":348845143,"pnl_lamports":-16463069,"timestamp":1750749680}
{"mint":"Ghf16Uo4S6FmVzqUWejiT23ydWxiK3pQoPfPKvNLcFZN","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":288086634,"tokens_received":0,"buy_slot":348845505,"sell_lamports":278392947,"tokens_sold":10000000000000,"sell_slot":348845658,"pnl_lamports":-9693687,"timestamp":1750749887}
{"mint":"CEiBAsaG1jSYkvYtv65vrgxqLVyPgbtAodxNCRYspump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":314419813,"tokens_received":0,"buy_slot":348845670,"sell_lamports":277892928,"tokens_sold":10000000000000,"sell_slot":348845677,"pnl_lamports":-36526885,"timestamp":1750749895}
{"mint":"2SdcFK2nXiVJWTyG1qxUsQuPZA4BmUex77mxXGRnpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":337630594,"tokens_received":0,"buy_slot":348845684,"sell_lamports":298424223,"tokens_sold":10000000000000,"sell_slot":348845693,"pnl_lamports":-39206371,"timestamp":1750749901}
{"mint":"Hb9a7eSFQdauPaC9uT9SmtTugRGVAutLFGwbJF9upump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":158164387,"tokens_received":0,"buy_slot":348846820,"sell_lamports":147665459,"tokens_sold":0,"sell_slot":348846824,"pnl_lamports":-10498928,"timestamp":1750750359}
{"mint":"G6wD1MiBDwhChjTwNMxQFke71CYK3wSG8ouRMUwZp5SV","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":144898214,"tokens_received":0,"buy_slot":348847162,"sell_lamports":138039952,"tokens_sold":5000000000000,"sell_slot":348847248,"pnl_lamports":-6858262,"timestamp":1750750531}
{"mint":"5PAAjsJta7j5fobWM9YMBs6w1quq1faYxLpRmbKZpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":163277710,"tokens_received":0,"buy_slot":348847272,"sell_lamports":145386402,"tokens_sold":5000000000000,"sell_slot":348847310,"pnl_lamports":-17891308,"timestamp":1750750556}
{"mint":"3U2MfYZxdFRQT9nGb8ZfMbiTp115ddH3FNKbHqbspump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":240557728,"tokens_received":0,"buy_slot":348848043,"sell_lamports":225797371,"tokens_sold":8000000000000,"sell_slot":348848049,"pnl_lamports":-14760357,"timestamp":1750750854}
{"mint":"B87cFXzCEFG8xko4y1vTroFfd2Bx3z6w9o5fzgXvpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":262531414,"tokens_received":0,"buy_slot":348848071,"sell_lamports":243690170,"tokens_sold":8000000000000,"sell_slot":348848073,"pnl_lamports":-18841244,"timestamp":1750750864}
{"mint":"59M9CEptdEKVRHiJhifJEppJAP1tHK7cigQJ1Rp2pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":230649918,"tokens_received":0,"buy_slot":348848113,"sell_lamports":222093654,"tokens_sold":0,"sell_slot":348848165,"pnl_lamports":-8556264,"timestamp":1750750903}
{"mint":"W1m7WqD8gaMU24fPamTCa21vMuVyq1RkxYAi48qpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":32893681,"tokens_received":0,"buy_slot":348850445,"sell_lamports":29311907,"tokens_sold":1000000000000,"sell_slot":348850455,"pnl_lamports":-3581774,"timestamp":1750751827}
{"mint":"7q1NLLamka5W2HMiYuY9WiZEX53gYtcGxDUA6eutpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":35298671,"tokens_received":0,"buy_slot":348850469,"sell_lamports":29310040,"tokens_sold":0,"sell_slot":348850475,"pnl_lamports":-5988631,"timestamp":1750751835}
{"mint":"7afH9uykpURnQ4iZwzvG56UVe12U3xaqgA59WKA9pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":38113536,"tokens_received":0,"buy_slot":348850489,"sell_lamports":37218322,"tokens_sold":0,"sell_slot":348850491,"pnl_lamports":-895214,"timestamp":1750751841}
{"mint":"DYvBqTkRBb4a1Rq6qQynGHvJx8mUXJ57HbnqZPFGpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":32887781,"tokens_received":0,"buy_slot":348850517,"sell_lamports":29677597,"tokens_sold":0,"sell_slot":348850524,"pnl_lamports":-3210184,"timestamp":1750751854}
{"mint":"2SuZKRgnv79boZfAmWaWGNB1eh1u4TeCS6t2A8aypump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":30336949,"tokens_received":0,"buy_slot":348851141,"sell_lamports":27707612,"tokens_sold":0,"sell_slot":348851173,"pnl_lamports":-2629337,"timestamp":1750752115}
{"mint":"6SSroMAYNH2ALPXDjcUabeLhCJ9Pf3GYBBtzSJeDpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":31077283,"tokens_received":0,"buy_slot":348851184,"sell_lamports":27766686,"tokens_sold":0,"sell_slot":348851189,"pnl_lamports":-3310597,"timestamp":1750752123}
{"mint":"G3wAS3VSCbup1WFgZFRqDBdXJhPJgLeEDwLpg2k3pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":36911512,"tokens_received":0,"buy_slot":348851741,"sell_lamports":38879167,"tokens_sold":0,"sell_slot":348851744,"pnl_lamports":1967655,"timestamp":1750752347}
{"mint":"8E1Nga4Ks6vaaZseotTh3gsJKaTGChqXihRLy9M5pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":32998500,"tokens_received":0,"buy_slot":348851790,"sell_lamports":29510227,"tokens_sold":1000000000000,"sell_slot":348851793,"pnl_lamports":-3488273,"timestamp":1750752368}
{"mint":"9hTNAimEz3D9Xv2gwqGEanhE3QzriKVt6NHqhFaEpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":35090224,"tokens_received":0,"buy_slot":348851816,"sell_lamports":31536764,"tokens_sold":1000000000000,"sell_slot":348851818,"pnl_lamports":-3553460,"timestamp":1750752378}
{"mint":"CMRaF9TmsJvk6aRX95vzgZFQtoyRi6EvyXMKmjw9pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":34544665,"tokens_received":0,"buy_slot":348851831,"sell_lamports":30413481,"tokens_sold":0,"sell_slot":348851832,"pnl_lamports":-4131184,"timestamp":1750752383}
{"mint":"nX8Det3HEpa1m2VAXrkeF75CFzkG18vvuEvdPtMpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":33083505,"tokens_received":0,"buy_slot":348851842,"sell_lamports":31023264,"tokens_sold":0,"sell_slot":348851848,"pnl_lamports":-2060241,"timestamp":1750752389}
{"mint":"CGfSih9Ls2f6ngnxMHcPeVerLxfyQDE4F8iowupBpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":58672448,"tokens_received":0,"buy_slot":348852512,"sell_lamports":55844128,"tokens_sold":2000000000000,"sell_slot":348852595,"pnl_lamports":-2828320,"timestamp":1750752687}
{"mint":"EBDgRzqq26677T7xbA4jhyDcRHmzboEg1Y6FYZtzpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":58674304,"tokens_received":0,"buy_slot":348852605,"sell_lamports":56542102,"tokens_sold":2000000000000,"sell_slot":348852688,"pnl_lamports":-2132202,"timestamp":1750752724}
{"mint":"C37mTvcaiL3jgVHDE86KrG1mNBZ8rABQBnrAaTn3pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":334214149,"tokens_received":0,"buy_slot":348852699,"sell_lamports":327276460,"tokens_sold":2000000000000,"sell_slot":348852714,"pnl_lamports":-6937689,"timestamp":1750752734}
{"mint":"56PoMj2gagSkA3dXzomZAH23X5GrkGnexThgzBWWpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":59707519,"tokens_received":0,"buy_slot":348853382,"sell_lamports":56496588,"tokens_sold":0,"sell_slot":348853465,"pnl_lamports":-3210931,"timestamp":1750753038}
{"mint":"3fxc1hJ7JQ4LQjoS1NrCHtMViRe4eU7MhyJh16sZpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":68240263,"tokens_received":0,"buy_slot":348853486,"sell_lamports":64895234,"tokens_sold":0,"sell_slot":348853489,"pnl_lamports":-3345029,"timestamp":1750753047}
{"mint":"55w2WX4VVC6bPYUspCs6ZCDBW2YZ6dTCAfXTQvRsvuPB","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":58640691,"tokens_received":0,"buy_slot":348853530,"sell_lamports":55537060,"tokens_sold":2000000000000,"sell_slot":348853613,"pnl_lamports":-3103631,"timestamp":1750753096}
{"mint":"GytSoBW3hPr1uEYobsyYmdJLowHDnEjgsVfqAGZ8pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":83452730,"tokens_received":0,"buy_slot":348853650,"sell_lamports":79257200,"tokens_sold":2000000000000,"sell_slot":348853655,"pnl_lamports":-4195530,"timestamp":1750753113}
{"mint":"7qwYv16DNYCD5yHuvQtU5ZxbY9vodBfBrf73r8Kepump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":63401503,"tokens_received":0,"buy_slot":348853683,"sell_lamports":62121470,"tokens_sold":2000000000000,"sell_slot":348853687,"pnl_lamports":-1280033,"timestamp":1750753126}
{"mint":"7BRQYQosZBLGzPeNCqRVMbMovEPyyVgv4P8SNpvHpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":60653849,"tokens_received":0,"buy_slot":348853694,"sell_lamports":57138474,"tokens_sold":2000000000000,"sell_slot":348853776,"pnl_lamports":-3515375,"timestamp":1750753164}
{"mint":"Go6g4aZXZ3RsgXFRNANvYp1pPztyLrS2rDTk4LaHpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":64411884,"tokens_received":0,"buy_slot":348853820,"sell_lamports":59904272,"tokens_sold":2000000000000,"sell_slot":348853825,"pnl_lamports":-4507612,"timestamp":1750753184}
{"mint":"EJZJ22gQqf9XgtjZ9N31pJCRtakjyd4YCmcdmFwLpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":87052436,"tokens_received":0,"buy_slot":348855812,"sell_lamports":83361598,"tokens_sold":0,"sell_slot":348855865,"pnl_lamports":-3690838,"timestamp":1750754013}
{"mint":"GBBrsJEjVomNxGYt2Dkrhfi4wJbzwUsP8RTyW9Rzpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":90031336,"tokens_received":0,"buy_slot":348859474,"sell_lamports":84832357,"tokens_sold":3000000000000,"sell_slot":348859556,"pnl_lamports":-5198979,"timestamp":1750755507}
{"mint":"3hmxuDesxQD7ZrVXn28QnhWwF6C91fbPvxBudCZipump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":94947622,"tokens_received":0,"buy_slot":348859569,"sell_lamports":95767332,"tokens_sold":0,"sell_slot":348859604,"pnl_lamports":819710,"timestamp":1750755526}
{"mint":"FaGKu4x2gx7xMLnZSd8M6Aq6eNZKRNcdBUgUrtuLpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":87007606,"tokens_received":0,"buy_slot":348859744,"sell_lamports":83317598,"tokens_sold":0,"sell_slot":348859789,"pnl_lamports":-3690008,"timestamp":1750755600}
{"mint":"HqAhSCmcCVLtGdXoPeomAmQs3Jebk94TgeybRGSUpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":98105407,"tokens_received":0,"buy_slot":348859840,"sell_lamports":88903282,"tokens_sold":0,"sell_slot":348859849,"pnl_lamports":-9202125,"timestamp":1750755625}
{"mint":"WFdd1g4EY54QKixKLgnpU7WhP1Re7PE3soka8EKpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":5000,"tokens_received":0,"buy_slot":348859860,"sell_lamports":0,"tokens_sold":0,"sell_slot":348859868,"pnl_lamports":-5000,"timestamp":1750755632}
{"mint":"BzbwMagMLkU5y7SaywZneiUuznwcwgEmFLGWDRGr78Lv","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":79093510,"tokens_received":0,"buy_slot":348921450,"sell_lamports":79088938,"tokens_sold":2000000000000,"sell_slot":348921453,"pnl_lamports":-4572,"timestamp":1750780447}
{"mint":"25y7YcNghk9TMHYynYZwKYgBUK9x4tuhhaJbnHJ2pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":63601160,"tokens_received":0,"buy_slot":348921487,"sell_lamports":59132111,"tokens_sold":0,"sell_slot":348921494,"pnl_lamports":-4469049,"timestamp":1750780463}
{"mint":"5n4iU2VP4k4rNaqPsWxgVcHz28a8j1CMGnHWbWqLpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":65198490,"tokens_received":0,"buy_slot":348921528,"sell_lamports":59132115,"tokens_sold":2000000000000,"sell_slot":348921535,"pnl_lamports":-6066375,"timestamp":1750780479}
{"mint":"YVrBU2ZxMSi8gdMDTRxcofKnSYmWs9Vm9sbnvHWpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":77811086,"tokens_received":0,"buy_slot":348921549,"sell_lamports":75745702,"tokens_sold":0,"sell_slot":348921552,"pnl_lamports":-2065384,"timestamp":1750780486}
{"mint":"FBDFQdN1ztSVy4thoPibtECeZg3kZ35Nw3FdMCNEe19e","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":65566214,"tokens_received":0,"buy_slot":348921597,"sell_lamports":56409159,"tokens_sold":0,"sell_slot":348921601,"pnl_lamports":-9157055,"timestamp":1750780506}
{"mint":"7KGg12TnBD497VvTUyBEx9WndmTrtokWSTe33FGtpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":66800206,"tokens_received":0,"buy_slot":348921609,"sell_lamports":60570130,"tokens_sold":0,"sell_slot":348921615,"pnl_lamports":-6230076,"timestamp":1750780511}
{"mint":"7to5A8SaV8jM4L86CV2FRd7X7mcoF3fmk2ZmigbQPsZa","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":85237997,"tokens_received":0,"buy_slot":348921634,"sell_lamports":75458701,"tokens_sold":0,"sell_slot":348921639,"pnl_lamports":-9779296,"timestamp":1750780521}
{"mint":"4BrkzdZaNzs4rN5VoeqQjN9496c1U7sKPgQmzizTpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":69490872,"tokens_received":0,"buy_slot":348921648,"sell_lamports":83127494,"tokens_sold":2000000000000,"sell_slot":348921649,"pnl_lamports":13636622,"timestamp":1750780525}
{"mint":"HBFKY3oeXsTBj2hzXZgPiQze2Q8PgtKzEesfzPFhAe7E","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":58674678,"tokens_received":0,"buy_slot":348921685,"sell_lamports":55444203,"tokens_sold":2000000000000,"sell_slot":348921705,"pnl_lamports":-3230475,"timestamp":1750780549}
{"mint":"GzRmd7nD8dbggpCxFiMwEmE4fwJE5KwgzXa98mXLpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":63647684,"tokens_received":0,"buy_slot":348921772,"sell_lamports":55833614,"tokens_sold":2000000000000,"sell_slot":348921780,"pnl_lamports":-7814070,"timestamp":1750780579}
{"mint":"81enfW3nvwMuUZPP3fB3m7X4FrTB3jF3bXSMKnEKpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":61397067,"tokens_received":0,"buy_slot":348921810,"sell_lamports":61590721,"tokens_sold":2000000000000,"sell_slot":348921812,"pnl_lamports":193654,"timestamp":1750780592}
{"mint":"YWHvhAWSMddbnWMtZ1yV1nhDNVAtA4SPJwJnc7Cpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":76060939,"tokens_received":0,"buy_slot":348921836,"sell_lamports":84497393,"tokens_sold":2000000000000,"sell_slot":348921839,"pnl_lamports":8436454,"timestamp":1750780603}
{"mint":"7YcGpgdcGe12mPgQSGqvbBUjTBQrnQ7ncZwhJD6spump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":65789598,"tokens_received":0,"buy_slot":348921884,"sell_lamports":78228983,"tokens_sold":2000000000000,"sell_slot":348921886,"pnl_lamports":12439385,"timestamp":1750780621}
{"mint":"F9p4NK7kQyik7PTyESZP7Qq9LB2Mi7e4YSvvApAqpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":62940981,"tokens_received":0,"buy_slot":348921923,"sell_lamports":55774219,"tokens_sold":2000000000000,"sell_slot":348921930,"pnl_lamports":-7166762,"timestamp":1750780638}
{"mint":"H9YKYRF3QSaN6gLiSHXLgN9jkLL8YNy3N6LfiQ7Zpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":66451413,"tokens_received":0,"buy_slot":348921948,"sell_lamports":58970843,"tokens_sold":2000000000000,"sell_slot":348921956,"pnl_lamports":-7480570,"timestamp":1750780649}
{"mint":"DHAkZieMnbcNBBoRaPriKRhyJL96r4SSnazbaQJpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":62835541,"tokens_received":0,"buy_slot":348921988,"sell_lamports":58377427,"tokens_sold":0,"sell_slot":348921996,"pnl_lamports":-4458114,"timestamp":1750780666}
{"mint":"2iMHfPLU1mVKQ1LA1wxX141ppXRebbFJaPRiq4QGpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":77534598,"tokens_received":0,"buy_slot":348922008,"sell_lamports":74746215,"tokens_sold":0,"sell_slot":348922061,"pnl_lamports":-2788383,"timestamp":1750780690}
{"mint":"5sKaSuEjnKCk78dhCnMHrBw8hibkuvAu4wXBvr18pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":63612975,"tokens_received":0,"buy_slot":348922082,"sell_lamports":59132115,"tokens_sold":2000000000000,"sell_slot":348922094,"pnl_lamports":-4480860,"timestamp":1750780704}
{"mint":"GqBGxcrFURRRzmSDVBAWbvdCaAr7ZiS46u9xF5BTpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":58640654,"tokens_received":0,"buy_slot":348922095,"sell_lamports":55446996,"tokens_sold":0,"sell_slot":348922131,"pnl_lamports":-3193658,"timestamp":1750780719}
{"mint":"Gz9fgqH1zdjrQcGSAKM3p1LhbjwSYZYFbUEgaxdkpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":64099711,"tokens_received":0,"buy_slot":348922139,"sell_lamports":60112225,"tokens_sold":0,"sell_slot":348922141,"pnl_lamports":-3987486,"timestamp":1750780723}
{"mint":"JAULmkXKAYPb7LtzLwKRvv9RuhKfa2BHqLRnFzCUpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":62354825,"tokens_received":0,"buy_slot":348922192,"sell_lamports":57422024,"tokens_sold":2000000000000,"sell_slot":348922201,"pnl_lamports":-4932801,"timestamp":1750780747}
{"mint":"5qWm348XTs6n5fygzumMHmjr8K7CggSgPQfMo3Sepump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":66035518,"tokens_received":0,"buy_slot":348922228,"sell_lamports":68444613,"tokens_sold":2000000000000,"sell_slot":348922230,"pnl_lamports":2409095,"timestamp":1750780759}
{"mint":"9yzz3jASKveAsFy28eCE67DMTtFukMBsqY23RAYzpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":58636918,"tokens_received":0,"buy_slot":348922259,"sell_lamports":57536067,"tokens_sold":2000000000000,"sell_slot":348922260,"pnl_lamports":-1100851,"timestamp":1750780771}
{"mint":"DC8hVnwsNKAdFDfDrhULLTHQewyBYxd7DhQvw3LfkMSx","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":66559643,"tokens_received":0,"buy_slot":348922283,"sell_lamports":58968010,"tokens_sold":0,"sell_slot":348922288,"pnl_lamports":-7591633,"timestamp":1750780782}
{"mint":"HWAy2jF9YxwkkYCakuB4oXUMwiAWoHWNz7L9CKWzpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":69276780,"tokens_received":0,"buy_slot":348922298,"sell_lamports":61562354,"tokens_sold":2000000000000,"sell_slot":348922308,"pnl_lamports":-7714426,"timestamp":1750780791}
{"mint":"5gSAhjhryMaLurtSPTmmXcq83J2A7xkc9EqYCWT2pump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":5000,"tokens_received":0,"buy_slot":348924397,"sell_lamports":0,"tokens_sold":0,"sell_slot":348924482,"pnl_lamports":-5000,"timestamp":1750781674}
{"mint":"Dzc9wxBKcGjaeKJQQnyqnxfYDXa5AZdqSK4wVAaUpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":63641160,"tokens_received":0,"buy_slot":348924497,"sell_lamports":58932115,"tokens_sold":2000000000000,"sell_slot":348924504,"pnl_lamports":-4709045,"timestamp":1750781682}
{"mint":"3hdnm9VmE8XeaFmLJj9LwfTjWXzbgC1QS13t7BFHpump","creator":"ETiN5Tdx2zGfJGuB5XN29sGybPk7i1rZrYzLeSY1BviH","buy_lamports":62820272,"tokens_received":0,"buy_slot":348924517,"sell_lamports":56834800,"tokens_sold":2000000000000,"sell_slot":348924525,"pnl_lamports":-5985472,"timestamp":1750781691}

View File

@@ -0,0 +1,286 @@
# 🚀 Solana Raydium Sniper Bot
A high-performance Solana trading bot that automatically snipes new token launches on Raydium, PumpFun, and PumpSwap using gRPC streaming for real-time transaction monitoring.
## ✨ Features
- **Real-time Monitoring**: Uses gRPC streaming to detect new token launches instantly
- **Multi-Pool Support**: Supports Raydium LaunchLab, PumpFun, PumpSwap, and Raydium CPMM
- **Automated Trading**: Automatically buys and sells tokens based on configurable parameters
- **Risk Management**: Built-in stop-loss, profit-taking, and position monitoring
- **Multiple Swap Methods**: Support for Solana, JITO, Nozomi, and 0slot trading
- **Position Tracking**: Monitors active positions and manages exit strategies
- **Graceful Shutdown**: Safely closes all positions before stopping
## 🏗️ Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ gRPC Stream │───▶│ Transaction │───▶│ Trading Engine │
│ (Triton One) │ │ Parser │ │ (Main Bot) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Pool Detection │ │ Position │
│ (PumpFun, etc.) │ │ Management │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Jupiter API │ │ Profit/Loss │
│ Swap Execution │ │ Monitoring │
└─────────────────┘ └─────────────────┘
```
## 🚀 Quick Start
### 1. Prerequisites
- Node.js 16+
- Solana wallet with SOL balance
- Triton One gRPC access
- RPC endpoint (Helius, QuickNode, etc.)
### 2. Installation
```bash
# Clone the repository
git clone <your-repo-url>
cd solana-sniper-bot
# Install dependencies
npm install
# Copy environment template
cp env.template .env
```
### 3. Configuration
Edit the `.env` file with your configuration:
```bash
# Essential Configuration
PRIVATE_KEY=your_wallet_private_key
RPC_URL=https://your-rpc-endpoint.com
GRPC_ENDPOINT=https://your-grpc-endpoint.com
GRPCTOKEN=your_grpc_token
# Trading Parameters
SNIPERAMOUNT=0.1 # SOL amount per snipe
PROFIT_TARGET=2.0 # 2x profit target
STOP_LOSS=0.5 # 50% stop loss
MAX_HOLD_TIME=300000 # 5 minutes max hold
MIN_LIQUIDITY=10 # Minimum liquidity in SOL
```
### 4. Run the Bot
```bash
# Start the sniper bot
npm start
# Or for development with auto-restart
npm run dev
```
## ⚙️ Configuration Options
### Trading Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `SNIPERAMOUNT` | 0.1 | SOL amount to use for each snipe |
| `PROFIT_TARGET` | 2.0 | Profit target multiplier (2x) |
| `STOP_LOSS` | 0.5 | Stop loss multiplier (50% loss) |
| `MAX_HOLD_TIME` | 300000 | Maximum time to hold position (5 min) |
| `MIN_LIQUIDITY` | 10 | Minimum liquidity required in SOL |
### Swap Methods
| Method | Description | Use Case |
|--------|-------------|----------|
| `solana` | Standard Solana prioritization | General trading |
| `race` | JITO MEV protection | MEV protection |
| `nozomi` | Nozomi RPC with tips | Ultra-fast execution |
| `0slot` | 0-slot transaction | Maximum speed |
### Pool Types Supported
- **Raydium LaunchLab**: New token launches
- **PumpFun**: Pump.fun platform
- **PumpSwap**: PumpSwap platform
- **Raydium CPMM**: Constant Product Market Maker
## 📊 How It Works
### 1. Transaction Monitoring
- Bot connects to Solana gRPC stream via Triton One
- Monitors for `MintTo` instructions indicating new token launches
- Filters transactions by SOL transfer amounts (1-85 SOL)
### 2. Transaction Parsing
- Parses transaction data to identify pool type and parameters
- Extracts liquidity, fees, and trading direction
- Determines if transaction meets sniper criteria
### 3. Trading Execution
- Automatically executes buy orders when criteria are met
- Uses Jupiter API for optimal swap routing
- Implements configurable slippage and prioritization fees
### 4. Position Management
- Tracks active positions with entry/exit criteria
- Monitors for profit targets and stop losses
- Automatically closes positions based on conditions
## 🔧 Advanced Configuration
### Custom Pool Filters
```javascript
// Enable/disable specific pool types
ENABLE_PUMPFUN=true
ENABLE_PUMPSWAP=true
ENABLE_RAYDIUM_LAUNCHLAB=true
ENABLE_RAYDIUM_CPMM=true
```
### Risk Management
```javascript
// Maximum concurrent positions
MAX_POSITIONS=5
// Minimum transaction age
MIN_TX_AGE=1
```
### Notifications
```javascript
// Telegram notifications
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
// Email notifications
SMTP_HOST=smtp.gmail.com
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password
```
## 📈 Trading Strategies
### Conservative Strategy
```bash
SNIPERAMOUNT=0.05
PROFIT_TARGET=1.5
STOP_LOSS=0.7
MAX_HOLD_TIME=600000
MIN_LIQUIDITY=20
```
### Aggressive Strategy
```bash
SNIPERAMOUNT=0.2
PROFIT_TARGET=3.0
STOP_LOSS=0.3
MAX_HOLD_TIME=180000
MIN_LIQUIDITY=5
```
## 🛡️ Safety Features
- **Balance Checks**: Verifies wallet balance before trading
- **Liquidity Validation**: Ensures sufficient pool liquidity
- **Position Limits**: Maximum concurrent position management
- **Graceful Shutdown**: Safely closes all positions on exit
- **Error Handling**: Comprehensive error handling and logging
- **Retry Logic**: Automatic retry for failed transactions
## 📝 Logging
The bot provides detailed logging with color-coded output:
- 🚀 **Blue**: Bot startup and configuration
- 🎯 **Green**: Successful trades and profit targets
- 🛑 **Red**: Errors and stop losses
- ⚠️ **Yellow**: Warnings and position updates
- 📊 **Cyan**: Position information and PnL
## 🚨 Important Notes
### Security
- **Never share your private key**
- Use dedicated trading wallets
- Regularly rotate API keys
- Monitor bot activity
### Risk Disclaimer
- This bot is for educational purposes
- Cryptocurrency trading involves significant risk
- Past performance doesn't guarantee future results
- Use at your own risk
### Legal Compliance
- Ensure compliance with local regulations
- Check tax implications of automated trading
- Consult with financial advisors if needed
## 🔍 Troubleshooting
### Common Issues
1. **gRPC Connection Failed**
- Verify `GRPC_ENDPOINT` and `GRPCTOKEN`
- Check network connectivity
- Ensure Triton One subscription is active
2. **Transaction Failures**
- Verify wallet has sufficient SOL
- Check RPC endpoint status
- Adjust slippage tolerance
3. **No Trades Executing**
- Verify transaction filters
- Check liquidity requirements
- Review pool type settings
### Debug Mode
Enable debug logging by setting:
```bash
DEBUG=true
LOG_LEVEL=debug
```
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## 📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🙏 Acknowledgments
- [Triton One](https://triton.one/) for gRPC streaming
- [Jupiter](https://jup.ag/) for swap aggregation
- [Solana Labs](https://solana.com/) for the blockchain
- [Raydium](https://raydium.io/) for the DEX platform
## 📞 Support
For support and questions:
- Create an issue on GitHub
- Join our Discord community
- Check the documentation
---
**⚠️ Disclaimer: This software is for educational purposes only. Use at your own risk. The authors are not responsible for any financial losses.**

View File

@@ -0,0 +1,84 @@
# 🚀 Quick Setup Guide
## ⚡ 5-Minute Setup
### 1. Install Dependencies
```bash
npm install
```
### 2. Configure Environment
```bash
# Copy the template
cp env.template .env
# Edit .env with your settings
# At minimum, you need:
PRIVATE_KEY=your_wallet_private_key
RPC_URL=https://your-rpc-endpoint.com
GRPC_ENDPOINT=https://your-grpc-endpoint.com
GRPCTOKEN=your_grpc_token
```
### 3. Test Configuration
```bash
npm run test-config
```
### 4. Run Demo (Optional)
```bash
npm run demo
```
### 5. Start Trading
```bash
npm start
```
## 🔑 Required Services
- **Solana Wallet**: Phantom, Solflare, or private key
- **RPC Endpoint**: [Helius](https://helius.xyz/), [QuickNode](https://quicknode.com/), or [Alchemy](https://alchemy.com/)
- **gRPC Access**: [Triton One](https://triton.one/) subscription
## 💰 Minimum Requirements
- **Wallet Balance**: At least 1 SOL
- **Sniper Amount**: 0.1 SOL (configurable)
- **Network**: Stable internet connection
## 🚨 First Time Setup
1. **Test with small amounts first**
2. **Use a dedicated trading wallet**
3. **Monitor the bot initially**
4. **Adjust parameters based on performance**
## 📱 Quick Commands
| Command | Description |
|---------|-------------|
| `npm start` | Start the sniper bot |
| `npm run dev` | Start with auto-restart |
| `npm run test-config` | Verify configuration |
| `npm run demo` | Run demo mode |
| `start.bat` | Windows startup script |
| `start.sh` | Linux/Mac startup script |
## 🔧 Troubleshooting
### Common Issues:
- **"Private key invalid"** → Check key format (base58/base64/JSON)
- **"RPC connection failed"** → Verify RPC URL and network
- **"gRPC error"** → Check Triton One subscription
- **"No trades executing"** → Verify transaction filters
### Need Help?
1. Check the README.md
2. Run `npm run test-config`
3. Check console logs for errors
4. Verify all environment variables
---
**⚡ Ready to snipe? Run `npm start` and watch the magic happen!**

View File

@@ -0,0 +1,24 @@
# Nozomi settings (optional)
NOZOMI_URL=
NOZOMI_UUID=
# Private key (required)
PRIVATE_KEY=
# gRPC settings (📞Contact us to receive a one-time trial version)
GRPCTOKEN=
GRPC_ENDPOINT=
RPC_URL=https://api.mainnet-beta.solana.com
# Sniper amount (in SOL)
SNIPERAMOUNT=0.1
# Swap method: "0slot", "nozomi", "race", "solana"
SWAP_METHOD=solana
ENABLE_SWAP_TIP=true
SLIPPAGE_BPS=5000
PRIORITIZATION_FEE_LAMPORTS=20001
# Retry settings
MAX_RETRIES=2
RETRY_DELAY=500

View File

@@ -0,0 +1,182 @@
import { Connection, PublicKey, LAMPORTS_PER_SOL, Keypair } from "@solana/web3.js";
import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token";
import chalk from "chalk";
import dotenv from "dotenv";
import bs58 from "bs58";
dotenv.config();
import { swap } from "./swap.js";
// import { buy_pumpfun, buy_pumpswap, sell_pumpfun, sell_pumpswap } from "./swapsdk_0slot.js";
// import { buy_raydium_CPMM, buy_raydium_launchpad, sell_raydium_CPMM, sell_raydium_launchpad } from "./swapRaydium.js";
const RPC_URL = process.env.RPC_URL;
const connection = new Connection(RPC_URL, "confirmed");
//============functions============//
export const token_buy = async (mint, sol_amount, pool_status, context) => {
if (!mint) {
throw new Error("mint is required and was not provided.");
}
const currentUTC = new Date();
const txid = await swap("BUY", mint, sol_amount * LAMPORTS_PER_SOL);
// let txid = "";
console.log(chalk.green(`🟢BUY tokenAmount:::${sol_amount} pool_status: ${pool_status} `));
//============off chain sign ultra fast============//
// if (pool_status == "pumpfun") {
// txid = await buy_pumpfun(mint, sol_amount * LAMPORTS_PER_SOL, context);//off chain sign ultra fast
// } else if (pool_status == "pumpswap") {
// txid = await buy_pumpswap(mint, sol_amount * LAMPORTS_PER_SOL, context.pool);
// } else if (pool_status == "raydium_launchlab") {
// txid = await buy_raydium_launchpad(mint, sol_amount * LAMPORTS_PER_SOL, context);
// } else {
// txid = await buy_raydium_CPMM(mint, sol_amount * LAMPORTS_PER_SOL);
// }
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ Total BUY time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
return txid;
};
export const token_sell = async (mint, tokenAmount, pool_status, isFull, context) => {
try {
if (!mint) {
throw new Error("mint is required and was not provided.");
}
console.log(chalk.red(`🔴SELL tokenAmount:::${tokenAmount} pool_status: ${pool_status} `));
const currentUTC = new Date();
//============off chain sign ultra fast============//
// let txid = "";
// if (pool_status == "pumpfun") {
// txid = await sell_pumpfun(mint, tokenAmount, isFull, context);
// } else if (pool_status == "pumpswap") {
// txid = await sell_pumpswap(mint, tokenAmount, context.pool, isFull);
// } else if (pool_status == "raydium_launchlab") {
// txid = await sell_raydium_launchpad(mint, tokenAmount, isFull);
// } else {
// txid = await sell_raydium_CPMM(mint, tokenAmount, isFull);
// }
const txid = await swap("SELL", mint, tokenAmount);
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ Total SELL time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
if (txid === "stop") {
console.log(chalk.red(`[${new Date().toISOString()}] 🛑 Swap returned "stop" - no balance for ${mint}`));
return "stop";
}
if (txid) {
console.log(chalk.green(`Successfully sold ${tokenAmount} tokens : https://solscan.io/tx/${txid}`));
return txid;
}
return null;
} catch (error) {
console.error("Error in token_sell:", error.message);
if (error.response?.data) {
console.error("API Error details:", error.response.data);
}
return null;
}
};
export const getSplTokenBalance = async (mint) => {
if (!mint) {
console.log("🔄 Token balance error: Mint address is undefined or null.");
throw new Error("Mint address is undefined or null.");
}
let mintPubkey;
try {
mintPubkey = new PublicKey(mint);
} catch (err) {
console.log("🔄 Token balance error: Invalid mint address provided.");
throw err;
}
// const publicKey = getPublicKeyFromPrivateKey();
const publicKey = getPublicKeyFromPrivateKey();
const ata = await getAssociatedTokenAddress(mintPubkey, new PublicKey(publicKey));
let account;
try {
account = await getAccount(connection, ata);
} catch (err) {
// Handle TokenAccountNotFoundError gracefully
if (
err.name === "TokenAccountNotFoundError" ||
(err.message && (
err.message.includes("Failed to find account") ||
err.message.includes("Account does not exist") ||
err.message.includes("could not find account")
))
) {
// No account found, treat as zero balance
console.log("🔄 Token balance: Account not found, returning 0.");
return null;
}
// If the error is related to an invalid mint, log and throw error
if (err.message && err.message.includes("Invalid param")) {
console.log("🔄 Token balance error: Invalid mint param.");
throw err;
}
// Other errors
console.log("🔄 Token balance error:", err.message || err);
throw err;
}
return Number(account.amount); // Convert BigInt to Number
};
export const checkWalletBalance = async () => {
try {
const pubkey = getPublicKeyFromPrivateKey();
const balanceLamports = await connection.getBalance(pubkey);
const balance = balanceLamports / LAMPORTS_PER_SOL;
return { balance };
} catch (err) {
console.error("Error checking wallet balance:", err.message || err);
throw err;
}
};
export const getKeypairFromPrivateKey = (privateKeyString) => {
try {
// Try base58 first
try {
const decoded = bs58.decode(privateKeyString);
return Keypair.fromSecretKey(decoded);
} catch (e) {
// Not base58, try base64
try {
const decoded = Buffer.from(privateKeyString, 'base64');
return Keypair.fromSecretKey(decoded);
} catch (e2) {
// Not base64, try JSON array
try {
const arr = JSON.parse(privateKeyString);
const uint8arr = new Uint8Array(arr);
return Keypair.fromSecretKey(uint8arr);
} catch (e3) {
throw new Error('Invalid private key format. Supported formats: base58, base64, or JSON array');
}
}
}
} catch (err) {
throw new Error('Failed to decode private key: ' + err.message);
}
};
export const getPublicKeyFromPrivateKey = () => {
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error("Private key is required and was not provided.");
}
const keypair = getKeypairFromPrivateKey(privateKey);
return keypair.publicKey.toString();
};

View File

@@ -0,0 +1,204 @@
import "dotenv/config";
import Client from "@triton-one/yellowstone-grpc";
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";
import { decodeInstruction } from '@solana/spl-token';
import { Connection, Keypair } from '@solana/web3.js';
import chalk from "chalk";
import { tOutPut } from "./parsingtransaction.js";
import { handleNewTokenLaunch } from "./main.js";
import dotenv from 'dotenv'
dotenv.config();
const GRPCTOKEN=process.env.GRPCTOKEN
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT
// Pre-define constants
const SOLANA_TOKEN = "So11111111111111111111111111111111111111112";
const RAYDIUM_FEE = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"//"7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5";
const Raydium_launchpad_authority = "WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh"
// Create default client
const defaultClient = new Client(
GRPC_ENDPOINT,
GRPCTOKEN
);
export let isNewLaunchRunning = true;
export const stopNewLaunch = () => {
isNewLaunchRunning = false;
console.log(chalk.red("New launch monitoring stopped"));
};
// Default request args
const defaultArgs = {
accounts: {},
slots: {},
transactions: {
pumpfun: {
vote: false,
failed: false,
signature: undefined,
accountInclude: [RAYDIUM_FEE],
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.PROCESSED,
};
// This function checks for MintTo instructions by comparing with log messages
async function checkMintTo(data) {
const tx = data.transaction?.transaction;
const meta = data.transaction;
if (!tx || !meta?.transaction?.meta?.logMessages) return;
// Find if MintTo is present in log messages
const mintToLog = meta.transaction.meta.logMessages.find((log) =>
typeof log === "string" && log.toLowerCase().includes("instruction: mintto")
);
if (mintToLog) {
console.log("🩸🩸🩸🩸🩸 MintTo found in logs!");
return true
} else {
// No MintTo found in logs
return false
}
}
async function handleStream(client = defaultClient, args = defaultArgs) {
const stream = await client.subscribe(args);
const streamClosed = new Promise((resolve, reject) => {
stream.on("error", (error) => {
console.error("Stream Error:", error);
reject(error);
stream.end();
});
stream.on("end", resolve);
stream.on("close", resolve);
});
stream.on("data", async (data) => {
// Return early if monitoring is disabled
if (!isNewLaunchRunning) {
stream.end();
return;
}
try {
// console.log(chalk.green("start__________new token streaming_________"));
if (!data?.transaction?.transaction) {
return null;
}
const mintTo = await checkMintTo(data)
if(!mintTo){
return null
}
console.log(`[${new Date().toISOString()}] 🩸🩸🩸🩸🩸 MintTo found in logs!`);
const preTokenBalances = data?.transaction?.transaction?.meta?.preTokenBalances;
const postTokenBalances = data?.transaction?.transaction?.meta?.postTokenBalances;
if (!preTokenBalances || !postTokenBalances) {
console.log("Token balances not found in transaction data");
return null;
}
let pre_sol = 0;
let post_sol = 0;
let pre_token = 0;
let post_token = 0;
let token_mint = "";
let token_owner = "";
for (const balance of postTokenBalances) {
if (balance.owner !== Raydium_launchpad_authority) {
if (balance.mint !== SOLANA_TOKEN) {
post_token = balance.uiTokenAmount.uiAmount || 0;
token_mint = balance.mint;
token_owner = balance.owner;
}
} else {
post_sol = balance.uiTokenAmount.uiAmount || 0;
}
}
for (const balance of preTokenBalances) {
if (balance.owner !== Raydium_launchpad_authority) {
if (balance.mint !== SOLANA_TOKEN) {
pre_token = balance.uiTokenAmount.uiAmount || 0;
}
}
}
const solChanges = post_sol-pre_sol;
const tokenChanges = post_token-pre_token;
console.log(chalk.bgBlue.bold(`🪙 Token Mint:`), chalk.white(token_mint));
console.log(chalk.bgMagenta.bold(`👤 Token Owner:`), chalk.white(token_owner));
console.log(chalk.bgYellow.bold(`💸 SOL Balance Change:`), chalk.yellow(`${solChanges > 0 ? "+" : ""}${solChanges}`));
console.log(chalk.bgCyan.bold(`🔄 Token Balance Change:`), chalk.cyan(`${tokenChanges > 0 ? "+" : ""}${tokenChanges}`));
if (solChanges> 0.1) {
console.log(chalk.bgGreen("Found large SOL transfer:", solChanges));
// Parse transaction data to get pool information
const parsedData = await tOutPut(data);
if (parsedData) {
console.log(chalk.cyan(`Pool status: Raydium launchpad`));
// Call the main bot logic to handle the new token launch
await handleNewTokenLaunch(token_mint, parsedData.pool_status, parsedData.context);
} else {
console.log(chalk.yellow("Failed to parse transaction data"));
}
return token_mint;
}
return null;
} catch (error) {
console.error("Error processing transaction data:", error);
return null;
}
});
try {
await stream.write(args);
} catch (error) {
console.error("Subscription request failed:", error);
throw error;
}
await streamClosed;
}
export async function newlunched_subscribeCommand(client = defaultClient, args = defaultArgs) {
// Set monitoring flag to true when starting
isNewLaunchRunning = true;
console.log(chalk.green("New launch monitoring started"));
while (isNewLaunchRunning) {
try {
await handleStream(client, args);
} catch (error) {
console.error("Stream error, restarting in 1 second...", error);
// Only wait and retry if monitoring is still enabled
if (isNewLaunchRunning) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
console.log("New launch monitoring stopped");
}
// Export client and args for external use
export { defaultClient, defaultArgs };
// Remove the auto-execution to prevent conflicts
// newlunched_subscribeCommand()

View File

@@ -0,0 +1,28 @@
import { pump_geyser } from "./main.js";
import dotenv from "dotenv";
dotenv.config()
const privateKey = process.env.PRIVATE_KEY; // Use private key directly
if (!privateKey) {
console.error("Error: PRIVATE_KEY is not set in environment variables.");
process.exit(1);
}
(async () => {
try {
const { getBalance } = await import("./swap.js");
const balance = await getBalance();
if (balance < 1) {
console.error("Error: Wallet balance is below 1 SOL. Current balance:", balance, "SOL");
process.exit(1);
}
} catch (err) {
console.error("Error checking wallet balance:", err.message);
process.exit(1);
}
})();
pump_geyser()

View File

@@ -0,0 +1,194 @@
import { newlunched_subscribeCommand, stopNewLaunch } from "./grpc.js";
import { token_buy, token_sell, getSplTokenBalance, getPublicKeyFromPrivateKey } from "./fuc.js";
import chalk from "chalk";
import dotenv from "dotenv";
dotenv.config();
// Trading configuration
const SNIPER_AMOUNT = parseFloat(process.env.SNIPERAMOUNT || "0.1"); // SOL amount to snipe with
const PROFIT_TARGET = parseFloat(process.env.PROFIT_TARGET || "2.0"); // 2x profit target
const STOP_LOSS = parseFloat(process.env.STOP_LOSS || "0.5"); // 50% stop loss
const MAX_HOLD_TIME = parseInt(process.env.MAX_HOLD_TIME || "300000"); // 5 minutes in ms
// Track active positions
const activePositions = new Map();
export const pump_geyser = async () => {
try {
const walletKey = getPublicKeyFromPrivateKey();
// INSERT_YOUR_CODE
console.log(chalk.magentaBright(`
██████╗██████╗ ██╗ ██╗██████╗ ████████╗ ██████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗
██╔════╝██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██║ ██╔╝██║████╗ ██║██╔════╝
██║ ██████╔╝ ██║ ██╔╝██████╔╝ ██║ ██║ ██║█████╔╝ ██║██╔██╗ ██║██║ ██╗
██║ ██╔══██╗ ██╔═╝ ██╔═══╝ ██║ ██║ ██║██╔═██╗ ██║██║╚██╗██║██║ ██║
╚██████╗██║ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██╗██║██║ ╚████║╚██████╔╝
╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝
`));
console.log(chalk.blue.bold("🚀 Starting Solana Raydium Sniper Bot..."));
console.log(chalk.blue(`🔑 Wallet Public Key: ${chalk.yellow(walletKey)}`));
console.log(chalk.blue(`💰 Sniper Amount: ${chalk.green(SNIPER_AMOUNT)} SOL`));
console.log(chalk.blue(`🎯 Profit Target: ${chalk.green(PROFIT_TARGET)}x`));
console.log(chalk.blue(`🛑 Stop Loss: ${chalk.green(STOP_LOSS)}x`));
console.log(chalk.blue(`⏱️ Max Hold Time: ${chalk.green(MAX_HOLD_TIME/1000)}s`));
// Start monitoring for new token launches
await newlunched_subscribeCommand();
// Set up position monitoring
setInterval(monitorPositions, 5000); // Check positions every 5 seconds
// Set up graceful shutdown
process.on('SIGINT', async () => {
console.log(chalk.yellow("\n🛑 Shutting down sniper bot..."));
stopNewLaunch();
// Close all positions before exit
for (const [mint, position] of activePositions) {
try {
console.log(chalk.yellow(`🔄 Closing position for ${mint}...`));
await closePosition(mint, position);
} catch (error) {
console.error(chalk.red(`Error closing position for ${mint}:`, error.message));
}
}
process.exit(0);
});
} catch (error) {
console.error(chalk.red("Error in pump_geyser:", error));
throw error;
}
};
// Monitor active positions for profit taking or stop loss
async function monitorPositions() {
for (const [mint, position] of activePositions) {
try {
const currentBalance = await getSplTokenBalance(mint);
if (!currentBalance || currentBalance <= 0) {
console.log(chalk.yellow(`⚠️ No balance for ${mint}, removing from active positions`));
activePositions.delete(mint);
continue;
}
const currentValue = currentBalance * position.currentPrice;
const profitRatio = currentValue / position.entryValue;
const holdTime = Date.now() - position.entryTime;
// Check profit target
if (profitRatio >= PROFIT_TARGET) {
console.log(chalk.green(`🎯 Profit target reached for ${mint}: ${profitRatio.toFixed(2)}x`));
await closePosition(mint, position);
continue;
}
// Check stop loss
if (profitRatio <= STOP_LOSS) {
console.log(chalk.red(`🛑 Stop loss triggered for ${mint}: ${profitRatio.toFixed(2)}x`));
await closePosition(mint, position);
continue;
}
// Check max hold time
if (holdTime >= MAX_HOLD_TIME) {
console.log(chalk.yellow(`⏰ Max hold time reached for ${mint}, closing position`));
await closePosition(mint, position);
continue;
}
// Update current price (you might want to implement price fetching here)
// For now, we'll use a simple approach
position.currentPrice = position.entryPrice; // Placeholder
} catch (error) {
console.error(chalk.red(`Error monitoring position for ${mint}:`, error.message));
}
}
}
// Close a position by selling tokens
async function closePosition(mint, position) {
try {
const currentBalance = await getSplTokenBalance(mint);
if (!currentBalance || currentBalance <= 0) {
console.log(chalk.yellow(`No balance to sell for ${mint}`));
activePositions.delete(mint);
return;
}
console.log(chalk.blue(`🔄 Closing position for ${mint}, selling ${currentBalance} tokens`));
const txid = await token_sell(mint, currentBalance, position.poolStatus, true, position.context);
if (txid && txid !== "stop") {
console.log(chalk.green(`✅ Position closed for ${mint}: ${txid}`));
// Calculate final PnL
const finalValue = currentBalance * position.currentPrice;
const pnl = finalValue - position.entryValue;
const pnlRatio = (pnl / position.entryValue) * 100;
console.log(chalk.cyan(`📊 Final PnL for ${mint}: ${pnl.toFixed(4)} SOL (${pnlRatio.toFixed(2)}%)`));
} else {
console.log(chalk.red(`❌ Failed to close position for ${mint}`));
}
activePositions.delete(mint);
} catch (error) {
console.error(chalk.red(`Error closing position for ${mint}:`, error.message));
}
}
// Function to handle new token launches (called from grpc.js)
export const handleNewTokenLaunch = async (tokenMint, poolStatus, context) => {
try {
console.log(chalk.green(`🎯 New token launch detected: ${tokenMint}`));
console.log(chalk.blue(`🏊 Pool type: ${poolStatus}`));
// Check if we already have a position in this token
if (activePositions.has(tokenMint)) {
console.log(chalk.yellow(`⚠️ Already have position in ${tokenMint}, skipping`));
return;
}
// Execute snipe
console.log(chalk.blue(`🚀 Sniping ${tokenMint} with ${SNIPER_AMOUNT} SOL...`));
const txid = await token_buy(tokenMint, SNIPER_AMOUNT, poolStatus, context);
if (txid) {
console.log(chalk.green(`✅ Snipe successful! TX: ${txid}`));
// Add to active positions
activePositions.set(tokenMint, {
entryTime: Date.now(),
entryValue: SNIPER_AMOUNT,
entryPrice: 1, // Placeholder - you might want to calculate actual price
currentPrice: 1,
poolStatus: poolStatus,
context: context,
txid: txid
});
console.log(chalk.cyan(`📊 Position opened for ${tokenMint}`));
console.log(chalk.cyan(`💰 Entry Value: ${SNIPER_AMOUNT} SOL`));
console.log(chalk.cyan(`🎯 Profit Target: ${PROFIT_TARGET}x`));
console.log(chalk.cyan(`🛑 Stop Loss: ${STOP_LOSS}x`));
} else {
console.log(chalk.red(`❌ Snipe failed for ${tokenMint}`));
}
} catch (error) {
console.error(chalk.red(`Error handling new token launch for ${tokenMint}:`, error.message));
}
};
// // Export for external use
// export { activePositions, handleNewTokenLaunch };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
{
"name": "solana-trading-bot",
"version": "1.0.0",
"description": "Solana trading bot with automated PnL strategies",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"@coral-xyz/anchor": "^0.30.1",
"@degenfrends/solana-rugchecker": "^0.0.16",
"@project-serum/anchor": "^0.26.0",
"@pump-fun/pump-sdk": "^1.3.4",
"@pump-fun/pump-swap-sdk": "^0.0.1-beta.36",
"@raydium-io/raydium-sdk": "^1.3.1-beta.0",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98.0",
"@triton-one/yellowstone-grpc": "^4.0.0",
"axios": "^1.7.9",
"bs58": "^6.0.0",
"chalk": "^5.3.0",
"dotenv": "^16.4.7",
"fs": "^0.0.1-security",
"helius-sdk": "^1.4.2",
"node-fetch": "^3.3.2",
"node-telegram-bot-api": "^0.63.0",
"pump-swap-core-v1": "^2.0.0",
"readline-sync": "^1.4.10",
"require": "^0.4.4",
"tweetnacl": "^1.0.3",
"util": "^0.12.5",
"web3-fb": "^1.2.2",
"websocket": "^1.0.35",
"ws": "^8.18.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
},
"type": "module",
"engines": {
"node": ">=16.0.0"
}
}

View File

@@ -0,0 +1,266 @@
import bs58 from "bs58";
// New function to handle parsed transaction data from getDataFromTx
export async function parseTransactionFromData(parsedTx) {
if (!parsedTx) return null;
const meta = parsedTx.meta;
// const logs = meta?.logMessages;
// const logFilter = logs?.some((instruction) => instruction.match(/MintTo/i));
const innerInstructions = meta.innerInstructions;
const flattenedInnerInstructions = (await innerInstructions?.flatMap((ix) => ix.instructions || [])) || [];
const allInstructions = [...flattenedInnerInstructions];
// console.log(allInstructions)
if (allInstructions.length === 0) return null;
// Filter out instructions that don't have data property
const validInstructions = allInstructions.filter((instruction) => instruction && instruction.data);
if (validInstructions.length === 0) return null;
const largestDataInstruction = await validInstructions.reduce((largest, current) => {
if (!current || !current.data || !largest || !largest.data) {
return largest || current;
}
return current.data.length > largest.data.length ? current : largest;
});
// console.log(largestDataInstruction)
if (!largestDataInstruction || !largestDataInstruction.data) {
return null;
}
// console.log(largestDataInstruction.data)
const rawData = bs58.decode(largestDataInstruction.data);
const buffer = Buffer.from(rawData);
// console.log(buffer)
const parsedInstructionData = parseTransactionData(buffer);
// console.log(parsedInstructionData)
if (!parsedInstructionData) return null;
return {
solChanges: parseFloat(parsedInstructionData.solchange),
tokenChanges: parseFloat(parsedInstructionData.tokenchange),
isBuy: parsedInstructionData.isBuy,
user: parsedInstructionData.user,
mint: parsedInstructionData.mint,
pool: parsedInstructionData.pool,
liquidity: parsedInstructionData.liquidity,
coinCreator: parsedInstructionData.coinCreator,
context: parsedInstructionData.context,
};
}
export async function tOutPut(data) {
// Check if this is parsed transaction data from getDataFromTx
if (data && data.meta && data.transaction) {
// This is parsed transaction data from getDataFromTx
return await parseTransactionFromData(data);
}
// Original format handling
const dataTx = data?.transaction?.transaction;
if (!dataTx) return;
const signature = bs58.encode(Buffer.from(dataTx?.transaction.signatures?.[0]));
// console.log("signature:::", signature);
const meta = dataTx?.meta;
const logs = meta?.logMessages;
const logFilter = logs?.some((instruction) => instruction.match(instruction.match(/MintTo/i)));
const innerInstructions = meta.innerInstructions;
// console.log(innerInstructions)
const flattenedInnerInstructions = (await innerInstructions?.flatMap((ix) => ix.instructions || [])) || [];
// console.log(flattenedInnerInstructions)
const allInstructions = [...flattenedInnerInstructions];
// console.log("allInstructions",allInstructions)
if (allInstructions.length === 0) return;
// Filter out instructions that don't have data property
const validInstructions = allInstructions.filter((instruction) => instruction && instruction.data);
if (validInstructions.length === 0) return null;
const largestDataInstruction = await validInstructions.reduce((largest, current) => {
if (!current || !current.data || !largest || !largest.data) {
return largest || current;
}
return current.data.length > largest.data.length ? current : largest;
});
if (!largestDataInstruction || !largestDataInstruction.data) {
return null;
}
// console.log("🎈🎈🎈largestDataInstruction:::", largestDataInstruction.data);
const parsedInstructionData = parseTransactionData(largestDataInstruction.data);
// console.log("🎈",JSON.stringify(parsedInstructionData,null,2))
if (!parsedInstructionData) return null;
// console.log(parsedInstructionData);
// console.log("Mint>>>>>>>>", parsedInstructionData.mint);
return {
solChanges: parseFloat(parsedInstructionData.solchange),
tokenChanges: parseFloat(parsedInstructionData.tokenchange),
isBuy: parsedInstructionData.isBuy,
user: parsedInstructionData.user,
mint: parsedInstructionData.mint,
pool: parsedInstructionData.pool,
liquidity: parsedInstructionData.liquidity / 10 ** 9,
coinCreator: parsedInstructionData.coinCreator,
pool_status: parsedInstructionData.pool_status,
signature: signature,
context: parsedInstructionData.context,
};
// console.log("Signature>>>>>>>>", signature);
}
export function parseTransactionData(buffer) {
try {
function parsePublicKey(offset) {
return bs58.encode(buffer.slice(offset, offset + 32)); // Convert 32 bytes to Base58
}
function parseBigInt(offset) {
return buffer.readBigUInt64LE(offset).toString(); // Read 8 bytes as Little-Endian
}
if (buffer.length == 368) {
const parsedData_PumpSwap = {
mint: null,
timestamp: parseBigInt(16), // 8 bytes (Timestamp)
baseAmountIn: parseBigInt(24), // 8 bytes (Base amount in)
minQuoteAmountOut: parseBigInt(32), // 8 bytes (Minimum quote amount out)
userBaseTokenReserves: parseBigInt(40), // 8 bytes (User base token reserves)
userQuoteTokenReserves: parseBigInt(48), // 8 bytes (User quote token reserves)
poolBaseTokenReserves: parseBigInt(56), // 8 bytes (Pool base token reserves)
poolQuoteTokenReserves: parseBigInt(64), // 8 bytes (Pool quote token reserves)
quoteAmountOut: parseBigInt(72), // 8 bytes (Quote amount out)
lpFeeBasisPoints: parseBigInt(80), // 8 bytes (LP fee basis points)
lpFee: parseBigInt(88), // 8 bytes (LP fee)
protocolFeeBasisPoints: parseBigInt(96), // 8 bytes (Protocol fee basis points)
protocolFee: parseBigInt(104), // 8 bytes (Protocol fee)
quoteAmountOutWithoutLpFee: parseBigInt(112), // 8 bytes (Quote amount out without LP fee)
userQuoteAmountOut: parseBigInt(120), // 8 bytes (User quote amount out)
pool: parsePublicKey(128), // 32 bytes (Pool address)
user: parsePublicKey(160), // 32 bytes (User address)
userBaseTokenAccount: parsePublicKey(192), // 32 bytes (User base token account)
userQuoteTokenAccount: parsePublicKey(224), // 32 bytes (User quote token account)
protocolFeeRecipient: parsePublicKey(256), // 32 bytes (Protocol fee recipient)
protocolFeeRecipientTokenAccount: parsePublicKey(288), // 32 bytes (Protocol fee recipient token account)
coinCreator: parsePublicKey(320), // 32 bytes (Coin creator address)
coinCreatorFeeBasisPoints: parseBigInt(328), // 8 bytes (Coin creator fee basis points)
coinCreatorFee: parseBigInt(336), // 8 bytes (Coin creator fee)
};
// console.log(parsedData_PumpSwap);
let isBuy = parsedData_PumpSwap.quoteAmountOutWithoutLpFee > parsedData_PumpSwap.quoteAmountOut;
return {
solchange: parsedData_PumpSwap.userQuoteAmountOut,
tokenchange: parsedData_PumpSwap.baseAmountIn,
isBuy,
user: parsedData_PumpSwap.user,
mint: parsedData_PumpSwap.mint,
pool: parsedData_PumpSwap.pool,
liquidity: parsedData_PumpSwap.poolQuoteTokenReserves * 2,
coinCreator: parsedData_PumpSwap.coinCreator,
pool_status: "pumpswap",
context: parsedData_PumpSwap,
};
} else if (buffer.length == 233) {
const parsedData_PumpFun = {
mint: parsePublicKey(16), // 32 bytes (Mint address)
solAmount: parseBigInt(48), // 8 bytes (Amount in SOL)
tokenAmount: parseBigInt(56), // 8 bytes (Token amount)
isBuy: buffer[64] === 1, // 1 byte (Boolean: 0 = Sell, 1 = Buy)
user: parsePublicKey(65), // 32 bytes (User address)
timestamp: parseBigInt(97), // 8 bytes (Timestamp - Unix format)
virtualSolReserves: parseBigInt(105), // 8 bytes (Virtual reserves)
virtualTokenReserves: parseBigInt(113), // 8 bytes (Virtual token reserves)
realSolReserves: parseBigInt(121), // 8 bytes (Real reserves)
realTokenReserves: parseBigInt(129), // 8 bytes (Real token reserves)
feeRecipient: parsePublicKey(137), // 32 bytes (Fee recipient address)
feeBasisPoints: parseBigInt(169), // 8 bytes (Fee basis points)
fee: parseBigInt(177), // 8 bytes (Fee amount)
creator: parsePublicKey(185), // 32 bytes (Creator address)
creatorFeeBasisPoints: parseBigInt(217), // 8 bytes (Creator fee basis points)
creatorFee: parseBigInt(225), // 8 bytes (Creator fee amount)
};
// console.log(parsedData_PumpFun);
let isBuy = parsedData_PumpFun.isBuy;
return {
solchange: parsedData_PumpFun.solAmount,
tokenchange: parsedData_PumpFun.tokenAmount,
isBuy,
user: parsedData_PumpFun.user,
mint: parsedData_PumpFun.mint,
pool: null,
liquidity: parsedData_PumpFun.virtualSolReserves,
coinCreator: parsedData_PumpFun.creator,
pool_status: "pumpfun",
context: parsedData_PumpFun,
};
} else if (buffer.length == 146) {
const parsedData_Raydium_LaunchLab = {
poolState: parsePublicKey(16), // 32 bytes (Pool state address)
totalBaseSell: parseBigInt(48), // 8 bytes (Total base sold)
virtualBase: parseBigInt(56), // 8 bytes (Virtual base reserves)
virtualQuote: parseBigInt(64), // 8 bytes (Virtual quote reserves)
realBaseBefore: parseBigInt(72), // 8 bytes (Real base before)
realQuoteBefore: parseBigInt(80), // 8 bytes (Real quote before)
realBaseAfter: parseBigInt(88), // 8 bytes (Real base after)
realQuoteAfter: parseBigInt(96), // 8 bytes (Real quote after)
amountIn: parseBigInt(104), // 8 bytes (Amount in)
amountOut: parseBigInt(112), // 8 bytes (Amount out)
protocolFee: parseBigInt(120), // 8 bytes (Protocol fee)
platformFee: parseBigInt(128), // 8 bytes (Platform fee)
shareFee: parseBigInt(136), // 8 bytes (Share fee)
tradeDirection: buffer[144] , // 1 byte (1 = sell, 0 = buy)
poolStatus: buffer[145] === 0 ? { normal: {} } : { fund: {} }, // 1 byte (0 = normal, 1 = fund)
};
const isBuy = !parsedData_Raydium_LaunchLab.tradeDirection;
let solAmount = 0;
let tokenAmount = 0;
// console.log("🎈🎈🎈parsedData_Raydium_LaunchLab:::", parsedData_Raydium_LaunchLab);
if (isBuy){
solAmount = parsedData_Raydium_LaunchLab.amountIn;
tokenAmount = parsedData_Raydium_LaunchLab.amountOut;
}else{
solAmount = parsedData_Raydium_LaunchLab.amountOut;
tokenAmount = parsedData_Raydium_LaunchLab.amountIn;
}
return {
solchange: solAmount,
tokenchange: tokenAmount,
isBuy,
user: null,
mint: null,
pool: parsedData_Raydium_LaunchLab.poolState,
liquidity: 2*parsedData_Raydium_LaunchLab.realQuoteAfter,
coinCreator: parsedData_Raydium_LaunchLab.poolState,
pool_status: "raydium_launchlab",
context: parsedData_Raydium_LaunchLab,
};
} else {
return {
solchange: 0,
tokenchange: 0,
isBuy: false,
user: null,
mint: null,
pool: null,
liquidity: 0,
coinCreator: null,
pool_status: "raydium",
context: null,
};
} }catch (error) {
console.error("Error parsing transaction data:", error);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
// This module provides an offchain swap function using an IDL (Interface Definition Language) specification.
// It assumes you have a gRPC client generated from the IDL and a wallet/signer available.
export async function offchainSwap({ fromToken, toToken, amount, walletAddress }) {
//#========📞📞contact to dev📞📞============//
}

View File

@@ -0,0 +1,307 @@
import axios from "axios";
import { Keypair, Connection, LAMPORTS_PER_SOL, VersionedTransaction, SystemProgram, PublicKey, TransactionMessage, sendAndConfirmRawTransaction } from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";
import { readFile } from "fs/promises";
import { Wallet } from "@project-serum/anchor";
import dotenv from "dotenv";
import chalk from "chalk";
import fs from "fs";
import path from "path";
import bs58 from "bs58";
import { getSplTokenBalance } from "./fuc.js";
dotenv.config();
// SWAP_METHOD: "0slot", "nozomi", "race", "solana"
const SWAP_METHOD = (process.env.SWAP_METHOD || "solana").toLowerCase();
const NOZOMI_URL = process.env.NOZOMI_URL;
const NOZOMI_UUID = process.env.NOZOMI_UUID;
const nozomiConnection = new Connection(`${NOZOMI_URL}?c=${NOZOMI_UUID}`);
const NOZOMI_TIP_LAMPORTS = Number(process.env.NOZOMI_TIP_LAMPORTS || 200000);
const JITO_TIP_LAMPORTS = Number(process.env.JITO_TIP || 100000);
const PRIORITIZATION_FEE_LAMPORTS = Number(process.env.PRIORITIZATION_FEE_LAMPORTS || 10000);
const NOZOMI_TIP_ADDRESS = new PublicKey("TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq");
export const MAX_RETRIES = parseInt(process.env.MAX_RETRIES) || 3;
export const decodePrivateKey = (secretKeyString) =>{
try {
// Try base58 first
return bs58.decode(secretKeyString);
} catch (error) {
try {
// Try base64
return Buffer.from(secretKeyString, 'base64');
} catch (base64Error) {
try {
// Try JSON array (for array format)
const jsonArray = JSON.parse(secretKeyString);
return new Uint8Array(jsonArray);
} catch (jsonError) {
throw new Error('Invalid private key format. Supported formats: base58, base64, or JSON array');
}
}
}
}
export const loadwallet = async () => {
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error("PRIVATE_KEY not found in environment variables");
}
try {
const privateKeyBytes=decodePrivateKey(privateKey)
const keypair = Keypair.fromSecretKey(privateKeyBytes);
if (!keypair) {
throw new Error("Failed to create Keypair from the provided private key");
}
const wallet = new Wallet(keypair);
wallet.keypair = keypair;
return wallet;
} catch (error) {
console.error("Error loading wallet:", error);
throw error;
}
};
export const rpc_connection = () => {
return new Connection(process.env.RPC_URL, "confirmed");
};
export const getBalance = async () => {
const connection = rpc_connection();
const walletInstance = await loadwallet();
const balance = await connection.getBalance(walletInstance.publicKey);
console.log(`Balance =>`, balance / LAMPORTS_PER_SOL, "SOL");
return balance / LAMPORTS_PER_SOL;
};
// const quoteResponse = await (
// await fetch(
// `https://lite-api.jup.ag/swap/v1/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=${this.mint.toString()}&amount=${this.buy_amount}&slippageBps=4000&restrictIntermediateTokens=true`
// )
// ).json();
// const swapResponse = await (
// await fetch('https://lite-api.jup.ag/swap/v1/swap', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// quoteResponse,
// userPublicKey: wallet.publicKey,
// dynamicComputeUnitLimit: true,
// dynamicSlippage: true,
// prioritizationFeeLamports: {
// priorityLevelWithMaxLamports: {
// maxLamports: 4000,
// priorityLevel: "high"
// }
// }
// })
// })
// ).json();
// const transactionBase64 = swapResponse.swapTransaction
// const transaction = VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
// transaction.sign([wallet]);
// await sendAndConfirmRawTransaction(
// this.instantConnection,
// Buffer.from(transaction.serialize()),
// { skipPreflight: true, maxRetries: 5 }
// )
const getResponse = async (tokenA, tokenB, amount, slippageBps, anchorWallet) => {
const quoteResponse = (
await axios.get(
`https://lite-api.jup.ag/swap/v1/quote?inputMint=${tokenA}&outputMint=${tokenB}&amount=${amount}&slippageBps=${slippageBps}`
)
).data;
// Build swap request body based on SWAP_METHOD
let swapRequestBody = {
quoteResponse,
userPublicKey: anchorWallet.publicKey.toString(),
wrapAndUnwrapSol: true,
dynamicComputeUnitLimit: true,
};
// Map SWAP_METHOD string to behavior
if (SWAP_METHOD === "solana" || SWAP_METHOD === "0slot") {
// Standard prioritization fee
swapRequestBody.prioritizationFeeLamports = PRIORITIZATION_FEE_LAMPORTS;
} else if (SWAP_METHOD === "race") {
// JITO tip
swapRequestBody.prioritizationFeeLamports = { jitoTipLamports: JITO_TIP_LAMPORTS };
}
// "nozomi" handled in executeTransaction
const swapResponse = await axios.post(`https://lite-api.jup.ag/swap/v1/swap`, swapRequestBody);
return swapResponse;
};
const executeTransaction = async (connection, swapTransaction, anchorWallet) => {
try {
if (!anchorWallet?.keypair) {
throw new Error("Invalid anchorWallet: keypair is undefined");
}
const transaction = VersionedTransaction.deserialize(Buffer.from(swapTransaction, "base64"));
transaction.sign([anchorWallet.keypair]);
let newMessage, newTransaction, rawTransaction, txid, timestart;
if (SWAP_METHOD === "nozomi") {
console.log("Nozomi response: send via nozomi connection");
let blockhash = await connection.getLatestBlockhash();
let message = transaction.message;
let addressLookupTableAccounts = await loadAddressLookupTablesFromMessage(message, connection);
let txMessage = TransactionMessage.decompile(message, { addressLookupTableAccounts });
// Add Nozomi tip instruction
let nozomiTipIx = SystemProgram.transfer({
fromPubkey: anchorWallet.publicKey,
toPubkey: NOZOMI_TIP_ADDRESS,
lamports: NOZOMI_TIP_LAMPORTS,
});
txMessage.instructions.push(nozomiTipIx);
newMessage = txMessage.compileToV0Message(addressLookupTableAccounts);
newMessage.recentBlockhash = blockhash.blockhash;
newTransaction = new VersionedTransaction(newMessage);
newTransaction.sign([anchorWallet.keypair]);
rawTransaction = newTransaction.serialize();
timestart = Date.now();
txid = await nozomiConnection.sendRawTransaction(rawTransaction, {
skipPreflight: false,
maxRetries: 2,
});
console.log("Nozomi response: txid: %s", txid);
} else {
console.log("Standard/JITO/0slot/solana/race: send via normal connection");
// Standard/JITO/0slot/solana/race: send via normal connection
const currentUTC = new Date();
rawTransaction = transaction.serialize();
timestart = Date.now();
txid = await sendAndConfirmRawTransaction(connection, Buffer.from(rawTransaction), {
skipPreflight: true,
maxRetries: 1,
});
console.log("Standard/JITO/0slot/solana/race response: txid: %s", txid);
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ confirm time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
return txid;
}
} catch (error) {
console.error("Transaction execution error:", error);
console.log(chalk.red("Transaction reconfirm after 1s!"));
await new Promise((resolve) => setTimeout(resolve, 1000));
}
};
async function loadAddressLookupTablesFromMessage(message, connection) {
let addressLookupTableAccounts = [];
for (let lookup of message.addressTableLookups) {
let lutAccounts = await connection.getAddressLookupTable(lookup.accountKey);
addressLookupTableAccounts.push(lutAccounts.value);
}
return addressLookupTableAccounts;
}
export const swap = async (action, mint, amount) => {
const SOL_ADDRESS = "So11111111111111111111111111111111111111112";
const RETRY_DELAY = Number(process.env.RETRY_DELAY) || 1000; // fallback to 1s if not set
try {
const connection = rpc_connection();
const wallet = await loadwallet();
// Determine tokenA and tokenB based on action and mint
let tokenA, tokenB;
if (action === "BUY") {
tokenA = SOL_ADDRESS;
tokenB = mint;
} else if (action === "SELL") {
tokenA = mint;
tokenB = SOL_ADDRESS;
} else {
throw new Error(`Unknown action: ${action}`);
}
console.log(`Swapping ${amount} of ${tokenA} for ${tokenB}...`);
let retryCount = 0;
while (retryCount <= MAX_RETRIES) {
try {
console.log(`Attempt ${retryCount + 1}/${MAX_RETRIES + 1}`);
// If this is a sell (tokenA is not SOL and tokenB is SOL), and retryCount > 1, check tokenA balance before proceeding
if (
retryCount > 1 &&
tokenA !== SOL_ADDRESS &&
tokenB === SOL_ADDRESS
) {
const balance = await getSplTokenBalance(tokenA);
console.log(`(Retry #${retryCount}) Current tokenA (${tokenA}) balance:`, balance, "Requested amount:", amount);
if (balance <= 0) {
console.log(`No balance for tokenA (${tokenA}) to sell. Aborting swap.`);
return "stop";
}
if (amount > balance) {
console.log(`Requested amount (${amount}) exceeds available balance (${balance}) for tokenA (${tokenA}). Adjusting amount to available balance.`);
amount = balance;
}
}
const quoteData = await getResponse(tokenA, tokenB, amount, process.env.SLIPPAGE_BPS || "5000", wallet);
if (!quoteData?.swapTransaction) {
throw new Error("Failed to get swap transaction data");
}
const txid = await executeTransaction(connection, quoteData.swapTransaction, wallet);
if (!txid) {
throw new Error("Transaction was not confirmed");
}
console.log(`--------------------------------------------------------\n
✌✌✌Swap successful! ${tokenA} for ${tokenB}`);
console.log(`https://solscan.io/tx/${txid}\n`);
return txid;
} catch (error) {
console.error(`Attempt ${retryCount + 1} failed:`, error.message);
retryCount++;
if (retryCount > MAX_RETRIES) {
console.error(`Transaction failed after ${MAX_RETRIES + 1} attempts.`);
throw error;
}
console.warn(`Retrying in ${RETRY_DELAY / 1000} seconds (${retryCount}/${MAX_RETRIES})...`);
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
}
}
} catch (error) {
console.error("Swap failed:", error.message);
return null;
}
return null;
};

View File

@@ -0,0 +1,445 @@
# 🚀 Solana Trading Bot v2.0
A high-performance, enterprise-grade Solana trading bot with advanced MEV protection, comprehensive risk management, and real-time monitoring capabilities.
## ✨ New Features in v2.0
- **🔒 Advanced Risk Management**: Daily loss limits, position limits, and automated risk scoring
- **📊 Real-time Dashboard**: Web-based monitoring interface with live charts and metrics
- **🔔 Multi-channel Notifications**: Telegram, Discord, and email notifications
- **📝 Structured Logging**: Winston-based logging with daily rotation and multiple levels
- **⚡ Performance Optimization**: Connection pooling, rate limiting, and retry mechanisms
- **🛡️ Enhanced Security**: IP whitelisting, rate limiting, and secure configuration management
- **📈 Advanced Analytics**: PnL tracking, win rate analysis, and risk metrics
- **🔄 Graceful Shutdown**: Safe position closure and cleanup on exit
## 🏗️ Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ gRPC Stream │───▶│ Transaction │───▶│ Trading Engine │
│ (Triton One) │ │ Parser │ │ (Main Bot) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Pool Detection │ │ Risk Manager │
│ (PumpFun, etc.) │ │ & Position Mgmt │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Jupiter API │ │ Notification │
│ Swap Execution │ │ Service │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Web Dashboard │ │ Logger & │
│ (Real-time UI) │ │ Analytics │
└─────────────────┘ └─────────────────┘
```
## 🚀 Quick Start
### 1. Prerequisites
- **Node.js 18+** (Latest LTS recommended)
- **Solana wallet** with SOL balance
- **Triton One gRPC** access
- **RPC endpoint** (Helius, QuickNode, etc.)
### 2. Installation
```bash
# Clone the repository
git clone <your-repo-url>
cd solana-trading-bot
# Install dependencies
npm install
# Copy environment template
cp env.template .env
```
### 3. Configuration
Edit the `.env` file with your configuration:
```bash
# Essential Configuration
PRIVATE_KEY=your_wallet_private_key
RPC_URL=https://your-rpc-endpoint.com
GRPC_ENDPOINT=https://your-grpc-endpoint.com
GRPCTOKEN=your_grpc_token
# Trading Parameters
SNIPERAMOUNT=0.1 # SOL amount per snipe
PROFIT_TARGET=2.0 # 2x profit target
STOP_LOSS=0.5 # 50% stop loss
MAX_HOLD_TIME=300000 # 5 minutes max hold
MIN_LIQUIDITY=10 # Minimum liquidity in SOL
MAX_POSITIONS=5 # Maximum concurrent positions
# Risk Management
MAX_DAILY_LOSS=1.0 # Maximum daily loss in SOL
MAX_SINGLE_LOSS=0.5 # Maximum single trade loss
TRADE_COOLDOWN=5000 # Cooldown between trades (ms)
# Dashboard (Optional)
ENABLE_DASHBOARD=true # Enable web dashboard
DASHBOARD_PORT=3000 # Dashboard port
# Notifications (Optional)
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
DISCORD_WEBHOOK_URL=your_webhook_url
```
### 4. Run the Bot
```bash
# Start the trading bot
npm start
# Or for development with auto-restart
npm run dev
# Start with dashboard enabled
ENABLE_DASHBOARD=true npm start
```
## 🌐 Web Dashboard
Access the real-time dashboard at `http://localhost:3000` (when enabled):
- **📊 Live Metrics**: Real-time PnL, positions, and risk levels
- **📈 Interactive Charts**: PnL trends and position distribution
- **🔍 Position Management**: View and manage active positions
- **📋 Trading History**: Complete trade history with analytics
- **⚙️ Configuration**: View and monitor bot settings
- **🚨 Emergency Controls**: Emergency position closure
## ⚙️ Configuration Options
### Trading Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `SNIPERAMOUNT` | 0.1 | SOL amount to use for each snipe |
| `PROFIT_TARGET` | 2.0 | Profit target multiplier (2x) |
| `STOP_LOSS` | 0.5 | Stop loss multiplier (50% loss) |
| `MAX_HOLD_TIME` | 300000 | Maximum time to hold position (5 min) |
| `MIN_LIQUIDITY` | 10 | Minimum liquidity required in SOL |
| `MAX_POSITIONS` | 5 | Maximum concurrent positions |
### Risk Management
| Parameter | Default | Description |
|-----------|---------|-------------|
| `MAX_DAILY_LOSS` | 1.0 | Maximum daily loss in SOL |
| `MAX_SINGLE_LOSS` | 0.5 | Maximum single trade loss in SOL |
| `TRADE_COOLDOWN` | 5000 | Cooldown between trades (ms) |
### Pool Filters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `ENABLE_PUMPFUN` | true | Enable PumpFun pool monitoring |
| `ENABLE_PUMPSWAP` | true | Enable PumpSwap pool monitoring |
| `ENABLE_RAYDIUM_LAUNCHLAB` | true | Enable Raydium LaunchLab monitoring |
| `ENABLE_RAYDIUM_CPMM` | true | Enable Raydium CPMM monitoring |
## 📊 Risk Management Features
### Automated Risk Controls
- **Daily Loss Limits**: Automatic trading halt when daily loss threshold reached
- **Position Limits**: Maximum concurrent position management
- **Trade Cooldowns**: Prevents rapid-fire trading
- **Risk Scoring**: Real-time risk level assessment (LOW/MEDIUM/HIGH)
### Position Management
- **Profit Targets**: Automatic exit at configured profit levels
- **Stop Losses**: Automatic exit at configured loss levels
- **Time-based Exits**: Maximum hold time enforcement
- **Emergency Closure**: Immediate closure of all positions
### Risk Metrics
- **Win Rate Analysis**: Track profitable vs. losing trades
- **PnL Tracking**: Real-time profit/loss monitoring
- **Risk Recommendations**: AI-powered trading suggestions
- **Performance Analytics**: Detailed trading performance metrics
## 🔔 Notification System
### Multi-channel Alerts
- **Telegram**: Real-time trading alerts and updates
- **Discord**: Webhook-based notifications with rich embeds
- **Email**: Detailed reports for important events
- **Dashboard**: Real-time web interface updates
### Notification Types
- **Trade Execution**: Buy/sell confirmations
- **Profit Targets**: When profit targets are reached
- **Stop Losses**: When stop losses are triggered
- **Risk Alerts**: High-risk situation notifications
- **Bot Status**: Startup, shutdown, and error notifications
## 📝 Logging & Monitoring
### Structured Logging
- **Multiple Levels**: Debug, Info, Warn, Error, Trade, Profit, Loss
- **Daily Rotation**: Automatic log file rotation and compression
- **Performance Tracking**: Operation timing and performance metrics
- **Error Context**: Detailed error information with stack traces
### Monitoring Features
- **Health Checks**: API endpoint health monitoring
- **Performance Metrics**: Response times and throughput
- **Error Tracking**: Comprehensive error logging and alerting
- **Audit Trail**: Complete trading activity audit log
## 🛡️ Security Features
### Access Control
- **Rate Limiting**: API request rate limiting
- **IP Whitelisting**: Configurable IP address restrictions
- **Authentication**: Secure API key management
- **Input Validation**: Comprehensive input sanitization
### Data Protection
- **Secure Configuration**: Environment variable management
- **Sensitive Data Masking**: Private key and API key protection
- **Audit Logging**: Complete access and action logging
- **Error Handling**: Secure error message handling
## 🔧 Advanced Configuration
### Performance Optimization
```bash
# Connection settings
CONNECTION_TIMEOUT=30000
MAX_RETRIES=3
RETRY_DELAY=1000
# Rate limiting
MAX_REQUESTS_PER_MINUTE=100
ENABLE_RATE_LIMITING=true
```
### Logging Configuration
```bash
# Log levels
LOG_LEVEL=info
DEBUG=false
LOG_TO_FILE=true
LOG_FILE_PATH=./logs/trading-bot.log
```
### MEV Protection
```bash
# MEV protection settings
ENABLE_MEV_PROTECTION=true
SWAP_METHOD=race # race, nozomi, 0slot, solana
PRIORITY_FEE=1000
```
## 📈 Trading Strategies
### Conservative Strategy
```bash
SNIPERAMOUNT=0.05
PROFIT_TARGET=1.5
STOP_LOSS=0.7
MAX_HOLD_TIME=600000
MIN_LIQUIDITY=20
MAX_DAILY_LOSS=0.5
```
### Aggressive Strategy
```bash
SNIPERAMOUNT=0.2
PROFIT_TARGET=3.0
STOP_LOSS=0.3
MAX_HOLD_TIME=180000
MIN_LIQUIDITY=5
MAX_DAILY_LOSS=2.0
```
### Balanced Strategy
```bash
SNIPERAMOUNT=0.1
PROFIT_TARGET=2.0
STOP_LOSS=0.5
MAX_HOLD_TIME=300000
MIN_LIQUIDITY=10
MAX_DAILY_LOSS=1.0
```
## 🚨 Emergency Procedures
### Emergency Stop
```bash
# Send SIGINT to gracefully shutdown
Ctrl+C
# Or use the dashboard emergency button
# Click "Emergency Close All" in the web interface
```
### Manual Position Closure
```bash
# Close specific position via dashboard
# Or modify the code to add CLI commands
```
## 🔍 Troubleshooting
### Common Issues
1. **gRPC Connection Failed**
- Verify `GRPC_ENDPOINT` and `GRPCTOKEN`
- Check network connectivity
- Ensure Triton One subscription is active
2. **Transaction Failures**
- Verify wallet has sufficient SOL
- Check RPC endpoint status
- Adjust slippage tolerance
3. **Dashboard Not Loading**
- Ensure `ENABLE_DASHBOARD=true`
- Check port availability
- Verify firewall settings
4. **Notification Failures**
- Verify API keys and tokens
- Check network connectivity
- Review notification service logs
### Debug Mode
Enable debug logging:
```bash
DEBUG=true
LOG_LEVEL=debug
```
### Log Files
Check log files for detailed information:
```bash
# Main logs
tail -f logs/trading-bot-*.log
# Error logs
tail -f logs/trading-bot-error-*.log
```
## 📚 API Reference
### Dashboard API Endpoints
- `GET /api/health` - Health check
- `GET /api/status` - Bot status and configuration
- `GET /api/risk` - Risk metrics and analytics
- `GET /api/positions` - Active positions
- `GET /api/history` - Trading history
- `GET /api/stats` - Daily statistics
- `POST /api/emergency/close-all` - Emergency position closure
### WebSocket Support
Real-time updates via WebSocket (planned for v2.1):
```javascript
// Future implementation
const ws = new WebSocket('ws://localhost:3000/api/ws');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Handle real-time updates
};
```
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
### Development Setup
```bash
# Install development dependencies
npm install
# Run linting
npm run lint
# Format code
npm run format
# Run tests
npm test
```
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
- [Triton One](https://triton.one/) for gRPC streaming
- [Jupiter](https://jup.ag/) for swap aggregation
- [Solana Labs](https://solana.com/) for the blockchain
- [Raydium](https://raydium.io/) for the DEX platform
- [Winston](https://github.com/winstonjs/winston) for logging
- [Express](https://expressjs.com/) for the web framework
## 📞 Support
For support and questions:
- Create an issue on GitHub
- Join our Discord community
- Check the documentation
- Review the troubleshooting guide
## 🔮 Roadmap
### v2.1 (Q2 2024)
- WebSocket real-time updates
- Advanced charting and analytics
- Mobile-responsive dashboard
- API rate limiting improvements
### v2.2 (Q3 2024)
- Machine learning price prediction
- Advanced order types
- Portfolio rebalancing
- Multi-wallet support
### v3.0 (Q4 2024)
- Cross-chain support
- Advanced MEV strategies
- Institutional features
- Cloud deployment options
---
**⚠️ Disclaimer: This software is for educational purposes only. Use at your own risk. The authors are not responsible for any financial losses. Cryptocurrency trading involves significant risk and may not be suitable for all investors.**

View File

@@ -0,0 +1,84 @@
# 🚀 Quick Setup Guide
## ⚡ 5-Minute Setup
### 1. Install Dependencies
```bash
npm install
```
### 2. Configure Environment
```bash
# Copy the template
cp env.template .env
# Edit .env with your settings
# At minimum, you need:
PRIVATE_KEY=your_wallet_private_key
RPC_URL=https://your-rpc-endpoint.com
GRPC_ENDPOINT=https://your-grpc-endpoint.com
GRPCTOKEN=your_grpc_token
```
### 3. Test Configuration
```bash
npm run test-config
```
### 4. Run Demo (Optional)
```bash
npm run demo
```
### 5. Start Trading
```bash
npm start
```
## 🔑 Required Services
- **Solana Wallet**: Phantom, Solflare, or private key
- **RPC Endpoint**: [Helius](https://helius.xyz/), [QuickNode](https://quicknode.com/), or [Alchemy](https://alchemy.com/)
- **gRPC Access**: [Triton One](https://triton.one/) subscription
## 💰 Minimum Requirements
- **Wallet Balance**: At least 1 SOL
- **Sniper Amount**: 0.1 SOL (configurable)
- **Network**: Stable internet connection
## 🚨 First Time Setup
1. **Test with small amounts first**
2. **Use a dedicated trading wallet**
3. **Monitor the bot initially**
4. **Adjust parameters based on performance**
## 📱 Quick Commands
| Command | Description |
|---------|-------------|
| `npm start` | Start the sniper bot |
| `npm run dev` | Start with auto-restart |
| `npm run test-config` | Verify configuration |
| `npm run demo` | Run demo mode |
| `start.bat` | Windows startup script |
| `start.sh` | Linux/Mac startup script |
## 🔧 Troubleshooting
### Common Issues:
- **"Private key invalid"** → Check key format (base58/base64/JSON)
- **"RPC connection failed"** → Verify RPC URL and network
- **"gRPC error"** → Check Triton One subscription
- **"No trades executing"** → Verify transaction filters
### Need Help?
1. Check the README.md
2. Run `npm run test-config`
3. Check console logs for errors
4. Verify all environment variables
---
**⚡ Ready to snipe? Run `npm start` and watch the magic happen!**

View File

@@ -0,0 +1,180 @@
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import fs from 'fs';
// Load environment variables
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Validate required environment variables
const requiredEnvVars = [
'PRIVATE_KEY',
'RPC_URL',
'GRPC_ENDPOINT',
'GRPCTOKEN'
];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
}
// Configuration object
export const config = {
// Essential Configuration
wallet: {
privateKey: process.env.PRIVATE_KEY,
rpcUrl: process.env.RPC_URL,
grpcEndpoint: process.env.GRPC_ENDPOINT,
grpcToken: process.env.GRPCTOKEN,
},
// Trading Parameters
trading: {
sniperAmount: parseFloat(process.env.SNIPERAMOUNT || '0.1'),
profitTarget: parseFloat(process.env.PROFIT_TARGET || '2.0'),
stopLoss: parseFloat(process.env.STOP_LOSS || '0.5'),
maxHoldTime: parseInt(process.env.MAX_HOLD_TIME || '300000'),
minLiquidity: parseFloat(process.env.MIN_LIQUIDITY || '10'),
maxPositions: parseInt(process.env.MAX_POSITIONS || '5'),
minTxAge: parseInt(process.env.MIN_TX_AGE || '1'),
},
// Pool Filters
pools: {
pumpFun: process.env.ENABLE_PUMPFUN === 'true',
pumpSwap: process.env.ENABLE_PUMPSWAP === 'true',
raydiumLaunchLab: process.env.ENABLE_RAYDIUM_LAUNCHLAB === 'true',
raydiumCpmm: process.env.ENABLE_RAYDIUM_CPMM === 'true',
},
// Swap Configuration
swap: {
method: process.env.SWAP_METHOD || 'solana',
slippageTolerance: parseFloat(process.env.SLIPPAGE_TOLERANCE || '1.0'),
priorityFee: parseInt(process.env.PRIORITY_FEE || '1000'),
},
// Risk Management
risk: {
maxDailyLoss: parseFloat(process.env.MAX_DAILY_LOSS || '1.0'),
maxSingleLoss: parseFloat(process.env.MAX_SINGLE_LOSS || '0.5'),
tradeCooldown: parseInt(process.env.TRADE_COOLDOWN || '5000'),
},
// Notifications
notifications: {
telegram: {
botToken: process.env.TELEGRAM_BOT_TOKEN,
chatId: process.env.TELEGRAM_CHAT_ID,
},
email: {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587'),
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
discord: {
webhookUrl: process.env.DISCORD_WEBHOOK_URL,
},
},
// Logging & Monitoring
logging: {
level: process.env.LOG_LEVEL || 'info',
debug: process.env.DEBUG === 'true',
logToFile: process.env.LOG_TO_FILE === 'true',
logFilePath: process.env.LOG_FILE_PATH || './logs/trading-bot.log',
},
// API Keys
api: {
helius: process.env.HELIUS_API_KEY,
jupiter: process.env.JUPITER_API_KEY,
},
// Advanced Settings
advanced: {
mevProtection: process.env.ENABLE_MEV_PROTECTION === 'true',
backtestMode: process.env.BACKTEST_MODE === 'true',
paperTrading: process.env.PAPER_TRADING === 'true',
databaseUrl: process.env.DATABASE_URL || 'sqlite://./trades.db',
},
// Performance
performance: {
maxRetries: parseInt(process.env.MAX_RETRIES || '3'),
retryDelay: parseInt(process.env.RETRY_DELAY || '1000'),
connectionTimeout: parseInt(process.env.CONNECTION_TIMEOUT || '30000'),
},
// Security
security: {
rateLimiting: process.env.ENABLE_RATE_LIMITING === 'true',
maxRequestsPerMinute: parseInt(process.env.MAX_REQUESTS_PER_MINUTE || '100'),
ipWhitelist: process.env.ENABLE_IP_WHITELIST === 'false',
allowedIps: process.env.ALLOWED_IPS ? process.env.ALLOWED_IPS.split(',') : ['127.0.0.1', '::1'],
},
};
// Create logs directory if it doesn't exist
const logsDir = dirname(config.logging.logFilePath);
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Validation functions
export const validateConfig = () => {
const errors = [];
if (config.trading.sniperAmount <= 0) {
errors.push('SNIPERAMOUNT must be greater than 0');
}
if (config.trading.profitTarget <= 1.0) {
errors.push('PROFIT_TARGET must be greater than 1.0');
}
if (config.trading.stopLoss >= 1.0) {
errors.push('STOP_LOSS must be less than 1.0');
}
if (config.trading.maxHoldTime <= 0) {
errors.push('MAX_HOLD_TIME must be greater than 0');
}
if (config.trading.minLiquidity <= 0) {
errors.push('MIN_LIQUIDITY must be greater than 0');
}
if (errors.length > 0) {
throw new Error(`Configuration validation failed:\n${errors.join('\n')}`);
}
return true;
};
// Get configuration for specific module
export const getModuleConfig = (moduleName) => {
switch (moduleName) {
case 'trading':
return config.trading;
case 'swap':
return config.swap;
case 'risk':
return config.risk;
case 'notifications':
return config.notifications;
case 'logging':
return config.logging;
default:
return config;
}
};
// Export default configuration
export default config;

View File

@@ -0,0 +1,427 @@
// Dashboard JavaScript
class TradingBotDashboard {
constructor() {
this.charts = {};
this.refreshInterval = null;
this.init();
}
init() {
this.setupEventListeners();
this.initializeCharts();
this.startAutoRefresh();
this.loadInitialData();
}
setupEventListeners() {
// Refresh button
document.getElementById('refreshBtn').addEventListener('click', () => {
this.refreshAllData();
});
// Emergency close button
document.getElementById('emergencyCloseBtn').addEventListener('click', () => {
this.showEmergencyModal();
});
// Emergency modal
document.getElementById('confirmEmergencyClose').addEventListener('click', () => {
this.emergencyCloseAll();
});
document.getElementById('cancelEmergencyClose').addEventListener('click', () => {
this.hideEmergencyModal();
});
// Close modal on outside click
document.getElementById('emergencyModal').addEventListener('click', (e) => {
if (e.target.id === 'emergencyModal') {
this.hideEmergencyModal();
}
});
}
initializeCharts() {
// PnL Chart
const pnlCtx = document.getElementById('pnlChart').getContext('2d');
this.charts.pnl = new Chart(pnlCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Daily PnL (SOL)',
data: [],
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#ffffff' }
}
},
scales: {
x: {
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(156, 163, 175, 0.2)' }
},
y: {
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(156, 163, 175, 0.2)' }
}
}
}
});
// Positions Chart
const positionsCtx = document.getElementById('positionsChart').getContext('2d');
this.charts.positions = new Chart(positionsCtx, {
type: 'doughnut',
data: {
labels: ['Active', 'Closed'],
datasets: [{
data: [0, 0],
backgroundColor: ['#3b82f6', '#6b7280'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#ffffff' }
}
}
}
});
}
async loadInitialData() {
try {
await Promise.all([
this.loadStatus(),
this.loadRiskMetrics(),
this.loadPositions(),
this.loadHistory(),
this.loadConfiguration()
]);
} catch (error) {
console.error('Error loading initial data:', error);
this.showError('Failed to load dashboard data');
}
}
async refreshAllData() {
const refreshBtn = document.getElementById('refreshBtn');
refreshBtn.classList.add('animate-spin');
try {
await this.loadInitialData();
this.showSuccess('Dashboard refreshed successfully');
} catch (error) {
console.error('Error refreshing data:', error);
this.showError('Failed to refresh dashboard');
} finally {
refreshBtn.classList.remove('animate-spin');
}
}
startAutoRefresh() {
// Refresh data every 30 seconds
this.refreshInterval = setInterval(() => {
this.loadStatus();
this.loadRiskMetrics();
this.loadPositions();
}, 30000);
}
async loadStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
// Update bot status
const statusElement = document.getElementById('botStatus');
statusElement.innerHTML = `
<i class="fas fa-circle mr-2"></i>${data.bot.status}
`;
statusElement.className = `font-bold status-${data.bot.status}`;
} catch (error) {
console.error('Error loading status:', error);
}
}
async loadRiskMetrics() {
try {
const response = await fetch('/api/risk');
const data = await response.json();
// Update quick stats
document.getElementById('activePositions').textContent = data.positionSummary.activePositions;
document.getElementById('dailyPnL').textContent = `${data.dailyStats.netPnL.toFixed(4)} SOL`;
document.getElementById('winRate').textContent = `${data.dailyStats.winRate}%`;
document.getElementById('riskLevel').textContent = data.riskLevel;
// Update PnL chart
this.updatePnLChart(data.dailyStats);
// Update positions chart
this.updatePositionsChart(data.positionSummary);
} catch (error) {
console.error('Error loading risk metrics:', error);
}
}
async loadPositions() {
try {
const response = await fetch('/api/positions');
const data = await response.json();
this.updatePositionsTable(data.positions);
} catch (error) {
console.error('Error loading positions:', error);
}
}
async loadHistory() {
try {
const response = await fetch('/api/history');
const data = await response.json();
this.updateHistoryTable(data.history);
} catch (error) {
console.error('Error loading history:', error);
}
}
async loadConfiguration() {
try {
const response = await fetch('/api/config');
const data = await response.json();
this.updateConfigurationGrid(data);
} catch (error) {
console.error('Error loading configuration:', error);
}
}
updatePnLChart(dailyStats) {
const chart = this.charts.pnl;
// Add current data point
const now = new Date();
const timeLabel = now.toLocaleTimeString();
chart.data.labels.push(timeLabel);
chart.data.datasets[0].data.push(dailyStats.netPnL);
// Keep only last 20 data points
if (chart.data.labels.length > 20) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update();
}
updatePositionsChart(positionSummary) {
const chart = this.charts.positions;
chart.data.datasets[0].data = [
positionSummary.activePositions,
positionSummary.totalPositions || 0
];
chart.update();
}
updatePositionsTable(positions) {
const tbody = document.getElementById('positionsTableBody');
if (positions.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">No active positions</td>
</tr>
`;
return;
}
tbody.innerHTML = positions.map(position => `
<tr class="border-b border-gray-700">
<td class="p-3">
<div class="flex items-center space-x-2">
<span class="font-mono text-sm">${position.mint.substring(0, 8)}...</span>
<button class="text-blue-400 hover:text-blue-300" onclick="copyToClipboard('${position.mint}')">
<i class="fas fa-copy"></i>
</button>
</div>
</td>
<td class="p-3">${position.entryPrice.toFixed(6)}</td>
<td class="p-3">${(position.currentPrice || position.entryPrice).toFixed(6)}</td>
<td class="p-3 ${position.pnl >= 0 ? 'text-green-400' : 'text-red-400'}">
${(position.pnl || 0).toFixed(4)} SOL
</td>
<td class="p-3">${this.formatDuration(position.holdTime)}</td>
<td class="p-3">
<button class="bg-red-600 hover:bg-red-700 px-3 py-1 rounded text-sm transition-colors"
onclick="closePosition('${position.mint}')">
Close
</button>
</td>
</tr>
`).join('');
}
updateHistoryTable(history) {
const tbody = document.getElementById('historyTableBody');
if (history.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">No trading history</td>
</tr>
`;
return;
}
tbody.innerHTML = history.slice(-10).reverse().map(trade => `
<tr class="border-b border-gray-700">
<td class="p-3">${new Date(trade.timestamp).toLocaleString()}</td>
<td class="p-3">
<span class="px-2 py-1 rounded text-xs ${trade.type === 'buy' ? 'bg-green-600' : 'bg-red-600'}">
${trade.type.toUpperCase()}
</span>
</td>
<td class="p-3">
<span class="font-mono text-sm">${trade.tokenMint.substring(0, 8)}...</span>
</td>
<td class="p-3">${trade.amount.toFixed(4)}</td>
<td class="p-3">${trade.price.toFixed(6)}</td>
<td class="p-3 ${trade.pnl >= 0 ? 'text-green-400' : 'text-red-400'}">
${trade.pnl ? trade.pnl.toFixed(4) : '-'}
</td>
</tr>
`).join('');
}
updateConfigurationGrid(config) {
const grid = document.getElementById('configGrid');
const configItems = [
{ label: 'Sniper Amount', value: `${config.trading.sniperAmount} SOL` },
{ label: 'Profit Target', value: `${config.trading.profitTarget}x` },
{ label: 'Stop Loss', value: `${config.trading.stopLoss}x` },
{ label: 'Max Hold Time', value: `${config.trading.maxHoldTime / 1000}s` },
{ label: 'Max Positions', value: config.trading.maxPositions },
{ label: 'Max Daily Loss', value: `${config.risk.maxDailyLoss} SOL` },
{ label: 'Swap Method', value: config.swap.method },
{ label: 'Slippage', value: `${config.swap.slippageTolerance}%` },
{ label: 'Priority Fee', value: `${config.swap.priorityFee} lamports` }
];
grid.innerHTML = configItems.map(item => `
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-sm text-gray-400">${item.label}</div>
<div class="text-lg font-semibold">${item.value}</div>
</div>
`).join('');
}
showEmergencyModal() {
document.getElementById('emergencyModal').classList.remove('hidden');
document.getElementById('emergencyModal').classList.add('flex');
}
hideEmergencyModal() {
document.getElementById('emergencyModal').classList.add('hidden');
document.getElementById('emergencyModal').classList.remove('flex');
}
async emergencyCloseAll() {
try {
this.hideEmergencyModal();
const response = await fetch('/api/emergency/close-all', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason: 'dashboard_emergency' })
});
const result = await response.json();
if (result.success) {
this.showSuccess('Emergency closure initiated');
this.refreshAllData();
} else {
this.showError('Failed to initiate emergency closure');
}
} catch (error) {
console.error('Error during emergency closure:', error);
this.showError('Error during emergency closure');
}
}
formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}
showSuccess(message) {
this.showNotification(message, 'success');
}
showError(message) {
this.showNotification(message, 'error');
}
showNotification(message, type) {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 ${
type === 'success' ? 'bg-green-600' : 'bg-red-600'
}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
}
// Global functions for table actions
window.copyToClipboard = function(text) {
navigator.clipboard.writeText(text).then(() => {
// Could show a toast notification here
});
};
window.closePosition = function(mint) {
// Implement individual position closure
console.log('Closing position for:', mint);
};
// Initialize dashboard when page loads
document.addEventListener('DOMContentLoaded', () => {
window.dashboard = new TradingBotDashboard();
});

View File

@@ -0,0 +1,192 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solana Trading Bot Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.status-running { color: #10b981; }
.status-stopped { color: #ef4444; }
.status-warning { color: #f59e0b; }
.refresh-btn:hover { transform: rotate(180deg); transition: transform 0.3s ease; }
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<!-- Header -->
<header class="gradient-bg p-6 shadow-lg">
<div class="container mx-auto flex justify-between items-center">
<div class="flex items-center space-x-4">
<i class="fas fa-robot text-3xl"></i>
<div>
<h1 class="text-2xl font-bold">Solana Trading Bot</h1>
<p class="text-blue-100">Advanced MEV Protection & Risk Management</p>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="text-right">
<div class="text-sm text-blue-100">Status</div>
<div id="botStatus" class="font-bold status-running">
<i class="fas fa-circle mr-2"></i>Running
</div>
</div>
<button id="refreshBtn" class="refresh-btn bg-white bg-opacity-20 p-3 rounded-full hover:bg-opacity-30 transition-all">
<i class="fas fa-sync-alt text-white"></i>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto p-6 space-y-6">
<!-- Quick Stats -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Active Positions</p>
<p id="activePositions" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-chart-line text-2xl text-blue-400"></i>
</div>
</div>
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Daily PnL</p>
<p id="dailyPnL" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-coins text-2xl text-green-400"></i>
</div>
</div>
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Win Rate</p>
<p id="winRate" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-trophy text-2xl text-yellow-400"></i>
</div>
</div>
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Risk Level</p>
<p id="riskLevel" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-shield-alt text-2xl text-red-400"></i>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- PnL Chart -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Daily PnL Trend</h3>
<canvas id="pnlChart" height="200"></canvas>
</div>
<!-- Positions Chart -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Active Positions</h3>
<canvas id="positionsChart" height="200"></canvas>
</div>
</div>
<!-- Active Positions Table -->
<div class="card rounded-lg p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Active Positions</h3>
<button id="emergencyCloseBtn" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-exclamation-triangle mr-2"></i>Emergency Close All
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left p-3">Token</th>
<th class="text-left p-3">Entry Price</th>
<th class="text-left p-3">Current Price</th>
<th class="text-left p-3">PnL</th>
<th class="text-left p-3">Hold Time</th>
<th class="text-left p-3">Actions</th>
</tr>
</thead>
<tbody id="positionsTableBody">
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">Loading positions...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Trading History -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Recent Trades</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left p-3">Time</th>
<th class="text-left p-3">Type</th>
<th class="text-left p-3">Token</th>
<th class="text-left p-3">Amount</th>
<th class="text-left p-3">Price</th>
<th class="text-left p-3">PnL</th>
</tr>
</thead>
<tbody id="historyTableBody">
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">Loading history...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Configuration -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Bot Configuration</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="configGrid">
<!-- Configuration will be populated here -->
</div>
</div>
</main>
<!-- Emergency Close Modal -->
<div id="emergencyModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-gray-800 p-6 rounded-lg max-w-md w-full mx-4">
<h3 class="text-xl font-bold text-red-400 mb-4">
<i class="fas fa-exclamation-triangle mr-2"></i>Emergency Close All
</h3>
<p class="text-gray-300 mb-4">This will immediately close all active positions. Are you sure?</p>
<div class="flex space-x-3">
<button id="confirmEmergencyClose" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg transition-colors">
Yes, Close All
</button>
<button id="cancelEmergencyClose" class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg transition-colors">
Cancel
</button>
</div>
</div>
</div>
<script src="dashboard.js"></script>
</body>
</html>

View File

@@ -0,0 +1,237 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import { config } from '../config.js';
import logger from '../utils/logger.js';
import riskManager from '../services/riskManager.js';
import notificationService from '../services/notifications.js';
const app = express();
const PORT = process.env.DASHBOARD_PORT || 3000;
// Rate limiting
const rateLimiter = new RateLimiterMemory({
keyGenerator: (req) => req.ip,
points: config.security.maxRequestsPerMinute,
duration: 60,
});
// Middleware
app.use(helmet());
app.use(cors());
app.use(compression());
app.use(express.json());
app.use(express.static('dashboard/public'));
// Rate limiting middleware
app.use(async (req, res, next) => {
try {
await rateLimiter.consume(req.ip);
next();
} catch (rejRes) {
res.status(429).json({
error: 'Too many requests',
retryAfter: Math.round(rejRes.msBeforeNext / 1000),
});
}
});
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
});
});
// Get bot status and configuration
app.get('/api/status', (req, res) => {
try {
const status = {
bot: {
status: 'running',
uptime: process.uptime(),
version: '2.0.0',
timestamp: new Date().toISOString(),
},
config: {
trading: config.trading,
risk: config.risk,
pools: config.pools,
},
};
res.json(status);
} catch (error) {
logger.error('Error getting bot status', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get risk metrics
app.get('/api/risk', (req, res) => {
try {
const riskMetrics = riskManager.getRiskMetrics();
res.json(riskMetrics);
} catch (error) {
logger.error('Error getting risk metrics', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get active positions
app.get('/api/positions', (req, res) => {
try {
const positions = riskManager.getActivePositions();
const summary = riskManager.getPositionSummary();
res.json({
positions,
summary,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error getting positions', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get trading history
app.get('/api/history', (req, res) => {
try {
const history = riskManager.tradeHistory || [];
const dailyStats = riskManager.getDailyStats();
res.json({
history,
dailyStats,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error getting trading history', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get daily statistics
app.get('/api/stats', (req, res) => {
try {
const dailyStats = riskManager.getDailyStats();
res.json(dailyStats);
} catch (error) {
logger.error('Error getting daily stats', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Emergency actions
app.post('/api/emergency/close-all', async (req, res) => {
try {
const { reason } = req.body;
const results = await riskManager.emergencyCloseAll(reason || 'dashboard_request');
res.json({
success: true,
message: 'Emergency closure initiated',
results,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error initiating emergency closure', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Send test notification
app.post('/api/notifications/test', async (req, res) => {
try {
const { message, type } = req.body;
await notificationService.sendNotification(
message || 'Test notification from dashboard',
type || 'info',
{ source: 'dashboard' }
);
res.json({
success: true,
message: 'Test notification sent',
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error sending test notification', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Update configuration (read-only for now, could be extended)
app.get('/api/config', (req, res) => {
try {
// Return safe configuration (no sensitive data)
const safeConfig = {
trading: config.trading,
risk: config.risk,
pools: config.pools,
swap: config.swap,
logging: config.logging,
};
res.json(safeConfig);
} catch (error) {
logger.error('Error getting configuration', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// WebSocket endpoint for real-time updates (placeholder)
app.get('/api/ws', (req, res) => {
res.json({
message: 'WebSocket endpoint - implement with Socket.IO for real-time updates',
timestamp: new Date().toISOString(),
});
});
// Error handling middleware
app.use((error, req, res, next) => {
logger.error('Dashboard error', error);
res.status(500).json({
error: 'Internal server error',
message: error.message,
timestamp: new Date().toISOString(),
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
error: 'Endpoint not found',
timestamp: new Date().toISOString(),
});
});
// Start server
const server = app.listen(PORT, () => {
logger.info(`Dashboard server started on port ${PORT}`);
logger.info(`Dashboard available at: http://localhost:${PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down dashboard server');
server.close(() => {
logger.info('Dashboard server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down dashboard server');
server.close(() => {
logger.info('Dashboard server closed');
process.exit(0);
});
});
export default app;

View File

@@ -0,0 +1,157 @@
# =============================================================================
# SOLANA TRADING BOT CONFIGURATION
# =============================================================================
# =============================================================================
# ESSENTIAL CONFIGURATION
# =============================================================================
# Your wallet private key (base58 encoded)
PRIVATE_KEY=your_wallet_private_key_here
# Solana RPC endpoint (Helius, QuickNode, Alchemy, etc.)
RPC_URL=https://your-rpc-endpoint.com
# Triton One gRPC endpoint for transaction streaming
GRPC_ENDPOINT=https://your-grpc-endpoint.com
# Triton One authentication token
GRPCTOKEN=your_grpc_token_here
# =============================================================================
# TRADING PARAMETERS
# =============================================================================
# Amount of SOL to use for each snipe
SNIPERAMOUNT=0.1
# Profit target multiplier (e.g., 2.0 = 2x profit)
PROFIT_TARGET=2.0
# Stop loss multiplier (e.g., 0.5 = 50% loss)
STOP_LOSS=0.5
# Maximum time to hold position in milliseconds (5 minutes = 300000)
MAX_HOLD_TIME=300000
# Minimum liquidity required in SOL
MIN_LIQUIDITY=10
# Maximum concurrent positions
MAX_POSITIONS=5
# Minimum transaction age in seconds
MIN_TX_AGE=1
# =============================================================================
# POOL FILTERS
# =============================================================================
# Enable/disable specific pool types
ENABLE_PUMPFUN=true
ENABLE_PUMPSWAP=true
ENABLE_RAYDIUM_LAUNCHLAB=true
ENABLE_RAYDIUM_CPMM=true
# =============================================================================
# SWAP CONFIGURATION
# =============================================================================
# Swap method: solana, race, nozomi, 0slot
SWAP_METHOD=solana
# Slippage tolerance percentage
SLIPPAGE_TOLERANCE=1.0
# Priority fee in lamports
PRIORITY_FEE=1000
# =============================================================================
# RISK MANAGEMENT
# =============================================================================
# Maximum daily loss in SOL
MAX_DAILY_LOSS=1.0
# Maximum single trade loss in SOL
MAX_SINGLE_LOSS=0.5
# Cooldown period between trades in milliseconds
TRADE_COOLDOWN=5000
# =============================================================================
# NOTIFICATIONS
# =============================================================================
# Telegram bot configuration
TELEGRAM_BOT_TOKEN=your_bot_token_here
TELEGRAM_CHAT_ID=your_chat_id_here
# Email notifications
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password_here
# Discord webhook
DISCORD_WEBHOOK_URL=your_webhook_url_here
# =============================================================================
# LOGGING & MONITORING
# =============================================================================
# Log level: debug, info, warn, error
LOG_LEVEL=info
# Enable debug mode
DEBUG=false
# Log to file
LOG_TO_FILE=true
# Log file path
LOG_FILE_PATH=./logs/trading-bot.log
# =============================================================================
# API KEYS (Optional)
# =============================================================================
# Helius API key for enhanced RPC
HELIUS_API_KEY=your_helius_key_here
# Jupiter API key for better swap rates
JUPITER_API_KEY=your_jupiter_key_here
# =============================================================================
# ADVANCED SETTINGS
# =============================================================================
# Enable MEV protection
ENABLE_MEV_PROTECTION=true
# Enable backtesting mode
BACKTEST_MODE=false
# Enable paper trading
PAPER_TRADING=false
# Database connection (for position tracking)
DATABASE_URL=sqlite://./trades.db
# =============================================================================
# PERFORMANCE OPTIMIZATION
# =============================================================================
# Transaction retry attempts
MAX_RETRIES=3
# Retry delay in milliseconds
RETRY_DELAY=1000
# Connection timeout in milliseconds
CONNECTION_TIMEOUT=30000
# =============================================================================
# SECURITY
# =============================================================================
# Enable rate limiting
ENABLE_RATE_LIMITING=true
# Maximum requests per minute
MAX_REQUESTS_PER_MINUTE=100
# Enable IP whitelist
ENABLE_IP_WHITELIST=false
# Allowed IP addresses (comma-separated)
ALLOWED_IPS=127.0.0.1,::1

View File

@@ -0,0 +1,182 @@
import { Connection, PublicKey, LAMPORTS_PER_SOL, Keypair } from "@solana/web3.js";
import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token";
import chalk from "chalk";
import dotenv from "dotenv";
import bs58 from "bs58";
dotenv.config();
import { swap } from "./swap.js";
// import { buy_pumpfun, buy_pumpswap, sell_pumpfun, sell_pumpswap } from "./swapsdk_0slot.js";
// import { buy_raydium_CPMM, buy_raydium_launchpad, sell_raydium_CPMM, sell_raydium_launchpad } from "./swapRaydium.js";
const RPC_URL = process.env.RPC_URL;
const connection = new Connection(RPC_URL, "confirmed");
//============functions============//
export const token_buy = async (mint, sol_amount, pool_status, context) => {
if (!mint) {
throw new Error("mint is required and was not provided.");
}
const currentUTC = new Date();
const txid = await swap("BUY", mint, sol_amount * LAMPORTS_PER_SOL);
// let txid = "";
console.log(chalk.green(`🟢BUY tokenAmount:::${sol_amount} pool_status: ${pool_status} `));
//============off chain sign ultra fast============//
// if (pool_status == "pumpfun") {
// txid = await buy_pumpfun(mint, sol_amount * LAMPORTS_PER_SOL, context);//off chain sign ultra fast
// } else if (pool_status == "pumpswap") {
// txid = await buy_pumpswap(mint, sol_amount * LAMPORTS_PER_SOL, context.pool);
// } else if (pool_status == "raydium_launchlab") {
// txid = await buy_raydium_launchpad(mint, sol_amount * LAMPORTS_PER_SOL, context);
// } else {
// txid = await buy_raydium_CPMM(mint, sol_amount * LAMPORTS_PER_SOL);
// }
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ Total BUY time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
return txid;
};
export const token_sell = async (mint, tokenAmount, pool_status, isFull, context) => {
try {
if (!mint) {
throw new Error("mint is required and was not provided.");
}
console.log(chalk.red(`🔴SELL tokenAmount:::${tokenAmount} pool_status: ${pool_status} `));
const currentUTC = new Date();
//============off chain sign ultra fast============//
// let txid = "";
// if (pool_status == "pumpfun") {
// txid = await sell_pumpfun(mint, tokenAmount, isFull, context);
// } else if (pool_status == "pumpswap") {
// txid = await sell_pumpswap(mint, tokenAmount, context.pool, isFull);
// } else if (pool_status == "raydium_launchlab") {
// txid = await sell_raydium_launchpad(mint, tokenAmount, isFull);
// } else {
// txid = await sell_raydium_CPMM(mint, tokenAmount, isFull);
// }
const txid = await swap("SELL", mint, tokenAmount);
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ Total SELL time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
if (txid === "stop") {
console.log(chalk.red(`[${new Date().toISOString()}] 🛑 Swap returned "stop" - no balance for ${mint}`));
return "stop";
}
if (txid) {
console.log(chalk.green(`Successfully sold ${tokenAmount} tokens : https://solscan.io/tx/${txid}`));
return txid;
}
return null;
} catch (error) {
console.error("Error in token_sell:", error.message);
if (error.response?.data) {
console.error("API Error details:", error.response.data);
}
return null;
}
};
export const getSplTokenBalance = async (mint) => {
if (!mint) {
console.log("🔄 Token balance error: Mint address is undefined or null.");
throw new Error("Mint address is undefined or null.");
}
let mintPubkey;
try {
mintPubkey = new PublicKey(mint);
} catch (err) {
console.log("🔄 Token balance error: Invalid mint address provided.");
throw err;
}
// const publicKey = getPublicKeyFromPrivateKey();
const publicKey = getPublicKeyFromPrivateKey();
const ata = await getAssociatedTokenAddress(mintPubkey, new PublicKey(publicKey));
let account;
try {
account = await getAccount(connection, ata);
} catch (err) {
// Handle TokenAccountNotFoundError gracefully
if (
err.name === "TokenAccountNotFoundError" ||
(err.message && (
err.message.includes("Failed to find account") ||
err.message.includes("Account does not exist") ||
err.message.includes("could not find account")
))
) {
// No account found, treat as zero balance
console.log("🔄 Token balance: Account not found, returning 0.");
return null;
}
// If the error is related to an invalid mint, log and throw error
if (err.message && err.message.includes("Invalid param")) {
console.log("🔄 Token balance error: Invalid mint param.");
throw err;
}
// Other errors
console.log("🔄 Token balance error:", err.message || err);
throw err;
}
return Number(account.amount); // Convert BigInt to Number
};
export const checkWalletBalance = async () => {
try {
const pubkey = getPublicKeyFromPrivateKey();
const balanceLamports = await connection.getBalance(pubkey);
const balance = balanceLamports / LAMPORTS_PER_SOL;
return { balance };
} catch (err) {
console.error("Error checking wallet balance:", err.message || err);
throw err;
}
};
export const getKeypairFromPrivateKey = (privateKeyString) => {
try {
// Try base58 first
try {
const decoded = bs58.decode(privateKeyString);
return Keypair.fromSecretKey(decoded);
} catch (e) {
// Not base58, try base64
try {
const decoded = Buffer.from(privateKeyString, 'base64');
return Keypair.fromSecretKey(decoded);
} catch (e2) {
// Not base64, try JSON array
try {
const arr = JSON.parse(privateKeyString);
const uint8arr = new Uint8Array(arr);
return Keypair.fromSecretKey(uint8arr);
} catch (e3) {
throw new Error('Invalid private key format. Supported formats: base58, base64, or JSON array');
}
}
}
} catch (err) {
throw new Error('Failed to decode private key: ' + err.message);
}
};
export const getPublicKeyFromPrivateKey = () => {
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error("Private key is required and was not provided.");
}
const keypair = getKeypairFromPrivateKey(privateKey);
return keypair.publicKey.toString();
};

View File

@@ -0,0 +1,204 @@
import "dotenv/config";
import Client from "@triton-one/yellowstone-grpc";
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";
import { decodeInstruction } from '@solana/spl-token';
import { Connection, Keypair } from '@solana/web3.js';
import chalk from "chalk";
import { tOutPut } from "./parsingtransaction.js";
import { handleNewTokenLaunch } from "./main.js";
import dotenv from 'dotenv'
dotenv.config();
const GRPCTOKEN=process.env.GRPCTOKEN
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT
// Pre-define constants
const SOLANA_TOKEN = "So11111111111111111111111111111111111111112";
const RAYDIUM_FEE = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"//"7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5";
const Raydium_launchpad_authority = "WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh"
// Create default client
const defaultClient = new Client(
GRPC_ENDPOINT,
GRPCTOKEN
);
export let isNewLaunchRunning = true;
export const stopNewLaunch = () => {
isNewLaunchRunning = false;
console.log(chalk.red("New launch monitoring stopped"));
};
// Default request args
const defaultArgs = {
accounts: {},
slots: {},
transactions: {
pumpfun: {
vote: false,
failed: false,
signature: undefined,
accountInclude: [RAYDIUM_FEE],
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.PROCESSED,
};
// This function checks for MintTo instructions by comparing with log messages
async function checkMintTo(data) {
const tx = data.transaction?.transaction;
const meta = data.transaction;
if (!tx || !meta?.transaction?.meta?.logMessages) return;
// Find if MintTo is present in log messages
const mintToLog = meta.transaction.meta.logMessages.find((log) =>
typeof log === "string" && log.toLowerCase().includes("instruction: mintto")
);
if (mintToLog) {
console.log("🩸🩸🩸🩸🩸 MintTo found in logs!");
return true
} else {
// No MintTo found in logs
return false
}
}
async function handleStream(client = defaultClient, args = defaultArgs) {
const stream = await client.subscribe(args);
const streamClosed = new Promise((resolve, reject) => {
stream.on("error", (error) => {
console.error("Stream Error:", error);
reject(error);
stream.end();
});
stream.on("end", resolve);
stream.on("close", resolve);
});
stream.on("data", async (data) => {
// Return early if monitoring is disabled
if (!isNewLaunchRunning) {
stream.end();
return;
}
try {
// console.log(chalk.green("start__________new token streaming_________"));
if (!data?.transaction?.transaction) {
return null;
}
const mintTo = await checkMintTo(data)
if(!mintTo){
return null
}
console.log(`[${new Date().toISOString()}] 🩸🩸🩸🩸🩸 MintTo found in logs!`);
const preTokenBalances = data?.transaction?.transaction?.meta?.preTokenBalances;
const postTokenBalances = data?.transaction?.transaction?.meta?.postTokenBalances;
if (!preTokenBalances || !postTokenBalances) {
console.log("Token balances not found in transaction data");
return null;
}
let pre_sol = 0;
let post_sol = 0;
let pre_token = 0;
let post_token = 0;
let token_mint = "";
let token_owner = "";
for (const balance of postTokenBalances) {
if (balance.owner !== Raydium_launchpad_authority) {
if (balance.mint !== SOLANA_TOKEN) {
post_token = balance.uiTokenAmount.uiAmount || 0;
token_mint = balance.mint;
token_owner = balance.owner;
}
} else {
post_sol = balance.uiTokenAmount.uiAmount || 0;
}
}
for (const balance of preTokenBalances) {
if (balance.owner !== Raydium_launchpad_authority) {
if (balance.mint !== SOLANA_TOKEN) {
pre_token = balance.uiTokenAmount.uiAmount || 0;
}
}
}
const solChanges = post_sol-pre_sol;
const tokenChanges = post_token-pre_token;
console.log(chalk.bgBlue.bold(`🪙 Token Mint:`), chalk.white(token_mint));
console.log(chalk.bgMagenta.bold(`👤 Token Owner:`), chalk.white(token_owner));
console.log(chalk.bgYellow.bold(`💸 SOL Balance Change:`), chalk.yellow(`${solChanges > 0 ? "+" : ""}${solChanges}`));
console.log(chalk.bgCyan.bold(`🔄 Token Balance Change:`), chalk.cyan(`${tokenChanges > 0 ? "+" : ""}${tokenChanges}`));
if (solChanges> 0.1) {
console.log(chalk.bgGreen("Found large SOL transfer:", solChanges));
// Parse transaction data to get pool information
const parsedData = await tOutPut(data);
if (parsedData) {
console.log(chalk.cyan(`Pool status: Raydium launchpad`));
// Call the main bot logic to handle the new token launch
await handleNewTokenLaunch(token_mint, parsedData.pool_status, parsedData.context);
} else {
console.log(chalk.yellow("Failed to parse transaction data"));
}
return token_mint;
}
return null;
} catch (error) {
console.error("Error processing transaction data:", error);
return null;
}
});
try {
await stream.write(args);
} catch (error) {
console.error("Subscription request failed:", error);
throw error;
}
await streamClosed;
}
export async function newlunched_subscribeCommand(client = defaultClient, args = defaultArgs) {
// Set monitoring flag to true when starting
isNewLaunchRunning = true;
console.log(chalk.green("New launch monitoring started"));
while (isNewLaunchRunning) {
try {
await handleStream(client, args);
} catch (error) {
console.error("Stream error, restarting in 1 second...", error);
// Only wait and retry if monitoring is still enabled
if (isNewLaunchRunning) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
console.log("New launch monitoring stopped");
}
// Export client and args for external use
export { defaultClient, defaultArgs };
// Remove the auto-execution to prevent conflicts
// newlunched_subscribeCommand()

View File

@@ -0,0 +1,154 @@
import { pump_geyser } from "./main.js";
import { config, validateConfig } from "./config.js";
import logger from "./utils/logger.js";
import notificationService from "./services/notifications.js";
import riskManager from "./services/riskManager.js";
// Start dashboard server if enabled
let dashboardServer = null;
if (process.env.ENABLE_DASHBOARD === 'true') {
try {
const dashboardApp = await import("./dashboard/server.js");
dashboardServer = dashboardApp.default;
logger.info("Dashboard server started");
} catch (error) {
logger.warn("Failed to start dashboard server", error);
}
}
// Validate configuration before starting
try {
validateConfig();
logger.info("Configuration validated successfully");
} catch (error) {
logger.error("Configuration validation failed", error);
process.exit(1);
}
// Check wallet balance
const checkWalletBalance = async () => {
try {
const { getBalance } = await import("./swap.js");
const balance = await getBalance();
if (balance < 1) {
logger.error("Wallet balance is below 1 SOL. Current balance:", balance, "SOL");
await notificationService.sendNotification(
`❌ Insufficient wallet balance: ${balance} SOL`,
'error',
{ balance, required: 1 }
);
process.exit(1);
}
logger.info(`Wallet balance: ${balance} SOL`);
await notificationService.sendNotification(
`✅ Bot startup successful. Wallet balance: ${balance} SOL`,
'success',
{ balance }
);
return balance;
} catch (err) {
logger.error("Error checking wallet balance", err);
await notificationService.sendNotification(
`❌ Failed to check wallet balance: ${err.message}`,
'error',
{ error: err.message }
);
process.exit(1);
}
};
// Main startup function
const main = async () => {
try {
logger.info("🚀 Starting Solana Trading Bot...");
// Check wallet balance
await checkWalletBalance();
// Start the main bot
await pump_geyser();
// Send startup notification
await notificationService.notifyBotStatus("Started", {
timestamp: new Date().toISOString(),
config: {
trading: config.trading,
risk: config.risk,
pools: config.pools,
},
});
logger.info("✅ Bot startup completed successfully");
} catch (error) {
logger.error("❌ Bot startup failed", error);
await notificationService.notifyError(error, "Bot Startup");
process.exit(1);
}
};
// Handle process signals
process.on('SIGINT', async () => {
logger.info("🛑 SIGINT received, shutting down...");
await handleShutdown("SIGINT");
});
process.on('SIGTERM', async () => {
logger.info("🛑 SIGTERM received, shutting down...");
await handleShutdown("SIGTERM");
});
process.on('uncaughtException', async (error) => {
logger.error("Uncaught exception", error);
await notificationService.notifyError(error, "Uncaught Exception");
await handleShutdown("Uncaught Exception");
});
process.on('unhandledRejection', async (reason, promise) => {
logger.error("Unhandled rejection", { reason, promise });
await notificationService.notifyError(new Error(reason), "Unhandled Rejection");
});
// Graceful shutdown handler
const handleShutdown = async (reason) => {
try {
logger.info("Initiating graceful shutdown...");
// Get final statistics
const finalStats = riskManager.getDailyStats();
const riskMetrics = riskManager.getRiskMetrics();
// Send shutdown notification
await notificationService.notifyBotStatus("Shutdown", {
reason,
finalStats,
riskMetrics,
timestamp: new Date().toISOString(),
});
// Close dashboard server if running
if (dashboardServer) {
logger.info("Closing dashboard server...");
// Note: In a real implementation, you'd want to properly close the Express server
}
logger.info("Graceful shutdown completed");
process.exit(0);
} catch (error) {
logger.error("Error during shutdown", error);
process.exit(1);
}
};
// Start the bot
main().catch(async (error) => {
logger.error("Fatal error in main function", error);
await notificationService.notifyError(error, "Main Function");
process.exit(1);
});

View File

@@ -0,0 +1,15 @@
{
"keep": {
"days": true,
"amount": 14
},
"auditLog": "logs\\.6dee68a8e2d6fa545f978ea2a4cd18a2469f8eef-audit.json",
"files": [
{
"date": 1755198754557,
"name": "logs\\trading-bot-2025-08-14.log",
"hash": "2ce46b1fe2271a1eaab915e581771fb25f76310f7424154bac8bcfe21bdb8a8a"
}
],
"hashType": "sha256"
}

View File

@@ -0,0 +1,15 @@
{
"keep": {
"days": true,
"amount": 30
},
"auditLog": "logs\\.a961c1e9547c0e8ccd2f7b24907043cafbaced36-audit.json",
"files": [
{
"date": 1755198754559,
"name": "logs\\trading-bot-error-2025-08-14.log",
"hash": "da7d7bef5942bf1d56fce13f254a63f28454652870987c7bfd3303f17eef1ad2"
}
],
"hashType": "sha256"
}

View File

@@ -0,0 +1,20 @@
{"level":"info","message":"Configuration validated successfully","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:39.094Z"}
{"level":"info","message":"🚀 Starting Solana Trading Bot...","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:39.096Z"}
{"level":"info","message":"Wallet balance: 0.004996182 SOL","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.970Z"}
{"level":"info","message":"🚀 Starting Solana Raydium Sniper Bot...","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.973Z"}
{"level":"info","message":"🔑 Wallet Public Key: DopsYMstRqBm3SSsn1vY6sjhFPjXbtQeTPoXWsoopodF","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.973Z"}
{"level":"info","message":"💰 Sniper Amount: 0.00001 SOL","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.974Z"}
{"level":"info","message":"🎯 Profit Target: 2x","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.974Z"}
{"level":"info","message":"🛑 Stop Loss: 0.5x","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.974Z"}
{"level":"info","message":"⏱️ Max Hold Time: 300s","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.975Z"}
{"level":"info","message":"📊 Max Positions: 5","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.975Z"}
{"context":null,"level":"info","message":"New token launch detected: 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","poolStatus":"raydium","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:57.819Z"}
{"level":"info","message":"Executing sniper trade for 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:57.819Z"}
{"level":"error","message":"Failed to execute sniper trade for 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:02.572Z"}
{"context":{"amountIn":"309346784","amountOut":"1416041927389","platformFee":"3093468","poolState":"H6Cnafust6WPiduKfuuYoaYLLVHJankcuvRYjVdBrW8n","poolStatus":{"normal":{}},"protocolFee":"773367","realBaseAfter":"687438457793402","realBaseBefore":"686022415866013","realQuoteAfter":"53486586492","realQuoteBefore":"53181106543","shareFee":"0","totalBaseSell":"793100000000000","tradeDirection":0,"virtualBase":"1073025605596382","virtualQuote":"30000852951"},"level":"info","message":"New token launch detected: EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","poolStatus":"raydium_launchlab","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:28.663Z"}
{"level":"info","message":"Executing sniper trade for EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:28.663Z"}
{"level":"error","message":"Failed to execute sniper trade for EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.143Z"}
{"context":null,"level":"info","message":"New token launch detected: 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","poolStatus":"raydium","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.214Z"}
{"level":"info","message":"Executing sniper trade for 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.214Z"}
{"level":"error","message":"Failed to execute sniper trade for 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:35.375Z"}
{"level":"info","message":"🛑 SIGINT received, shutting down...","service":"solana-trading-bot","timestamp":"2025-08-14T19:14:09.666Z"}

View File

@@ -0,0 +1,3 @@
{"level":"error","message":"Failed to execute sniper trade for 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:02.572Z"}
{"level":"error","message":"Failed to execute sniper trade for EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.143Z"}
{"level":"error","message":"Failed to execute sniper trade for 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:35.375Z"}

View File

@@ -0,0 +1,325 @@
import { newlunched_subscribeCommand, stopNewLaunch } from "./grpc.js";
import { token_buy, token_sell, getSplTokenBalance, getPublicKeyFromPrivateKey } from "./fuc.js";
import { getBalance } from "./swap.js";
import { config, validateConfig } from "./config.js";
import logger from "./utils/logger.js";
import notificationService from "./services/notifications.js";
import riskManager from "./services/riskManager.js";
import chalk from "chalk";
// Trading configuration from config service
const tradingConfig = config.trading;
// Track active positions
const activePositions = new Map();
export const pump_geyser = async () => {
try {
// Validate configuration
validateConfig();
const walletKey = getPublicKeyFromPrivateKey();
// Log startup banner
console.log(chalk.magentaBright(`
██████╗██████╗ ██╗ ██╗██████╗ ████████╗ ██████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗
██╔════╝██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██║ ██╔╝██║████╗ ██║██╔════╝
██║ ██████╔╝ ██║ ██╔╝██████╔╝ ██║ ██║ ██║█████╔╝ ██║██╔██╗ ██║██║ ██╗
██║ ██╔══██╗ ██╔═╝ ██╔═══╝ ██║ ██║ ██║██╔═██╗ ██║██║╚██╗██║██║ ██║
╚██████╗██║ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██╗██║██║ ╚████║╚██████╔╝
╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝
`));
logger.info("🚀 Starting Solana Raydium Sniper Bot...");
logger.info(`🔑 Wallet Public Key: ${walletKey}`);
logger.info(`💰 Sniper Amount: ${tradingConfig.sniperAmount} SOL`);
logger.info(`🎯 Profit Target: ${tradingConfig.profitTarget}x`);
logger.info(`🛑 Stop Loss: ${tradingConfig.stopLoss}x`);
logger.info(`⏱️ Max Hold Time: ${tradingConfig.maxHoldTime/1000}s`);
logger.info(`📊 Max Positions: ${tradingConfig.maxPositions}`);
// Log configuration
logger.debug("Trading configuration loaded", tradingConfig);
logger.debug("Pool filters", config.pools);
logger.debug("Risk management settings", config.risk);
// Send startup notification
await notificationService.notifyBotStatus("Started", {
wallet: walletKey,
config: tradingConfig,
});
// Start monitoring for new token launches
await newlunched_subscribeCommand();
// Set up position monitoring
setInterval(monitorPositions, 5000); // Check positions every 5 seconds
// Set up risk monitoring
setInterval(monitorRisk, 10000); // Check risk metrics every 10 seconds
// Set up graceful shutdown
process.on('SIGINT', async () => {
logger.warn("🛑 Shutting down sniper bot...");
stopNewLaunch();
// Close all positions before exit
await closeAllPositions();
// Send shutdown notification
await notificationService.notifyBotStatus("Shutdown", {
reason: "SIGINT received",
finalStats: riskManager.getDailyStats(),
});
process.exit(0);
});
// Handle other shutdown signals
process.on('SIGTERM', async () => {
logger.warn("🛑 SIGTERM received, shutting down...");
await handleGracefulShutdown("SIGTERM");
});
process.on('uncaughtException', async (error) => {
logger.error("Uncaught exception", error);
await notificationService.notifyError(error, "Uncaught Exception");
await handleGracefulShutdown("Uncaught Exception");
});
process.on('unhandledRejection', async (reason, promise) => {
logger.error("Unhandled rejection", { reason, promise });
await notificationService.notifyError(new Error(reason), "Unhandled Rejection");
});
} catch (error) {
logger.error("Error in pump_geyser", error);
await notificationService.notifyError(error, "Bot Startup");
throw error;
}
};
// Monitor active positions for profit taking or stop loss
async function monitorPositions() {
try {
const positions = riskManager.getActivePositions();
for (const position of positions) {
try {
const currentBalance = await getSplTokenBalance(position.mint);
if (!currentBalance || currentBalance <= 0) {
logger.warn(`⚠️ No balance for ${position.mint}, removing from active positions`);
riskManager.activePositions.delete(position.mint);
continue;
}
// Update position price (you'll need to implement price fetching)
// const currentPrice = await getCurrentPrice(position.mint);
// riskManager.updatePositionPrice(position.mint, currentPrice);
// Check if position should be closed
const shouldClose = riskManager.shouldClosePosition(position.mint);
if (shouldClose.shouldClose) {
logger.info(`Position closure triggered for ${position.mint}: ${shouldClose.reason}`);
await closePosition(position.mint, position, shouldClose.reason);
}
} catch (error) {
logger.error(`Error monitoring position ${position.mint}`, error);
}
}
} catch (error) {
logger.error("Error in position monitoring", error);
}
}
// Monitor risk metrics
async function monitorRisk() {
try {
const riskMetrics = riskManager.getRiskMetrics();
// Log risk metrics periodically
if (riskMetrics.riskLevel === 'HIGH') {
logger.warn("High risk level detected", riskMetrics);
await notificationService.sendNotification(
"🚨 High risk level detected - review positions and consider reducing exposure",
"warning",
riskMetrics
);
}
// Log daily stats every hour
const now = new Date();
if (now.getMinutes() === 0) {
logger.info("Hourly risk summary", riskMetrics);
}
} catch (error) {
logger.error("Error in risk monitoring", error);
}
}
// Close a specific position
async function closePosition(mint, position, reason = 'manual') {
try {
logger.info(`Closing position for ${mint}`, { reason, position });
// Get current token balance
const currentBalance = await getSplTokenBalance(mint);
if (!currentBalance || currentBalance <= 0) {
logger.warn(`No balance to sell for ${mint}`);
riskManager.activePositions.delete(mint);
return;
}
// Execute sell transaction
const sellResult = await token_sell(mint, currentBalance);
if (sellResult && sellResult.txHash) {
logger.info(`Position closed successfully for ${mint}`, {
txHash: sellResult.txHash,
reason,
balance: currentBalance,
});
// Record the trade
riskManager.recordTrade('sell', mint, currentBalance, position.currentPrice || 0, sellResult.txHash);
// Send notification
await notificationService.notifyPositionUpdate('closed', mint, {
reason,
txHash: sellResult.txHash,
balance: currentBalance,
});
} else {
logger.error(`Failed to close position for ${mint}`);
}
} catch (error) {
logger.error(`Error closing position for ${mint}`, error);
await notificationService.notifyError(error, `Position Closure - ${mint}`);
}
}
// Close all active positions
async function closeAllPositions() {
try {
const positions = riskManager.getActivePositions();
logger.info(`Closing ${positions.length} active positions...`);
const results = [];
for (const position of positions) {
try {
await closePosition(position.mint, position, 'shutdown');
results.push({ mint: position.mint, status: 'closed' });
} catch (error) {
logger.error(`Error closing position ${position.mint}`, error);
results.push({ mint: position.mint, status: 'error', error: error.message });
}
}
logger.info("Position closure summary", { results });
return results;
} catch (error) {
logger.error("Error in closeAllPositions", error);
throw error;
}
}
// Handle graceful shutdown
async function handleGracefulShutdown(reason) {
try {
logger.warn(`Graceful shutdown initiated: ${reason}`);
// Stop gRPC monitoring
stopNewLaunch();
// Close all positions
await closeAllPositions();
// Send final notification
await notificationService.notifyBotStatus("Shutdown", {
reason,
finalStats: riskManager.getDailyStats(),
});
logger.info("Graceful shutdown completed");
process.exit(0);
} catch (error) {
logger.error("Error during graceful shutdown", error);
process.exit(1);
}
}
// Handle new token launch detected by gRPC
async function handleNewTokenLaunch(tokenMint, poolStatus, context) {
try {
logger.info(`New token launch detected: ${tokenMint}`, {
poolStatus,
context,
timestamp: new Date().toISOString(),
});
// Check if we can execute a trade
const tradeCheck = riskManager.canExecuteTrade(config.trading.sniperAmount, tokenMint);
if (!tradeCheck.allowed) {
logger.warn(`Trade blocked for ${tokenMint}`, { reasons: tradeCheck.errors });
return;
}
// Execute the sniper trade
logger.info(`Executing sniper trade for ${tokenMint}`);
// Get current SOL balance
const solBalance = await getBalance();
if (solBalance < config.trading.sniperAmount) {
logger.warn(`Insufficient SOL balance for sniper trade: ${solBalance} SOL`);
return;
}
// Execute buy transaction
const buyResult = await token_buy(tokenMint, config.trading.sniperAmount);
if (buyResult && buyResult.txHash) {
logger.info(`Sniper trade executed successfully for ${tokenMint}`, {
txHash: buyResult.txHash,
amount: config.trading.sniperAmount,
poolStatus,
});
// Record the trade
riskManager.recordTrade('buy', tokenMint, config.trading.sniperAmount, 0, buyResult.txHash);
// Send notification
await notificationService.notifyTradeExecution('buy', tokenMint, config.trading.sniperAmount, 0, buyResult.txHash);
// Add to active positions for monitoring
const position = {
entryPrice: 0, // Will be updated when we get price data
entryAmount: config.trading.sniperAmount,
entryTime: Date.now(),
entryValue: config.trading.sniperAmount,
currentPrice: 0,
poolStatus,
context,
};
riskManager.activePositions.set(tokenMint, position);
logger.info(`Position opened for ${tokenMint}`, position);
} else {
logger.error(`Failed to execute sniper trade for ${tokenMint}`);
await notificationService.notifyError(
new Error('Buy transaction failed'),
`Sniper Trade - ${tokenMint}`
);
}
} catch (error) {
logger.error(`Error handling new token launch for ${tokenMint}`, error);
await notificationService.notifyError(error, `New Token Launch - ${tokenMint}`);
}
}
// Export functions for external use
export { closePosition, closeAllPositions, monitorPositions, handleNewTokenLaunch };

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