Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 31 additions & 20 deletions sdk-libs/token-client/src/actions/approve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use light_client::rpc::{Rpc, RpcError};
use light_token::instruction::Approve as ApproveInstruction;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_pubkey::Pubkey;
use solana_signature::Signature;
Expand Down Expand Up @@ -45,7 +46,33 @@ pub struct Approve {
pub owner: Option<Pubkey>,
}

pub fn create_approve_instructions(
approve: &Approve,
fee_payer: Pubkey,
owner: Pubkey,
) -> Result<Vec<Instruction>, RpcError> {
let ix = ApproveInstruction {
token_account: approve.token_account,
delegate: approve.delegate,
owner,
amount: approve.amount,
fee_payer,
}
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;

Ok(vec![ix])
}

impl Approve {
pub fn instructions(
&self,
fee_payer: Pubkey,
owner: Pubkey,
) -> Result<Vec<Instruction>, RpcError> {
create_approve_instructions(self, fee_payer, owner)
}

/// Execute the approve action via RPC where payer is the owner.
///
/// This method only supports cases where `owner == payer`. If you need a
Expand Down Expand Up @@ -74,17 +101,9 @@ impl Approve {
));
}

let ix = ApproveInstruction {
token_account: self.token_account,
delegate: self.delegate,
owner: owner_pubkey,
amount: self.amount,
fee_payer: payer.pubkey(),
}
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;
let instructions = create_approve_instructions(&self, payer.pubkey(), owner_pubkey)?;

rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer])
rpc.create_and_send_transaction(&instructions, &payer.pubkey(), &[payer])
.await
}

Expand Down Expand Up @@ -117,22 +136,14 @@ impl Approve {
}
}

let ix = ApproveInstruction {
token_account: self.token_account,
delegate: self.delegate,
owner: owner.pubkey(),
amount: self.amount,
fee_payer: payer.pubkey(),
}
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;
let instructions = create_approve_instructions(&self, payer.pubkey(), owner.pubkey())?;

let mut signers: Vec<&Keypair> = vec![payer];
if owner.pubkey() != payer.pubkey() {
signers.push(owner);
}

rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers)
rpc.create_and_send_transaction(&instructions, &payer.pubkey(), &signers)
.await
}
}
36 changes: 25 additions & 11 deletions sdk-libs/token-client/src/actions/create_ata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use light_client::rpc::{Rpc, RpcError};
use light_token::instruction::{get_associated_token_address, CreateAssociatedTokenAccount};
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_pubkey::Pubkey;
use solana_signature::Signature;
Expand Down Expand Up @@ -37,7 +38,29 @@ pub struct CreateAta {
pub idempotent: bool,
}

pub fn create_ata_instructions(
create_ata: &CreateAta,
fee_payer: Pubkey,
) -> Result<Vec<Instruction>, RpcError> {
let mut instruction_builder =
CreateAssociatedTokenAccount::new(fee_payer, create_ata.owner, create_ata.mint);

if create_ata.idempotent {
instruction_builder = instruction_builder.idempotent();
}

let ix = instruction_builder
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;

Ok(vec![ix])
}

impl CreateAta {
pub fn instructions(&self, fee_payer: Pubkey) -> Result<Vec<Instruction>, RpcError> {
create_ata_instructions(self, fee_payer)
}

/// Execute the create_ata action via RPC.
///
/// # Arguments
Expand All @@ -51,19 +74,10 @@ impl CreateAta {
rpc: &mut R,
payer: &Keypair,
) -> Result<(Signature, Pubkey), RpcError> {
let mut instruction_builder =
CreateAssociatedTokenAccount::new(payer.pubkey(), self.owner, self.mint);

if self.idempotent {
instruction_builder = instruction_builder.idempotent();
}

let ix = instruction_builder
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;
let instructions = create_ata_instructions(&self, payer.pubkey())?;

let signature = rpc
.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer])
.create_and_send_transaction(&instructions, &payer.pubkey(), &[payer])
.await?;

