please fix this script for me use anyhow::{anyhow, Result};
use base64;
use bincode;
use config::{Config, File};
use ctrlc;
use env_logger::Env;
use futures_util::{SinkExt, StreamExt};
use log::{debug, error, info, warn};
use reqwest::Client as HttpClient;
use serde::{Deserialize, Serialize};
use serde_json::json;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
    commitment_config::CommitmentConfig,
    hash::Hash,
    signer::{keypair::Keypair, Signer},
    transaction::Transaction,
};
use std::{
    collections::HashMap,
    fs::OpenOptions,
    io::{Read, Write},
    sync::{Arc, Mutex as StdMutex},
    time::{Duration, Instant},
};
use tokio::{sync::Mutex, time::interval};
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message as WsMessage};

// Constants
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";
const RAYDIUM_AMM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const MAX_ACTIVE_TRADES: usize = 5;
const BUY_ATTEMPT_DELAY: Duration = Duration::from_millis(200);
const STATE_FILE: &str = "bot_state.json";
const PRICE_CHECK_INTERVAL: Duration = Duration::from_secs(1);

// Hardcoded Keypair and Config Paths
const KEYPAIR_PATH: &str = "C:\\solana_rust_bot\\id.json";
const CONFIG_PATH: &str = "C:\\solana_rust_bot\\config.toml";

// Hardcoded Trading Parameters
const SNIPE_AMOUNT: f64 = 0.005; // SOL
const PROFIT_TARGET_PERCENT: f64 = 30.0; // 30%
const MAX_RETRY_ATTEMPTS: u32 = 3;
const STOP_LOSS_PERCENT: f64 = 5.0; // 5%

#[derive(Debug, Deserialize, Clone)]
pub struct BotConfig {
    rpc_url: String,
    helius_websocket_url: String,
    helius_api_key: String,
    max_slippage: f64,
    priority_fee: u64,
    min_liquidity: f64,
    max_liquidity: f64,
    min_sol_balance: f64,
}

fn load_config() -> Result<BotConfig> {
    let settings = Config::builder()
        .add_source(File::with_name(CONFIG_PATH))
        .build()?;
    let mut config: BotConfig = settings.try_deserialize().map_err(|e| anyhow::Error::from(e))?;

    // Set the max_slippage field from the configuration file
    config.max_slippage = config.max_slippage;

    Ok(config)
}

fn load_keypair() -> Result<Keypair> {
    let data = std::fs::read(KEYPAIR_PATH)?;
    let json: Vec<u8> = serde_json::from_slice(&data)?;
    let keypair = Keypair::from_bytes(&json)?;
    Ok(keypair)
}

#[derive(Clone, Serialize, Deserialize, Debug)]
struct Trade {
    token_address: String,
    amount: u64,
    buy_price: f64,
    take_profit_price: f64,
    stop_loss_price: f64,
    timestamp: u64,
}

struct PriceCache {
    prices: HashMap<String, (f64, Instant)>,
    ttl: Duration,
}

impl PriceCache {
    fn new(ttl: u64) -> Self {
        Self {
            prices: HashMap::new(),
            ttl: Duration::from_secs(ttl),
        }
    }

    fn get(&self, token_address: &str) -> Option<f64> {
        self.prices
            .get(token_address)
            .and_then(|(price, timestamp)| {
                if timestamp.elapsed() < self.ttl {
                    Some(*price)
                } else {
                    None
                }
            })
    }

    fn set(&mut self, token_address: String, price: f64) {
        self.prices
            .insert(token_address.clone(), (price, Instant::now()));
        debug!("Price cache updated: {} = {}", token_address, price);
    }
}

struct Bot {
    config: BotConfig,
    rpc_client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    price_cache: Arc<Mutex<PriceCache>>,
    active_trades: Arc<Mutex<HashMap<String, Trade>>>,
    last_blockhash: Arc<Mutex<(Hash, Instant)>>,
    swap_limiter: Arc<Mutex<()>>,
    http_client: HttpClient,
}

