| Event | Description |
|---|---|
| New mints | Raw mint data |
| Mint updates | Supply changes, authority changes |
| TokenMetadata | Name, symbol, URI, additional_metadata |
Find devnet examples at c-token-toolkit/streaming-mints.
Setup
Cargo.toml
Report incorrect code
Copy
Ask AI
[dependencies]
helius-laserstream = "0.1"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
light-event = "0.2"
light-compressed-account = "0.7"
light-ctoken-interface = "0.1"
borsh = "0.10"
bs58 = "0.5"
Report incorrect code
Copy
Ask AI
use futures::StreamExt;
use helius_laserstream::{subscribe, LaserstreamConfig};
use light_event::parse::event_from_light_transaction;
use light_ctoken_interface::state::mint::CompressedMint;
use light_ctoken_interface::state::extensions::ExtensionStruct;
use borsh::BorshDeserialize;
const LIGHT_SYSTEM_PROGRAM: &str = "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7";
const CTOKEN_PROGRAM_ID: &str = "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m";
const COMPRESSED_MINT_DISCRIMINATOR: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1];
1
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
let request = helius_laserstream::grpc::SubscribeRequest {
transactions: [(
"light".to_string(),
helius_laserstream::grpc::SubscribeRequestFilterTransactions {
vote: Some(false),
failed: Some(false),
account_include: vec![LIGHT_SYSTEM_PROGRAM.to_string()],
..Default::default()
},
)]
.into(),
..Default::default()
};
let (stream, _handle) = subscribe(config, request);
tokio::pin!(stream);
while let Some(update) = stream.next().await {
// Process transactions...
}
2
Parse Events
Report incorrect code
Copy
Ask AI
fn process_transaction(msg: &Message, meta: Option<&TransactionStatusMeta>) {
let account_keys = extract_account_keys(msg, meta);
let (program_ids, instruction_data, accounts_per_ix) =
extract_instructions(msg, meta, &account_keys);
match event_from_light_transaction(&program_ids, &instruction_data, accounts_per_ix) {
Ok(Some(batches)) => {
for batch in batches {
println!(
"Event: {} inputs, {} outputs",
batch.event.input_compressed_account_hashes.len(),
batch.event.output_compressed_accounts.len()
);
}
}
Ok(None) => {} // No compressed account events
Err(e) => eprintln!("Parse error: {:?}", e),
}
}
3
Extract Mints
Report incorrect code
Copy
Ask AI
for output in event.output_compressed_accounts.iter() {
let owner = output.compressed_account.owner;
// Check if owned by cToken program
if owner != ctoken_program_id {
continue;
}
// Check discriminator
let data = match &output.compressed_account.data {
Some(d) if d.discriminator == COMPRESSED_MINT_DISCRIMINATOR => &d.data,
_ => continue,
};
// Deserialize
let mint = CompressedMint::try_from_slice(data)?;
// Check if new (address not in inputs)
let is_new = output
.compressed_account
.address
.map(|addr| {
!event.input_compressed_account_hashes.iter().any(|h| *h == addr)
})
.unwrap_or(true);
if is_new {
println!("New mint: {}", bs58::encode(mint.metadata.mint).into_string());
}
}
4
Extract Token Metadata from Mint Extensions
Report incorrect code
Copy
Ask AI
fn extract_metadata(mint: &CompressedMint) -> 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 CompressedMint {
pub base: BaseMint,
pub metadata: CompressedMintMetadata,
pub extensions: Option<Vec<ExtensionStruct>>,
}
/// SPL compatible basemint structure (82 bytes)
#[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>,
}
/// metadata used by the cToken program (34 bytes)
#[repr(C)]
pub struct CompressedMintMetadata {
pub version: u8,
pub spl_mint_initialized: bool,
pub mint: Pubkey, // PDA with compressed mint seed
}
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>,
}