light_token_client/actions/transfer_interface.rs
1//! Transfer interface action for Light Token.
2//!
3//! This action provides a clean interface for transferring tokens that auto-routes
4//! based on the account types (Light or SPL).
5
6use light_client::rpc::{Rpc, RpcError};
7use light_token::{
8 instruction::{SplInterface, TransferInterface as TransferInterfaceInstruction},
9 spl_interface::find_spl_interface_pda_with_index,
10};
11use solana_keypair::Keypair;
12use solana_pubkey::Pubkey;
13use solana_signature::Signature;
14use solana_signer::Signer;
15
16/// Parameters for transferring tokens using the interface that auto-routes based on account types.
17///
18/// This automatically detects whether the source and destination are
19/// Light token accounts or SPL token accounts and routes the transfer accordingly:
20/// - Light -> Light: Direct transfer between Light accounts
21/// - Light -> SPL: Decompress from Light to SPL account
22/// - SPL -> Light: Compress from SPL to Light account
23/// - SPL -> SPL: Pass-through to SPL token program
24///
25/// # Example
26/// ```ignore
27/// TransferInterface {
28/// source,
29/// mint,
30/// destination,
31/// amount: 1000,
32/// decimals: 9,
33/// ..Default::default()
34/// }.execute(&mut rpc, &payer, &authority).await?;
35/// ```
36#[derive(Default, Clone, Debug)]
37pub struct TransferInterface {
38 /// Source token account.
39 pub source: Pubkey,
40 /// The mint public key.
41 pub mint: Pubkey,
42 /// Destination token account.
43 pub destination: Pubkey,
44 /// Amount of tokens to transfer.
45 pub amount: u64,
46 /// Token decimals.
47 pub decimals: u8,
48 /// SPL token program (spl_token::ID or spl_token_2022::ID), required for cross-interface transfers.
49 pub spl_token_program: Option<Pubkey>,
50 /// Whether the mint has restricted extensions (Token-2022 specific).
51 pub restricted: bool,
52}
53
54impl TransferInterface {
55 /// Execute the transfer_interface action via RPC.
56 ///
57 /// # Arguments
58 /// * `rpc` - RPC client
59 /// * `payer` - Transaction fee payer keypair
60 /// * `authority` - Authority that can spend from the source account
61 ///
62 /// # Returns
63 /// `Result<Signature, RpcError>` - The transaction signature
64 pub async fn execute<R: Rpc>(
65 self,
66 rpc: &mut R,
67 payer: &Keypair,
68 authority: &Keypair,
69 ) -> Result<Signature, RpcError> {
70 // Fetch account info to determine owners
71 let source_account = rpc.get_account(self.source).await?.ok_or_else(|| {
72 RpcError::CustomError(format!("Source account {} not found", self.source))
73 })?;
74
75 let destination_account = rpc.get_account(self.destination).await?.ok_or_else(|| {
76 RpcError::CustomError(format!(
77 "Destination account {} not found",
78 self.destination
79 ))
80 })?;
81
82 let source_owner = source_account.owner;
83 let destination_owner = destination_account.owner;
84
85 // Build SplInterface if needed for cross-interface transfers
86 let spl_interface = if let Some(spl_program) = self.spl_token_program {
87 let (spl_interface_pda, spl_interface_pda_bump) =
88 find_spl_interface_pda_with_index(&self.mint, 0, self.restricted);
89 Some(SplInterface {
90 mint: self.mint,
91 spl_token_program: spl_program,
92 spl_interface_pda,
93 spl_interface_pda_bump,
94 })
95 } else {
96 None
97 };
98
99 let ix = TransferInterfaceInstruction {
100 source: self.source,
101 destination: self.destination,
102 amount: self.amount,
103 decimals: self.decimals,
104 authority: authority.pubkey(),
105 payer: payer.pubkey(),
106 spl_interface,
107 max_top_up: None,
108 source_owner,
109 destination_owner,
110 }
111 .instruction()
112 .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;
113
114 let mut signers = vec![payer];
115 if authority.pubkey() != payer.pubkey() {
116 signers.push(authority);
117 }
118
119 rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers)
120 .await
121 }
122}