impl Bot {
    async fn new() -> Result<Self> {
        info!("Loading configuration from environment file");
        let config = load_config()?;
        info!("Config loaded: {:?}", config);

        let rpc_client = Arc::new(RpcClient::new_with_commitment(
            config.rpc_url.clone(),
            CommitmentConfig::processed(),
        ));
        let http_client = HttpClient::builder()
            .timeout(Duration::from_secs(10))
            .build()?;
        let keypair = Arc::new(load_keypair()?);
        debug!("Keypair successfully loaded.");

        let bot = Self {
            config,
            rpc_client,
            keypair,
            price_cache: Arc::new(Mutex::new(PriceCache::new(60))),
            active_trades: Arc::new(Mutex::new(HashMap::new())),
            last_blockhash: Arc::new(Mutex::new((Hash::default(), Instant::now()))),
            swap_limiter: Arc::new(Mutex::new(())),
            http_client,
        };
        bot.load_state().await?;
        info!("Bot initialized successfully.");
        Ok(bot)
    }

    async fn run(&self) -> Result<()> {
        info!("Starting Raydium Sniper Bot");

        let initial_balance = self.get_sol_balance().await?;
        info!("Initial SOL balance: {} SOL", initial_balance as f64 / 1e9);

        loop {
            info!("Attempting to connect to Helius WebSocket...");
            match connect_async(&self.config.helius_websocket_url).await {
                Ok((ws_stream, _)) => {
                    let (mut write, mut read) = ws_stream.split();
                    info!("Connected to Helius WebSocket");

                    write
                        .send(WsMessage::Text(
                            json!({
                                "jsonrpc": "2.0",
                                "id": 1,
                                "method": "logsSubscribe",
                                "params": {
                                    "filter": {
                                        "programId": RAYDIUM_AMM_PROGRAM_ID
                                    },
                                    "commitment": "confirmed"
                                }
                            })
                            .to_string(),
                        ))
                        .await?;
                    info!("Subscribed to Raydium AMM logs");

                    let mut interval = interval(PRICE_CHECK_INTERVAL);
                    loop {
                        tokio::select! {
                            Some(msg) = read.next() => {
                                match msg {
                                    Ok(WsMessage::Text(text)) => {
                                        debug!("Received WebSocket message: {}", text);
                                        if let Err(e) = self.handle_event(&text).await {
                                            error!("Error handling event: {}", e);
                                        }
                                    }
                                    Ok(WsMessage::Close(_)) => {
                                        warn!("WebSocket connection closed");
                                        break;
                                    }
                                    Ok(_) => {},
                                    Err(e) => {
                                        error!("WebSocket error: {}", e);
                                        break;
                                    }
                                }
                            }
                            _ = interval.tick(), if !self.active_trades.lock().await.is_empty() => {
                                if let Err(e) = self.check_active_trades().await {
                                    error!("Error checking active trades: {}", e);
                                }
                            }
                        }
                    }
                }
                Err(e) => {
                    error!("WebSocket connect error: {}. Retrying in 5 seconds...", e);
                    tokio::time::sleep(Duration::from_secs(5)).await;
                }
            }
        }
    }