Ok((
Expand Down
109 changes: 107 additions & 2 deletions sdk-libs/token-client/src/actions/create_mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use light_token_interface::{
instructions::extensions::{ExtensionInstructionData, TokenMetadataInstructionData},
state::AdditionalMetadata,
};
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_pubkey::Pubkey;
use solana_signature::Signature;
Expand Down Expand Up @@ -66,7 +67,111 @@ pub struct CreateMint {
pub seed: Option<Keypair>,
}

pub async fn create_mint_instructions<R: Rpc + Indexer>(
rpc: &R,
create_mint: &CreateMint,
payer: Pubkey,
mint_authority: Pubkey,
) -> Result<Vec<Instruction>, RpcError> {
let mint_seed_pubkey = create_mint.seed.as_ref().map(|seed| seed.pubkey()).ok_or_else(|| {
RpcError::CustomError(
"create_mint_instructions requires CreateMint.seed = Some(Keypair) so caller can sign; use execute() for auto-generated seed".to_string(),
)
})?;

let address_tree = rpc.get_address_tree_v2();
let output_queue = rpc.get_random_state_tree_info()?.queue;

// Derive compression address
let compression_address = derive_mint_compressed_address(&mint_seed_pubkey, &address_tree.tree);

// Find mint PDA
let (mint, bump) = find_mint_address(&mint_seed_pubkey);

// Get validity proof for the address
let rpc_result = rpc
.get_validity_proof(
vec![],
vec![AddressWithTree {
address: compression_address,
tree: address_tree.tree,
}],
None,
)
.await
.map_err(|e| RpcError::CustomError(format!("Failed to get validity proof: {}", e)))?
.value;

// Build extensions if token metadata is provided
let extensions = create_mint.token_metadata.as_ref().map(|metadata| {
let additional_metadata = metadata.additional_metadata.as_ref().map(|items| {
items
.iter()
.map(|(key, value)| AdditionalMetadata {
key: key.clone().into_bytes(),
value: value.clone().into_bytes(),
})
.collect()
});

vec![ExtensionInstructionData::TokenMetadata(
TokenMetadataInstructionData {
update_authority: Some(
metadata
.update_authority
.unwrap_or(mint_authority)
.to_bytes()
.into(),
),
name: metadata.name.clone().into_bytes(),
symbol: metadata.symbol.clone().into_bytes(),
uri: metadata.uri.clone().into_bytes(),
additional_metadata,
},
)]
});

// Build params
let params = CreateMintInstructionParams {
decimals: create_mint.decimals,
address_merkle_tree_root_index: rpc_result.addresses[0].root_index,
mint_authority,
proof: rpc_result.proof.0.ok_or_else(|| {
RpcError::CustomError("Validity proof is required for create_mint".to_string())
})?,
compression_address,
mint,
bump,
freeze_authority: create_mint.freeze_authority,
extensions,
rent_payment: 16, // ~24 hours rent
write_top_up: 766, // ~3 hours per write
};

// Create instruction
let instruction = CreateMintInstruction::new(
params,
mint_seed_pubkey,
payer,
address_tree.tree,
output_queue,
)
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;

Ok(vec![instruction])
}

impl CreateMint {
pub async fn instructions<R: Rpc + Indexer>(
&self,
rpc: &R,
payer: Pubkey,
mint_authority: Pubkey,
) -> Result<Vec<Instruction>, RpcError> {
create_mint_instructions(rpc, self, payer, mint_authority).await
}

/// Execute the create_mint action via RPC.
///
/// # Arguments
Expand Down Expand Up @@ -161,8 +266,8 @@ impl CreateMint {
address_tree.tree,
output_queue,
)
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;
.instruction()
.map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;

// Build signers list
let mut signers: Vec<&Keypair> = vec![payer, &mint_seed];
Expand Down
Loading
Loading