extern crate self as light_instruction_decoder;
use light_instruction_decoder_derive::InstructionDecoder;
use light_token_interface::instructions::{
mint_action::MintActionCompressedInstructionData,
transfer2::CompressedTokenInstructionDataTransfer2,
};
use solana_instruction::AccountMeta;
#[cfg(not(target_os = "solana"))]
fn calculate_packed_accounts_start(data: &CompressedTokenInstructionDataTransfer2) -> usize {
let no_compressed_accounts = data.in_token_data.is_empty() && data.out_token_data.is_empty();
let cpi_context_write_required = data
.cpi_context
.as_ref()
.map(|ctx| ctx.set_context || ctx.first_set_context)
.unwrap_or(false);
if no_compressed_accounts {
2
} else if cpi_context_write_required {
4
} else {
let mut start = 7;
let in_lamports: u64 = data
.in_lamports
.as_ref()
.map(|v| v.iter().sum())
.unwrap_or(0);
let out_lamports: u64 = data
.out_lamports
.as_ref()
.map(|v| v.iter().sum())
.unwrap_or(0);
if in_lamports != out_lamports {
start += 1; }
if out_lamports > in_lamports {
start += 1; }
if data.cpi_context.is_some() {
start += 1; }
start
}
}
#[cfg(not(target_os = "solana"))]
pub fn format_transfer2(
data: &CompressedTokenInstructionDataTransfer2,
accounts: &[AccountMeta],
) -> String {
use std::fmt::Write;
let mut output = String::new();
let cpi_context_write_mode = data
.cpi_context
.as_ref()
.map(|ctx| ctx.set_context || ctx.first_set_context)
.unwrap_or(false);
let packed_accounts_start = calculate_packed_accounts_start(data);
let resolve = |index: u8| -> String {
if cpi_context_write_mode {
format!("packed[{}]", index)
} else {
accounts
.get(packed_accounts_start + index as usize)
.map(|a| a.pubkey.to_string())
.unwrap_or_else(|| format!("OUT_OF_BOUNDS({})", index))
}
};
if cpi_context_write_mode {
let _ = writeln!(
output,
"[CPI Context Write Mode - packed accounts in CPI context]"
);
}
let _ = writeln!(output, "output_queue: {}", resolve(data.output_queue));
if data.max_top_up != u16::MAX {
let _ = writeln!(output, "max_top_up: {}", data.max_top_up);
}
if data.with_transaction_hash {
let _ = writeln!(output, "with_transaction_hash: true");
}
let _ = writeln!(output, "Input Tokens ({}):", data.in_token_data.len());
for (i, token) in data.in_token_data.iter().enumerate() {
let _ = writeln!(output, " [{}]", i);
let _ = writeln!(output, " owner: {}", resolve(token.owner));
let _ = writeln!(output, " mint: {}", resolve(token.mint));
let _ = writeln!(output, " amount: {}", token.amount);
if token.has_delegate {
let _ = writeln!(output, " delegate: {}", resolve(token.delegate));
}
let _ = writeln!(output, " version: {}", token.version);
let _ = writeln!(
output,
" merkle_tree: {}",
resolve(token.merkle_context.merkle_tree_pubkey_index)
);
let _ = writeln!(
output,
" queue: {}",
resolve(token.merkle_context.queue_pubkey_index)
);
let _ = writeln!(
output,
" leaf_index: {}",
token.merkle_context.leaf_index
);
let _ = writeln!(output, " root_index: {}", token.root_index);
}
let _ = writeln!(output, "Output Tokens ({}):", data.out_token_data.len());
for (i, token) in data.out_token_data.iter().enumerate() {
let _ = writeln!(output, " [{}]", i);
let _ = writeln!(output, " owner: {}", resolve(token.owner));
let _ = writeln!(output, " mint: {}", resolve(token.mint));
let _ = writeln!(output, " amount: {}", token.amount);
if token.has_delegate {
let _ = writeln!(output, " delegate: {}", resolve(token.delegate));
}
let _ = writeln!(output, " version: {}", token.version);
}
if let Some(compressions) = &data.compressions {
let _ = writeln!(output, "Compressions ({}):", compressions.len());
for (i, comp) in compressions.iter().enumerate() {
let _ = writeln!(output, " [{}]", i);
let _ = writeln!(output, " mode: {:?}", comp.mode);
let _ = writeln!(output, " amount: {}", comp.amount);
let _ = writeln!(output, " mint: {}", resolve(comp.mint));
let _ = writeln!(
output,
" source_or_recipient: {}",
resolve(comp.source_or_recipient)
);
let _ = writeln!(output, " authority: {}", resolve(comp.authority));
}
}
output
}
#[cfg(not(target_os = "solana"))]
pub fn resolve_transfer2_account_names(
data: &CompressedTokenInstructionDataTransfer2,
accounts: &[AccountMeta],
) -> Vec<String> {
use std::collections::HashMap;
let mut names = Vec::with_capacity(accounts.len());
let mut idx = 0;
let mut known_pubkeys: HashMap<[u8; 32], String> = HashMap::new();
let mut add_name = |name: &str,
accounts: &[AccountMeta],
idx: &mut usize,
known: &mut HashMap<[u8; 32], String>| {
if *idx < accounts.len() {
names.push(name.to_string());
known.insert(accounts[*idx].pubkey.to_bytes(), name.to_string());
*idx += 1;
true
} else {
false
}
};
let no_compressed_accounts = data.in_token_data.is_empty() && data.out_token_data.is_empty();
let cpi_context_write_required = data
.cpi_context
.as_ref()
.map(|ctx| ctx.set_context || ctx.first_set_context)
.unwrap_or(false);
if no_compressed_accounts {
add_name(
"compressions_only_cpi_authority_pda",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name(
"compressions_only_fee_payer",
accounts,
&mut idx,
&mut known_pubkeys,
);
} else if cpi_context_write_required {
add_name(
"light_system_program",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name("fee_payer", accounts, &mut idx, &mut known_pubkeys);
add_name("cpi_authority_pda", accounts, &mut idx, &mut known_pubkeys);
add_name("cpi_context", accounts, &mut idx, &mut known_pubkeys);
return names;
} else {
add_name(
"light_system_program",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name("fee_payer", accounts, &mut idx, &mut known_pubkeys);
add_name("cpi_authority_pda", accounts, &mut idx, &mut known_pubkeys);
add_name(
"registered_program_pda",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name(
"account_compression_authority",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name(
"account_compression_program",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name("system_program", accounts, &mut idx, &mut known_pubkeys);
let in_lamports: u64 = data
.in_lamports
.as_ref()
.map(|v| v.iter().sum())
.unwrap_or(0);
let out_lamports: u64 = data
.out_lamports
.as_ref()
.map(|v| v.iter().sum())
.unwrap_or(0);
let with_sol_pool = in_lamports != out_lamports;
if with_sol_pool {
add_name("sol_pool_pda", accounts, &mut idx, &mut known_pubkeys);
}
let with_sol_decompression = out_lamports > in_lamports;
if with_sol_decompression {
add_name(
"sol_decompression_recipient",
accounts,
&mut idx,
&mut known_pubkeys,
);
}
if data.cpi_context.is_some() {
names.push(String::new()); idx += 1;
}
}
let mut packed_roles: HashMap<u8, String> = HashMap::new();
let mut owner_count = 0u8;
let mut mint_count = 0u8;
let mut delegate_count = 0u8;
let mut in_merkle_count = 0u8;
let mut in_queue_count = 0u8;
let mut compress_mint_count = 0u8;
let mut compress_source_count = 0u8;
let mut compress_auth_count = 0u8;
packed_roles
.entry(data.output_queue)
.or_insert_with(|| "output_queue".to_string());
for token in data.in_token_data.iter() {
packed_roles.entry(token.owner).or_insert_with(|| {
let name = if owner_count == 0 {
"owner".to_string()
} else {
format!("owner_{}", owner_count)
};
owner_count = owner_count.saturating_add(1);
name
});
packed_roles.entry(token.mint).or_insert_with(|| {
let name = if mint_count == 0 {
"mint".to_string()
} else {
format!("mint_{}", mint_count)
};
mint_count = mint_count.saturating_add(1);
name
});
if token.has_delegate {
packed_roles.entry(token.delegate).or_insert_with(|| {
let name = if delegate_count == 0 {
"delegate".to_string()
} else {
format!("delegate_{}", delegate_count)
};
delegate_count = delegate_count.saturating_add(1);
name
});
}
packed_roles
.entry(token.merkle_context.merkle_tree_pubkey_index)
.or_insert_with(|| {
let name = if in_merkle_count == 0 {
"in_merkle_tree".to_string()
} else {
format!("in_merkle_tree_{}", in_merkle_count)
};
in_merkle_count = in_merkle_count.saturating_add(1);
name
});
packed_roles
.entry(token.merkle_context.queue_pubkey_index)
.or_insert_with(|| {
let name = if in_queue_count == 0 {
"in_nullifier_queue".to_string()
} else {
format!("in_nullifier_queue_{}", in_queue_count)
};
in_queue_count = in_queue_count.saturating_add(1);
name
});
}
for token in data.out_token_data.iter() {
packed_roles.entry(token.owner).or_insert_with(|| {
let name = if owner_count == 0 {
"owner".to_string()
} else {
format!("owner_{}", owner_count)
};
owner_count = owner_count.saturating_add(1);
name
});
packed_roles.entry(token.mint).or_insert_with(|| {
let name = if mint_count == 0 {
"mint".to_string()
} else {
format!("mint_{}", mint_count)
};
mint_count = mint_count.saturating_add(1);
name
});
if token.has_delegate {
packed_roles.entry(token.delegate).or_insert_with(|| {
let name = if delegate_count == 0 {
"delegate".to_string()
} else {
format!("delegate_{}", delegate_count)
};
delegate_count = delegate_count.saturating_add(1);
name
});
}
}
if let Some(compressions) = &data.compressions {
for comp in compressions.iter() {
packed_roles.entry(comp.mint).or_insert_with(|| {
let name = if compress_mint_count == 0 {
"compress_mint".to_string()
} else {
format!("compress_mint_{}", compress_mint_count)
};
compress_mint_count = compress_mint_count.saturating_add(1);
name
});
packed_roles
.entry(comp.source_or_recipient)
.or_insert_with(|| {
let name = if compress_source_count == 0 {
"compress_source".to_string()
} else {
format!("compress_source_{}", compress_source_count)
};
compress_source_count = compress_source_count.saturating_add(1);
name
});
packed_roles.entry(comp.authority).or_insert_with(|| {
let name = if compress_auth_count == 0 {
"compress_authority".to_string()
} else {
format!("compress_authority_{}", compress_auth_count)
};
compress_auth_count = compress_auth_count.saturating_add(1);
name
});
}
}
let mut packed_idx: u8 = 0;
while idx < accounts.len() {
let pubkey_bytes = accounts[idx].pubkey.to_bytes();
if let Some(role) = packed_roles.get(&packed_idx) {
if let Some(known_name) = known_pubkeys.get(&pubkey_bytes) {
names.push(format!("{} (={})", role, known_name));
} else {
names.push(role.clone());
known_pubkeys.insert(pubkey_bytes, role.clone());
}
} else if let Some(known_name) = known_pubkeys.get(&pubkey_bytes) {
names.push(format!("packed_{} (={})", packed_idx, known_name));
} else {
names.push(format!("packed_account_{}", packed_idx));
}
idx += 1;
packed_idx = packed_idx.saturating_add(1);
}
names
}
#[cfg(not(target_os = "solana"))]
pub fn resolve_mint_action_account_names(
data: &MintActionCompressedInstructionData,
accounts: &[AccountMeta],
) -> Vec<String> {
use std::collections::HashMap;
use light_token_interface::instructions::mint_action::Action;
let mut names = Vec::with_capacity(accounts.len());
let mut idx = 0;
let mut known_pubkeys: HashMap<[u8; 32], String> = HashMap::new();
let mut add_name = |name: &str,
accounts: &[AccountMeta],
idx: &mut usize,
known: &mut HashMap<[u8; 32], String>| {
if *idx < accounts.len() {
names.push(name.to_string());
known.insert(accounts[*idx].pubkey.to_bytes(), name.to_string());
*idx += 1;
true
} else {
false
}
};
add_name(
"light_system_program",
accounts,
&mut idx,
&mut known_pubkeys,
);
if data.create_mint.is_some() {
add_name("mint_signer", accounts, &mut idx, &mut known_pubkeys);
}
add_name("authority", accounts, &mut idx, &mut known_pubkeys);
let write_to_cpi_context = data
.cpi_context
.as_ref()
.map(|ctx| ctx.first_set_context || ctx.set_context)
.unwrap_or(false);
if write_to_cpi_context {
add_name("fee_payer", accounts, &mut idx, &mut known_pubkeys);
add_name("cpi_authority_pda", accounts, &mut idx, &mut known_pubkeys);
add_name("cpi_context", accounts, &mut idx, &mut known_pubkeys);
} else {
let has_decompress_mint_action = data
.actions
.iter()
.any(|action| matches!(action, Action::DecompressMint(_)));
let has_compress_and_close_cmint_action = data
.actions
.iter()
.any(|action| matches!(action, Action::CompressAndCloseMint(_)));
let needs_compressible_accounts =
has_decompress_mint_action || has_compress_and_close_cmint_action;
let cmint_decompressed = data.mint.is_none();
let needs_cmint_account =
cmint_decompressed || has_decompress_mint_action || has_compress_and_close_cmint_action;
if needs_compressible_accounts {
add_name(
"compressible_config",
accounts,
&mut idx,
&mut known_pubkeys,
);
}
if needs_cmint_account {
add_name("cmint", accounts, &mut idx, &mut known_pubkeys);
}
if needs_compressible_accounts {
add_name("rent_sponsor", accounts, &mut idx, &mut known_pubkeys);
}
add_name("fee_payer", accounts, &mut idx, &mut known_pubkeys);
add_name("cpi_authority_pda", accounts, &mut idx, &mut known_pubkeys);
add_name(
"registered_program_pda",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name(
"account_compression_authority",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name(
"account_compression_program",
accounts,
&mut idx,
&mut known_pubkeys,
);
add_name("system_program", accounts, &mut idx, &mut known_pubkeys);
}
names
}
#[cfg(not(target_os = "solana"))]
fn calculate_mint_action_packed_accounts_start(
data: &MintActionCompressedInstructionData,
) -> usize {
let cpi_context_write_mode = data
.cpi_context
.as_ref()
.map(|ctx| ctx.set_context || ctx.first_set_context)
.unwrap_or(false);
if cpi_context_write_mode {
3
} else {
let mut start = 6;
if data.cpi_context.is_some() {
start += 1; }
start
}
}
#[cfg(not(target_os = "solana"))]
pub fn format_mint_action(
data: &MintActionCompressedInstructionData,
accounts: &[AccountMeta],
) -> String {
use std::fmt::Write;
use light_token_interface::instructions::mint_action::Action;
let mut output = String::new();
let cpi_context_write_mode = data
.cpi_context
.as_ref()
.map(|ctx| ctx.set_context || ctx.first_set_context)
.unwrap_or(false);
let packed_accounts_start = calculate_mint_action_packed_accounts_start(data);
let resolve = |index: u8| -> String {
if cpi_context_write_mode {
format!("packed[{}]", index)
} else {
accounts
.get(packed_accounts_start + index as usize)
.map(|a| a.pubkey.to_string())
.unwrap_or_else(|| format!("OUT_OF_BOUNDS({})", index))
}
};
if cpi_context_write_mode {
let _ = writeln!(
output,
"[CPI Context Write Mode - packed accounts in CPI context]"
);
}
if data.create_mint.is_some() {
let _ = writeln!(output, "create_mint: true");
} else {
let _ = writeln!(output, "leaf_index: {}", data.leaf_index);
if data.prove_by_index {
let _ = writeln!(output, "prove_by_index: true");
}
}
let _ = writeln!(output, "root_index: {}", data.root_index);
if data.max_top_up != u16::MAX {
let _ = writeln!(output, "max_top_up: {}", data.max_top_up);
}
if let Some(mint) = &data.mint {
let _ = writeln!(output, "Mint:");
let _ = writeln!(output, " supply: {}", mint.supply);
let _ = writeln!(output, " decimals: {}", mint.decimals);
if let Some(auth) = &mint.mint_authority {
let _ = writeln!(
output,
" mint_authority: {}",
bs58::encode(auth).into_string()
);
}
if let Some(auth) = &mint.freeze_authority {
let _ = writeln!(
output,
" freeze_authority: {}",
bs58::encode(auth).into_string()
);
}
if let Some(exts) = &mint.extensions {
let _ = writeln!(output, " extensions: {}", exts.len());
}
}
let _ = writeln!(output, "Actions ({}):", data.actions.len());
for (i, action) in data.actions.iter().enumerate() {
match action {
Action::MintToCompressed(a) => {
let _ = writeln!(output, " [{}] MintToCompressed:", i);
let _ = writeln!(output, " version: {}", a.token_account_version);
for (j, r) in a.recipients.iter().enumerate() {
let _ = writeln!(
output,
" recipient[{}]: {} amount: {}",
j,
bs58::encode(&r.recipient).into_string(),
r.amount
);
}
}
Action::UpdateMintAuthority(a) => {
let authority_str = a
.new_authority
.as_ref()
.map(|p| bs58::encode(p).into_string())
.unwrap_or_else(|| "None".to_string());
let _ = writeln!(output, " [{}] UpdateMintAuthority: {}", i, authority_str);
}
Action::UpdateFreezeAuthority(a) => {
let authority_str = a
.new_authority
.as_ref()
.map(|p| bs58::encode(p).into_string())
.unwrap_or_else(|| "None".to_string());
let _ = writeln!(output, " [{}] UpdateFreezeAuthority: {}", i, authority_str);
}
Action::MintTo(a) => {
let _ = writeln!(
output,
" [{}] MintTo: account: {}, amount: {}",
i,
resolve(a.account_index),
a.amount
);
}
Action::UpdateMetadataField(a) => {
let field_name = match a.field_type {
0 => "Name",
1 => "Symbol",
2 => "Uri",
_ => "Custom",
};
let _ = writeln!(
output,
" [{}] UpdateMetadataField: ext[{}] {} = {:?}",
i,
a.extension_index,
field_name,
String::from_utf8_lossy(&a.value)
);
}
Action::UpdateMetadataAuthority(a) => {
let _ = writeln!(
output,
" [{}] UpdateMetadataAuthority: ext[{}] = {}",
i,
a.extension_index,
bs58::encode(&a.new_authority).into_string()
);
}
Action::RemoveMetadataKey(a) => {
let _ = writeln!(
output,
" [{}] RemoveMetadataKey: ext[{}] key={:?} idempotent={}",
i,
a.extension_index,
String::from_utf8_lossy(&a.key),
a.idempotent != 0
);
}
Action::DecompressMint(a) => {
let _ = writeln!(
output,
" [{}] DecompressMint: rent_payment={} write_top_up={}",
i, a.rent_payment, a.write_top_up
);
}
Action::CompressAndCloseMint(a) => {
let _ = writeln!(
output,
" [{}] CompressAndCloseMint: idempotent={}",
i,
a.idempotent != 0
);
}
}
}
if let Some(ctx) = &data.cpi_context {
let _ = writeln!(output, "CPI Context:");
let _ = writeln!(
output,
" mode: {}",
if ctx.first_set_context {
"first_set_context"
} else if ctx.set_context {
"set_context"
} else {
"read"
}
);
let _ = writeln!(output, " in_tree: packed[{}]", ctx.in_tree_index);
let _ = writeln!(output, " in_queue: packed[{}]", ctx.in_queue_index);
let _ = writeln!(output, " out_queue: packed[{}]", ctx.out_queue_index);
if ctx.token_out_queue_index > 0 {
let _ = writeln!(
output,
" token_out_queue: packed[{}]",
ctx.token_out_queue_index
);
}
let _ = writeln!(
output,
" address_tree: {}",
bs58::encode(&ctx.address_tree_pubkey).into_string()
);
}
output
}
#[derive(InstructionDecoder)]
#[instruction_decoder(
program_id = "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m",
program_name = "Light Token",
discriminator_size = 1
)]
pub enum CTokenInstruction {
#[discriminator = 3]
#[instruction_decoder(account_names = ["source", "destination", "authority"])]
Transfer { amount: u64 },
#[discriminator = 4]
#[instruction_decoder(account_names = ["source", "delegate", "owner"])]
Approve { amount: u64 },
#[discriminator = 5]
#[instruction_decoder(account_names = ["source", "owner"])]
Revoke,
#[discriminator = 7]
#[instruction_decoder(account_names = ["cmint", "destination", "authority"])]
MintTo { amount: u64 },
#[discriminator = 8]
#[instruction_decoder(account_names = ["source", "cmint", "authority"])]
Burn { amount: u64 },
#[discriminator = 9]
#[instruction_decoder(account_names = ["account", "destination", "authority"])]
CloseTokenAccount,
#[discriminator = 10]
#[instruction_decoder(account_names = ["account", "mint", "authority"])]
FreezeAccount,
#[discriminator = 11]
#[instruction_decoder(account_names = ["account", "mint", "authority"])]
ThawAccount,
#[discriminator = 12]
#[instruction_decoder(account_names = ["source", "mint", "destination", "authority"])]
TransferChecked { amount: u64, decimals: u8 },
#[discriminator = 14]
#[instruction_decoder(account_names = ["cmint", "destination", "authority"])]
MintToChecked { amount: u64, decimals: u8 },
#[discriminator = 15]
#[instruction_decoder(account_names = ["source", "cmint", "authority"])]
BurnChecked { amount: u64, decimals: u8 },
#[discriminator = 18]
#[instruction_decoder(account_names = ["token_account", "mint", "payer", "config", "system_program", "rent_payer"])]
CreateTokenAccount,
#[discriminator = 100]
#[instruction_decoder(account_names = ["owner", "mint", "fee_payer", "ata", "system_program", "config", "rent_payer"])]
CreateAssociatedTokenAccount,
#[discriminator = 101]
#[instruction_decoder(
params = CompressedTokenInstructionDataTransfer2,
account_names_resolver_from_params = crate::programs::ctoken::resolve_transfer2_account_names,
pretty_formatter = crate::programs::ctoken::format_transfer2
)]
Transfer2,
#[discriminator = 102]
#[instruction_decoder(account_names = ["owner", "mint", "fee_payer", "ata", "system_program", "config", "rent_payer"])]
CreateAssociatedTokenAccountIdempotent,
#[discriminator = 103]
#[instruction_decoder(
params = MintActionCompressedInstructionData,
account_names_resolver_from_params = crate::programs::ctoken::resolve_mint_action_account_names,
pretty_formatter = crate::programs::ctoken::format_mint_action
)]
MintAction,
#[discriminator = 104]
#[instruction_decoder(account_names = ["forester", "ctoken_account", "rent_recipient", "config"])]
Claim,
#[discriminator = 105]
#[instruction_decoder(account_names = ["authority", "rent_recipient", "config", "destination"])]
WithdrawFundingPool,
}