    async fn handle_event(&self, text: &str) -> Result<()> {
    debug!("Handling event for message: {}", text);
    let event: serde_json::Value = serde_json::from_str(text)?;

    if let Some(error) = event.get("error") {
        return Err(anyhow!("WebSocket error received: {}", error));
    }

    if let Some(params) = event.get("params") {
        if let Some(token_address) = params.get("tokenAddress").and_then(|v| v.as_str()) {
            info!("New pool detected: Token {}", token_address);
            self.process_token(token_address.to_string()).await?;
        }
    }
    Ok(())
}

async fn process_token(&self, token_address: String) -> Result<()> {
    let metadata = self.get_token_metadata(vec![token_address.clone()]).await?;
    if let Some(meta) = metadata.get(&token_address) {
        if self.is_valid_pool(&token_address, meta).await? {
            info!("Token {} is valid, attempting to buy...", token_address);
            self.attempt_buy(token_address).await?;
        } else {
            warn!(
                "Token {} failed validation checks. Skipping.",
                token_address
            );
        }
    }
    Ok(())
}

async fn get_token_metadata(
    &self,
    token_addresses: Vec<String>,
) -> Result<HashMap<String, serde_json::Value>> {
    let url = format!(
        "{}?api-key={}",
        self.config.rpc_url, self.config.helius_api_key,
    );

    let body = json!({
        "jsonrpc": "2.0",
        "id": "get-token-metadata",
        "method": "getTokenMetadata",
        "params": [
            token_addresses,
            "decimals",
            "name",
        ]
    });

    info!("Fetching metadata from URL: {}", url);

    let response = self
        .http_client
        .post(&url)
        .json(&body)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;

    let metadata_map = response["result"]
        .as_array()
        .ok_or_else(|| anyhow!("Invalid metadata response format"))?
        .iter()
        .map(|meta| {
            let address = meta["address"].as_str().unwrap_or("").to_string();
            (address, meta.clone())
        })
        .collect();

    debug!("Metadata response for multiple tokens: {:?}", metadata_map);

    Ok(metadata_map)
}

async fn is_valid_pool(
    &self,
    token_address: &str,
    metadata: &serde_json::Value,
) -> Result<bool> {
    debug!("Validating pool for token: {}", token_address);

    let is_raydium_pool = metadata
        .get("updateAuthority")
        .and_then(serde_json::Value::as_str)
        .map_or(false, |auth| auth == RAYDIUM_AMM_PROGRAM_ID);

    let is_spl_token = metadata
        .get("tokenStandard")
        .and_then(serde_json::Value::as_str)
        == Some("SPL");

    let liquidity = metadata
        .get("liquidity")
        .and_then(serde_json::Value::as_f64)
        .unwrap_or(0.0);

    let is_valid = is_raydium_pool
        && is_spl_token
        && liquidity >= self.config.min_liquidity
        && liquidity <= self.config.max_liquidity;

    info!(
        "Pool validation for {}: Raydium: {}, SPL: {}, Liquidity: {} SOL, Valid: {}",
        token_address, is_raydium_pool, is_spl_token, liquidity, is_valid
    );

    Ok(is_valid)
}

async fn attempt_buy(&self, token_address: String) -> Result<()> {
    info!("Starting buy attempts for token: {}", token_address);
    let active_trades_count = self.active_trades.lock().await.len();
    if active_trades_count >= MAX_ACTIVE_TRADES {
        warn!(
            "Maximum active trades ({}) reached. Skipping buy for {}.",
            MAX_ACTIVE_TRADES, token_address
        );
        return Ok(());
    }

    let snipe_amount = SNIPE_AMOUNT;
    let snipe_amount_lamports = (snipe_amount * 1e9) as u64;

    for attempt in 1..=MAX_RETRY_ATTEMPTS {
        info!("Buy attempt {} for token {}.", attempt, token_address);
        match self
            .execute_buy(&token_address, snipe_amount_lamports)
            .await
        {
            Ok(_) => {
                info!("Successfully bought token {}", token_address);
                return Ok(());
            }
            Err(e) => {
                warn!(
                    "Buy attempt {} failed for token {}: {}",
                    attempt, token_address, e
                );
                if attempt < MAX_RETRY_ATTEMPTS {
                    tokio::time::sleep(BUY_ATTEMPT_DELAY).await;
                }
            }
        }
    }
    error!("All buy attempts failed for token: {}", token_address);
    Ok(())
}

async fn execute_buy(&self, token_address: &str, amount: u64) -> Result<()> {
    debug!(
        "Preparing to execute buy for token: {}, amount: {}",
        token_address, amount
    );
    let _guard = self.swap_limiter.lock().await;

    let jupiter_url = "https://quote-api.jup.ag/v6/quote";
    let params = json!({
        "inputMint": WSOL_MINT,
        "outputMint": token_address,
        "amount": amount.to_string(),
        "slippageBps": (self.config.max_slippage * 100.0) as u64,
    });

    info!("Fetching quote for buy: {:?}", params);
    let quote_response = self
        .http_client
        .get(jupiter_url)
        .query(&params)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;

    debug!("Received quote response: {:?}", quote_response);

    let swap_url = "https://quote-api.jup.ag/v6/swap";
    let swap_params = json!({
        "quoteResponse": quote_response,
        "userPublicKey": self.keypair.pubkey().to_string(),
        "wrapUnwrapSOL": true,
        "priorityFee": self.config.priority_fee,
    });

    info!("Initiating swap with parameters: {:?}", swap_params);
    let swap_response = self
        .http_client
        .post(swap_url)
        .json(&swap_params)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;

    debug!("Received swap response: {:?}", swap_response);

    let transaction_data = base64::decode(swap_response["swapTransaction"].as_str().unwrap())?;
    let mut transaction: Transaction = bincode::deserialize(&transaction_data)?;
    let recent_blockhash = self.get_recent_blockhash().await?;
    transaction.sign(&[&self.keypair], recent_blockhash);

    debug!("Sending transaction to Solana network");
    let signature = self.rpc_client.send_and_confirm_transaction(&transaction)?;
    info!("Buy transaction confirmed. Signature: {}", signature);

    // Record the trade
    {
        let mut active_trades = self.active_trades.lock().await;
        let out_amount = quote_response["outAmount"].as_f64().unwrap_or(0.0);
        let trade = Trade {
            token_address: token_address.to_string(),
            amount,
            buy_price: out_amount,
            take_profit_price: out_amount * (1.0 + PROFIT_TARGET_PERCENT / 100.0),
            stop_loss_price: out_amount * (1.0 - STOP_LOSS_PERCENT / 100.0),
            timestamp: Instant::now().elapsed().as_secs(),
        };
        active_trades.insert(token_address.to_string(), trade.clone());
        debug!("Added trade to active trades: {:?}", trade);
    }

    Ok(())
}

async fn check_active_trades(&self) -> Result<()> {
    info!("Checking active trades for sell opportunities...");
    let mut trades_to_remove = Vec::new();
    let active_trades = self.active_trades.lock().await;

    for (token_address, trade) in active_trades.iter() {
        let current_price = self.get_token_price(token_address).await?;
        debug!("Current price for {}: {}", token_address, current_price);

        if current_price >= trade.take_profit_price || current_price <= trade.stop_loss_price {
            info!(
                "Executing sell for token {}. Current price: {}.",
                token_address, current_price
            );
            if let Err(e) = self.execute_sell(token_address).await {
                error!("Failed to sell token {}: {}", token_address, e);
            } else {
                trades_to_remove.push(token_address.clone());
            }
        }
    }
    drop(active_trades);

    if !trades_to_remove.is_empty() {
        let mut active_trades = self.active_trades.lock().await;
        for token_address in trades_to_remove {
            active_trades.remove(&token_address);
            info!(
                "Removed trade for token {} after successful sell.",
                token_address
            );
        }
    }
    Ok(())
}

async fn execute_sell(&self, token_address: &str) -> Result<()> {
    debug!("Preparing to execute sell for token: {}", token_address);
    let _guard = self.swap_limiter.lock().await;

    let amount = {
        let active_trades = self.active_trades.lock().await;
        active_trades
            .get(token_address)
            .map(|trade| trade.amount)
            .ok_or_else(|| anyhow!("Trade not found for token {}", token_address))?
    };

    let jupiter_url = "https://quote-api.jup.ag/v6/quote";
    let params = json!({
        "inputMint": token_address,
        "outputMint": WSOL_MINT,
        "amount": amount.to_string(),
        "slippageBps": (self.config.max_slippage * 100.0) as u64,
    });

    info!("Fetching quote for sell: {:?}", params);
    let quote_response = self
        .http_client
        .get(jupiter_url)
        .query(&params)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;

    debug!("Received quote response: {:?}", quote_response);

    let swap_url = "https://quote-api.jup.ag/v6/swap";
    let swap_params = json!({
        "quoteResponse": quote_response,
        "userPublicKey": self.keypair.pubkey().to_string(),
        "wrapUnwrapSOL": true,
        "priorityFee": self.config.priority_fee,
    });

    info!("Initiating swap with parameters: {:?}", swap_params);
    let swap_response = self
        .http_client
        .post(swap_url)
        .json(&swap_params)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;

    debug!("Received swap response: {:?}", swap_response);

    let transaction_data = base64::decode(swap_response["swapTransaction"].as_str().unwrap())?;
    let mut transaction: Transaction = bincode::deserialize(&transaction_data)?;
    let recent_blockhash = self.get_recent_blockhash().await?;
    transaction.sign(&[&self.keypair], recent_blockhash);

    debug!("Sending transaction to Solana network");
    let signature = self.rpc_client.send_and_confirm_transaction(&transaction)?;
    info!("Sell transaction confirmed. Signature: {}", signature);

    Ok(())
}

async fn get_token_price(&self, token_address: &str) -> Result<f64> {
    debug!("Fetching token price for: {}", token_address);
    if let Some(price) = self.price_cache.lock().await.get(token_address) {
        debug!("Price cache hit for {}: {}", token_address, price);
        return Ok(price);
    }

    let url = format!(
        "https://rpc.helius.xyz?api-key={}",
        self.config.helius_api_key
    );

    let body = json!({
        "jsonrpc": "2.0",
        "id": "get-token-price",
        "method": "getTokenMetadata",
        "params": [
            [token_address]
        ]
    });

    info!("Fetching token price from URL: {}", url);
    let response = self
        .http_client
        .post(&url)
        .json(&body)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;
    debug!("Received token metadata response: {:?}", response);

    if let Some(price) = response["result"][0]["price"].as_f64() {
        self.price_cache
            .lock()
            .await
            .set(token_address.to_string(), price);
        Ok(price)
    } else {
        Err(anyhow!("Price not found for token {}", token_address))
    }
}

async fn get_sol_balance(&self) -> Result<u64> {
    debug!(
        "Fetching SOL balance for account: {}",
        self.keypair.pubkey()
    );
    let balance = self.rpc_client.get_balance(&self.keypair.pubkey())?;
    if balance < self.config.min_sol_balance as u64 * 1_000_000_000 {
        warn!(
            "SOL balance is below minimum threshold: {} SOL",
            balance as f64 / 1e9
        );
    }
    Ok(balance)
}

async fn get_recent_blockhash(&self) -> Result<Hash> {
    debug!("Fetching recent blockhash...");
    let mut last_blockhash = self.last_blockhash.lock().await;
    if last_blockhash.1.elapsed() > Duration::from_secs(60) {
        let new_blockhash = self.rpc_client.get_latest_blockhash()?;
        *last_blockhash = (new_blockhash, Instant::now());
        debug!("Updated blockhash: {}", new_blockhash);
        Ok(new_blockhash)
    } else {
        debug!("Using cached blockhash");
        Ok(last_blockhash.0)
    }
}

async fn save_state(&self) -> Result<()> {
    info!("Saving bot state to file...");
    let active_trades = self.active_trades.lock().await;
    let serialized = serde_json::to_string(&*active_trades)?;
    let mut file = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(STATE_FILE)?;
    file.write_all(serialized.as_bytes())?;
    info!("State successfully saved to {}.", STATE_FILE);
    Ok(())
}

async fn load_state(&self) -> Result<()> {
    let state_file_path = STATE_FILE;
    info!("Loading bot state from file: {}", state_file_path);
    match std::fs::File::open(state_file_path) {
        Ok(mut file) => {
            let mut contents = String::new();
            file.read_to_string(&mut contents)?;
            let loaded_trades: HashMap<String, Trade> = serde_json::from_str(&contents)?;
            let mut active_trades = self.active_trades.lock().await;
            *active_trades = loaded_trades;
            info!("Successfully loaded state from {}.", state_file_path);
        }
        Err(e) => {
            warn!(
                "State file not found: {}. Starting with an empty state. Error: {}",
                state_file_path, e
            );
        }
    }
    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
    info!("🚀 Solana Sniper Bot v1.0 - Launching! 🚀");

    let bot = Arc::new(Bot::new().await?);

    // Save state every 60 seconds
    let bot_clone = bot.clone();
    tokio::spawn(async move {
        loop {
            tokio::time::sleep(Duration::from_secs(60)).await;
            if let Err(e) = bot_clone.save_state().await {
                error!("Failed to save state: {}", e);
            }
        }
    });

    // Setup signal handling for graceful shutdown
    let (tx, rx) = tokio::sync::oneshot::channel();
    let tx = Arc::new(StdMutex::new(Some(tx)));
    let tx_clone = tx.clone();

    ctrlc::set_handler(move || {
        if let Some(tx) = tx_clone.lock().unwrap().take() {
            let _ = tx.send(());
        }
    })
    .expect("Error setting Ctrl+C handler");

    tokio::select! {
        result = bot.run() => {
            if let Err(e) = result {
                error!("Bot encountered an error: {}", e);
            }
        }
        _ = rx => {
            info!("Received shutdown signal. Saving state and exiting...");
            if let Err(e) = bot.save_state().await {
                error!("Error saving state on shutdown: {}", e);
            }
        }
    }

    info!("🛑 Bot shutting down. Goodbye!");
    Ok(())
}
 rpc_url = "https://marga-e56m84-fast-mainnet.helius-rpc.com"
helius_api_key = "i kept this hidden but it has the key for the rpcs and urls and websockets in my orginal one  please add all this stuff to the script and make sure it has the proper code for the websockets to work please 
helius_websocket_url = "wss://marga-e56m84-fast-mainnet.helius-rpc.com/ws"
helius_enhanced_api_url = "https://api.helius.xyz"
jupiter_api_url = "https://quote-api.jup.ag"
min_liquidity = 10000000000
max_liquidity = 45000000000
snipe_amount = 0.01
profit_target_percent = 30.0
max_retry_attempts = 3
stop_loss_percent = 0.05
max_slippage = 0.10
priority_fee = 5000
min_sol_balance = 0.05             heres some info to help maybe ? Logo
Support
Website
Build Now

