| Event | Description |
|---|---|
| New mints | Raw mint data |
| Mint updates | Supply changes, authority changes |
| TokenMetadata | Name, symbol, URI, additional_metadata |
Find devnet examples here.
Setup
Light mints are Solana accounts owned by the Light Token Program. Subscribe to account updates to detect new mints and changes.Cargo.toml
Report incorrect code
Copy
Ask AI
[dependencies]
helius-laserstream = "0.1"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
anyhow = "1"
dotenvy = "0.15"
bs58 = "0.5"
borsh = "0.10"
light-token-interface = "0.3.0"
light-compressed-account = { version = "0.9.0", features = ["std"] }
# Pin blake3 to avoid constant_time_eq edition2024 issue
blake3 = "=1.5.5"
Report incorrect code
Copy
Ask AI
use borsh::BorshDeserialize;
use futures::StreamExt;
use helius_laserstream::grpc::subscribe_request_filter_accounts_filter::Filter;
use helius_laserstream::grpc::subscribe_request_filter_accounts_filter_memcmp::Data;
use helius_laserstream::grpc::{
SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter,
SubscribeRequestFilterAccountsFilterMemcmp,
};
use helius_laserstream::{subscribe, LaserstreamConfig};
use light_token_interface::state::{ExtensionStruct, Mint};
const LIGHT_TOKEN_PROGRAM_ID: &str = "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m";
/// Byte offset of account_type in the Mint account data.
/// BaseMint (82) + MintMetadata (67) + reserved (16) = 165
const ACCOUNT_TYPE_OFFSET: u64 = 165;
Connect to Laserstream
- Mainnet
- Devnet
Report incorrect code
Copy
Ask AI
let config = LaserstreamConfig::new(
"https://laserstream-mainnet-ewr.helius-rpc.com".to_string(),
std::env::var("HELIUS_API_KEY")?,
);
Report incorrect code
Copy
Ask AI
let config = LaserstreamConfig::new(
"https://laserstream-devnet-ewr.helius-rpc.com".to_string(),
std::env::var("HELIUS_API_KEY")?,
);
Report incorrect code
Copy
Ask AI
// Subscribe to Solana accounts owned by the Light Token Program
// with account_type = 1 (Mint) at byte offset 165
let request = helius_laserstream::grpc::SubscribeRequest {
accounts: [(
"light_mints".to_string(),
SubscribeRequestFilterAccounts {
owner: vec![LIGHT_TOKEN_PROGRAM_ID.to_string()],
filters: vec![SubscribeRequestFilterAccountsFilter {
filter: Some(Filter::Memcmp(SubscribeRequestFilterAccountsFilterMemcmp {
offset: ACCOUNT_TYPE_OFFSET,
data: Some(Data::Bytes(vec![1])),
})),
}],
nonempty_txn_signature: Some(true),
..Default::default()
},
)]
.into(),
..Default::default()
};
let (stream, _handle) = subscribe(config, request);
tokio::pin!(stream);
while let Some(update) = stream.next().await {
// Process account updates...
}
Deserialize mint accounts
Report incorrect code
Copy
Ask AI
if let Some(helius_laserstream::grpc::subscribe_update::UpdateOneof::Account(
account_update,
)) = msg.update_oneof
{
if let Some(account_info) = account_update.account {
let pubkey = bs58::encode(&account_info.pubkey).into_string();
let tx_sig = account_info
.txn_signature
.as_ref()
.map(|s| bs58::encode(s).into_string())
.unwrap_or_default();
match Mint::deserialize(&mut account_info.data.as_slice()) {
Ok(mint) => {
println!("Mint: {}", pubkey);
println!(" tx: {}", tx_sig);
println!(" decimals: {}", mint.base.decimals);
println!(" supply: {}", mint.base.supply);
if let Some(authority) = &mint.base.mint_authority {
println!(
" mint_authority: {}",
bs58::encode(authority.to_bytes()).into_string()
);
}
if let Some(authority) = &mint.base.freeze_authority {
println!(
" freeze_authority: {}",
bs58::encode(authority.to_bytes()).into_string()
);
}
}
Err(e) => {
eprintln!("Failed to deserialize mint {}: {}", pubkey, e);
}
}
}
}
Extract Token Metadata from mint extensions
Report incorrect code
Copy
Ask AI
fn extract_metadata(mint: &Mint) -> Option<(String, String, String)> {
let extensions = mint.extensions.as_ref()?;
for ext in extensions {
if let ExtensionStruct::TokenMetadata(m) = ext {
let name = String::from_utf8_lossy(&m.name).to_string();
let symbol = String::from_utf8_lossy(&m.symbol).to_string();
let uri = String::from_utf8_lossy(&m.uri).to_string();
return Some((name, symbol, uri));
}
}
None
}
Report incorrect code
Copy
Ask AI
if let Some((name, symbol, uri)) = extract_metadata(&mint) {
println!(" Name: {}", name);
println!(" Symbol: {}", symbol);
println!(" URI: {}", uri);
}
Data Layouts
Mint
Report incorrect code
Copy
Ask AI
#[repr(C)]
pub struct Mint {
pub base: BaseMint,
pub metadata: MintMetadata,
pub reserved: [u8; 16],
pub account_type: u8,
pub compression: CompressionInfo,
pub extensions: Option<Vec<ExtensionStruct>>,
}
/// SPL-compatible base mint structure
#[repr(C)]
pub struct BaseMint {
pub mint_authority: Option<Pubkey>,
pub supply: u64,
pub decimals: u8,
pub is_initialized: bool,
pub freeze_authority: Option<Pubkey>,
}
/// Light Protocol metadata for mints (67 bytes)
#[repr(C)]
pub struct MintMetadata {
pub version: u8,
pub mint_decompressed: bool,
pub mint: Pubkey,
pub mint_signer: [u8; 32],
pub bump: u8,
}
TokenMetadata
Report incorrect code
Copy
Ask AI
#[repr(C)]
pub struct TokenMetadata {
pub update_authority: Pubkey, // [0u8; 32] = immutable
pub mint: Pubkey,
pub name: Vec<u8>,
pub symbol: Vec<u8>,
pub uri: Vec<u8>,
pub additional_metadata: Vec<AdditionalMetadata>,
}
pub struct AdditionalMetadata {
pub key: Vec<u8>,
pub value: Vec<u8>,
}