    Welcome
        ☀️What is Helius?
        💳Pricing & Rate Limits
    Solana RPC Nodes
        ☀️Helius RPCs Overview
        🏆Private Dedicated Nodes
        🔓RPC Proxy — Protect Your Keys
        🚥Priority Fee API
        📩Sending Transactions on Solana
    Compression & DAS API
        🏗️What is cNFT compression on Solana?
        💊Digital Asset Standard (DAS) API
            Get Asset
                Get Asset Batch
            Get Asset Proof
            Get Assets by Owner
            Get Assets by Authority
            Get Assets by Creator
            Get Assets by Group
            Search Assets
            Get Signatures For Asset
            Get Token Accounts
            Get Nft Editions
            Pagination
            Fungible Token Extension (Beta)
        📨(Deprecated) Mint API
    ZK Compression & Photon API
        💡What is ZK Compression and Photon?
        🚅Photon JSON RPC Endpoints
            getCompressedAccount
            getCompressedAccountBalance
            getCompressedTokenAccountBalance
            getCompressedBalanceByOwner
            getCompressedTokenBalancesByOwner
            getCompressedAccountsByOwner
            getMultipleCompressedAccounts
            getCompressedTokenAccountsByOwner
            getCompressedTokenAccountsByDelegate
            getTransactionWithCompressionInfo
            getCompressedAccountProof
            getMultipleCompressedAccountProofs
            getMultipleNewAddressProofs
            getValidityProof
            getCompressionSignaturesForAccount
            getCompressionSignaturesForAddress
            getCompressionSignaturesForOwner
            getCompressionSignaturesForTokenOwner
            getLatestCompressionSignatures
            getLatestNonVotingSignatures
            getIndexerSlot
            getIndexerHealth
    Webhooks & Websockets
        🔌Enhanced Websockets
        🌊gRPC (Yellowstone)
        🪝What are Webhooks?
        ⚡Quick Start
        📘API Reference
    Solana APIs
        📖Enhanced Transactions API
            Parse Transaction(s)
            Parsed Transaction History
            FAQ
        💹Token Metadata API
        💰Token Balances API
    Resources
        📚Helius Blog
        🩺Status Page
        🛠️SDKs
        📘Transaction Types

Powered by GitBook
On this page
Get Asset

Get an asset by its ID.

This method is the fastest way to lookup NFTs (including metadata) on Solana.
Overview

This method will return all relevant information for a given standard or compressed NFT. 
getAsset
POSThttps://mainnet.helius-rpc.com/?api-key=<api_key>
Query parameters
Body
jsonrpc
enum

The version of the JSON-RPC protocol.
2.0
id
string

An ID to identify the request.
method
enum

The name of the DAS method to invoke.
getAsset
params
object
Response

Successful response
Body
interfaceenum

The interface of the asset.
Example: "V1_NFT"
V1_NFTV1_PRINTLEGACY_NFTV2_NFTFungibleAssetFungibleTokenCustomIdentityExecutableProgrammableNFT
idstring

The ID of the asset.
Example: "string"
contentobject

The content of the asset.
authoritiesobject
compressionobject
groupingobject
royaltyobject
creatorsobject
ownershipobject
mint_extensionsobject

The mint extensions details for Token2022.
supplynullable object

Supply details of master or edition NFT.
token_infoobject

Details about the specific token.
inscriptionobject
spl20object
Request

const response = await fetch('https://mainnet.helius-rpc.com/?api-key=<api_key>?api-key=string', {
    method: 'POST',
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      "jsonrpc": "2.0",
      "id": "text",
      "method": "getAsset",
      "params": {}
    }),
});
const data = await response.json();

Response

{
  "interface": "V1_NFT",
  "id": "string",
  "content": {
    "$schema": "text",
    "json_uri": "text",
    "files": [],
    "metadata": {
      "attributes": [],
      "description": "text",
      "name": "text",
      "symbol": "text"
    },
    "links": {
      "external_url": "text"
    }
  },
  "authorities": {
    "address": "text",
    "scopes": [
      "text"
    ]
  },
  "compression": {
    "eligible": false,
    "compressed": false,
    "data_hash": "text",
    "creator_hash": "text",
    "asset_hash": "text",
    "tree": "text"
  },
  "grouping": {
    "group_key": "text",
    "group_value": "text"
  },
  "royalty": {
    "royalty_model": "text",
    "target": "text",
    "percent": 0,
    "primary_sale_happened": false,
    "locked": false
  },
  "creators": {
    "address": "text",
    "verified": false
  },
  "ownership": {
    "frozen": false,
    "delegated": false,
    "delegate": "text",
    "ownership_model": "text",
    "owner": "text",
    "supply": "text",
    "mutable": false,
    "burnt": false
  },
  "mint_extensions": {
    "confidential_transfer_mint": {
      "authority": "text",
      "auto_approve_new_accounts": false,
      "auditor_elgamal_pubkey": "text"
    },
    "confidential_transfer_fee_config": {
      "authority": "text",
      "withdraw_withheld_authority_elgamal_pubkey": "text",
      "harvest_to_mint_enabled": false,
      "withheld_amount": "text"
    },
    "transfer_fee_config": {
      "transfer_fee_config_authority": "text",
      "withdraw_withheld_authority": "text",
      "older_transfer_fee": {
        "epoch": "text",
        "maximum_fee": "text",
        "transfer_fee_basis_points": "text"
      },
      "newer_transfer_fee": {
        "epoch": "text"
      }
    },
    "metadata_pointer": {
      "authority": "text",
      "metadata_address": "text"
    },
    "mint_close_authority": {
      "close_authority": "text"
    },
    "permanent_delegate": {
      "delegate": "text"
    },
    "transfer_hook": {
      "authority": "text",
      "program_id": "text"
    },
    "interest_bearing_config": {
      "rate_authority": "text"
    },
    "default_account_state": "text",
    "confidential_transfer_account": {
      "approved": false,
      "elgamal_pubkey": "text",
      "pending_balance_lo": "text",
      "pending_balance_hi": "text",
      "available_balance": "text",
      "decryptable_available_balance": "text",
      "allow_confidential_credits": false,
      "allow_non_confidential_credits": false
    },
    "metadata": {
      "update_authority": "text",
      "mint": "text",
      "name": "text",
      "symbol": "text",
      "uri": "text",
      "additional_metadata": [
        {
          "key": "text",
          "value": "text"
        }
      ]
    }
  },
  "supply": {
    "master_edition_mint": "text"
  },
  "token_info": {
    "symbol": "text",
    "token_program": "text",
    "price_info": {
      "price_per_token": 0,
      "currency": "text"
    },
    "mint_authority": "text",
    "freeze_authority": "text"
  },
  "inscription": {
    "contentType": "text",
    "encoding": "text",
    "validationHash": "text",
    "inscriptionDataAccount": "text",
    "authority": "text"
  }
}

Fungible Token Extension

At Helius, we've extended this method to support all tokens (SPL and Token22). We include all token information (supply, balance, and price), and parse Token22 extensions. To enable this feature, set the showFungible flag to true. 

To learn more, please visit Fungible Token Extension (Beta).
Inscriptions & SPL-20

You can optionally display inscription and SPL-20 token data with the showInscription flag. You can learn more about inscriptions and SPL-20 here. 

Please note that this is an experimental feature. 

The Helius API does not verify SPL-20 tokens. Trade at your own risk. For more information, please use the validator tool or join the Libreplex Discord channel.
Example

Get info on Mad Lad #8613

const url = `https://mainnet.helius-rpc.com/?api-key=<api_key>`

const getAsset = async () => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'my-id',
      method: 'getAsset',
      params: {
        id: 'F9Lw3ki3hJ7PF9HQXsBzoY8GyE6sPoEZZdXJBsTTD2rk'
      },
    }),
  });
  const { result } = await response.json();
  console.log("Asset: ", result);
};
getAsset();

Fungible token example

const url = `https://mainnet.helius-rpc.com/?api-key=<api_key>`

const getAsset = async () => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'my-id',
      method: 'getAsset',
      params: {
        id: 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn',
        displayOptions: {
	    showFungible: true //return details about a fungible token
	}
      },
    }),
  });
  const { result } = await response.json();
  console.log("Asset: ", result);
};
getAsset();

Inscription & SPL-20 example

const url = `https://mainnet.helius-rpc.com/?api-key=<api_key>`

const getAsset = async () => {
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            jsonrpc: '2.0',
            id: 'my-id',
            method: 'getAsset',
            params: {
                id: 'AKo9P7S8FE9NYeAcrtZEpimwQAXJMp8Lrt8p4dMkHkY2',
                displayOptions: {
                    showInscription: true, // shows inscription and spl-20 data
                },
            },
        }),
    });
    const { result } = await response.json();
    console.log([result.inscription, result.spl20]);
};
getAsset();

// Example output:
// [
//     {
//         order: 308332,
//         size: 52,
//         contentType: 'application/text',
//         encoding: 'base64',
//         validationHash: '907e00a18f952ade319c21b90764e5d0a08ec31c92e792f806a995e8524535ca',
//         inscriptionDataAccount: '9qM9ThkVPxjq4TyBjCs1qpY15VYVim2Qh7uR5yG1Da3T',
//     },
//     { p: 'spl-20', op: 'mint', tick: 'helius', amt: '1' },
// ];

PreviousDigital Asset Standard (DAS) API
NextGet Asset Batch

Last updated 6 months ago