Skip to content

Commit c818986

Browse files
author
tilo-14
committed
Add devnet-spl-to-light-transfer.rs test
1 parent 90ed27f commit c818986

1 file changed

Lines changed: 285 additions & 0 deletions

File tree

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// SPL to light-token to light-token scenario test
2+
//
3+
// This test demonstrates the complete flow:
4+
// 1. Create SPL mint manually
5+
// 2. Create SPL interface PDA (token pool) using SDK instruction
6+
// 3. Create SPL token account
7+
// 4. Mint SPL tokens
8+
// 5. Create sender's cToken ATA (compressible)
9+
// 6. Transfer SPL tokens to cToken account
10+
// 7. Create recipient cATA + transfer cToken→cToken in SAME transaction
11+
// 8. Verify balances
12+
13+
use anchor_spl::token::{spl_token, Mint};
14+
use light_client::rpc::{LightClient, LightClientConfig, Rpc};
15+
use light_ctoken_sdk::{
16+
ctoken::{derive_ctoken_ata, CreateAssociatedCTokenAccount, TransferCToken, TransferSplToCtoken},
17+
spl_interface::{find_spl_interface_pda_with_index, CreateSplInterfacePda},
18+
};
19+
use serde_json;
20+
use solana_sdk::compute_budget::ComputeBudgetInstruction;
21+
use solana_sdk::program_pack::Pack;
22+
use solana_sdk::{signature::Keypair, signer::Signer};
23+
use spl_token_2022::pod::PodAccount;
24+
use std::convert::TryFrom;
25+
use std::env;
26+
use std::fs;
27+
28+
/// Test SPL → cToken → cToken flow with combined ATA creation + transfer
29+
#[tokio::test(flavor = "multi_thread")]
30+
async fn test_spl_to_ctoken_to_ctoken() {
31+
// 1. Setup test environment - load from .env
32+
dotenvy::dotenv().ok();
33+
34+
let keypair_path = env::var("KEYPAIR_PATH")
35+
.unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap()));
36+
let payer = load_keypair(&keypair_path).expect("Failed to load keypair");
37+
let api_key = env::var("api_key")
38+
.expect("api_key environment variable must be set. Create a .env file or set it in your environment.");
39+
40+
let config = LightClientConfig::devnet(
41+
Some("https://devnet.helius-rpc.com".to_string()),
42+
Some(api_key),
43+
);
44+
let mut rpc = LightClient::new_with_retry(config, None)
45+
.await
46+
.expect("Failed to initialize LightClient");
47+
48+
// 2. Create SPL mint manually
49+
let mint_keypair = Keypair::new();
50+
let mint = mint_keypair.pubkey();
51+
let decimals = 2u8;
52+
53+
// Get rent for mint account
54+
let mint_rent = rpc
55+
.get_minimum_balance_for_rent_exemption(Mint::LEN)
56+
.await
57+
.unwrap();
58+
59+
// Create mint account instruction
60+
let create_mint_account_ix = solana_sdk::system_instruction::create_account(
61+
&payer.pubkey(),
62+
&mint,
63+
mint_rent,
64+
Mint::LEN as u64,
65+
&spl_token::ID,
66+
);
67+
68+
// Initialize mint instruction
69+
let initialize_mint_ix = spl_token::instruction::initialize_mint(
70+
&spl_token::ID,
71+
&mint,
72+
&payer.pubkey(), // mint authority
73+
None, // freeze authority
74+
decimals,
75+
)
76+
.unwrap();
77+
78+
rpc.create_and_send_transaction(
79+
&[create_mint_account_ix, initialize_mint_ix],
80+
&payer.pubkey(),
81+
&[&payer, &mint_keypair],
82+
)
83+
.await
84+
.unwrap();
85+
86+
// 3. Create SPL interface PDA (token pool) using SDK instruction
87+
let create_spl_interface_pda_ix =
88+
CreateSplInterfacePda::new(payer.pubkey(), mint, anchor_spl::token::ID).instruction();
89+
90+
rpc.create_and_send_transaction(&[create_spl_interface_pda_ix], &payer.pubkey(), &[&payer])
91+
.await
92+
.unwrap();
93+
94+
let mint_amount = 10_000u64;
95+
let spl_to_ctoken_amount = 7_000u64;
96+
let ctoken_transfer_amount = 3_000u64;
97+
98+
// 4. Create SPL token account (inline)
99+
let spl_token_account_keypair = Keypair::new();
100+
let token_account_rent = rpc
101+
.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN)
102+
.await
103+
.unwrap();
104+
let create_token_account_ix = solana_sdk::system_instruction::create_account(
105+
&payer.pubkey(),
106+
&spl_token_account_keypair.pubkey(),
107+
token_account_rent,
108+
spl_token::state::Account::LEN as u64,
109+
&spl_token::ID,
110+
);
111+
let init_token_account_ix = spl_token::instruction::initialize_account(
112+
&spl_token::ID,
113+
&spl_token_account_keypair.pubkey(),
114+
&mint,
115+
&payer.pubkey(),
116+
)
117+
.unwrap();
118+
rpc.create_and_send_transaction(
119+
&[create_token_account_ix, init_token_account_ix],
120+
&payer.pubkey(),
121+
&[&spl_token_account_keypair, &payer],
122+
)
123+
.await
124+
.unwrap();
125+
126+
// 5. Mint SPL tokens to the SPL account (inline)
127+
let mint_to_ix = spl_token::instruction::mint_to(
128+
&spl_token::ID,
129+
&mint,
130+
&spl_token_account_keypair.pubkey(),
131+
&payer.pubkey(),
132+
&[&payer.pubkey()],
133+
mint_amount,
134+
)
135+
.unwrap();
136+
rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer])
137+
.await
138+
.unwrap();
139+
140+
// Verify SPL account has tokens
141+
let spl_account_data = rpc
142+
.get_account(spl_token_account_keypair.pubkey())
143+
.await
144+
.unwrap()
145+
.unwrap();
146+
let spl_account =
147+
spl_pod::bytemuck::pod_from_bytes::<PodAccount>(&spl_account_data.data).unwrap();
148+
let initial_spl_balance: u64 = spl_account.amount.into();
149+
assert_eq!(initial_spl_balance, mint_amount);
150+
151+
// 6. Create sender's cToken ATA (compressible with default 16 prepaid epochs)
152+
let (sender_ctoken_ata, _bump) = derive_ctoken_ata(&payer.pubkey(), &mint);
153+
let create_ata_instruction =
154+
CreateAssociatedCTokenAccount::new(payer.pubkey(), payer.pubkey(), mint)
155+
.instruction()
156+
.unwrap();
157+
158+
rpc.create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer])
159+
.await
160+
.unwrap();
161+
162+
// Verify sender's cToken ATA was created
163+
let ctoken_account_data = rpc.get_account(sender_ctoken_ata).await.unwrap().unwrap();
164+
assert!(
165+
!ctoken_account_data.data.is_empty(),
166+
"Sender cToken ATA should exist"
167+
);
168+
169+
// 7. Transfer SPL tokens to sender's cToken account
170+
let (spl_interface_pda, spl_interface_pda_bump) = find_spl_interface_pda_with_index(&mint, 0);
171+
172+
let spl_to_ctoken_ix = TransferSplToCtoken {
173+
amount: spl_to_ctoken_amount,
174+
spl_interface_pda_bump,
175+
source_spl_token_account: spl_token_account_keypair.pubkey(),
176+
destination_ctoken_account: sender_ctoken_ata,
177+
authority: payer.pubkey(),
178+
mint,
179+
payer: payer.pubkey(),
180+
spl_interface_pda,
181+
spl_token_program: anchor_spl::token::ID,
182+
}
183+
.instruction()
184+
.unwrap();
185+
186+
rpc.create_and_send_transaction(&[spl_to_ctoken_ix], &payer.pubkey(), &[&payer])
187+
.await
188+
.unwrap();
189+
190+
// 8. Create recipient cATA + transfer cToken→cToken in SAME transaction
191+
let recipient = Keypair::new();
192+
let (recipient_ctoken_ata, _) = derive_ctoken_ata(&recipient.pubkey(), &mint);
193+
194+
let create_recipient_ata_ix = CreateAssociatedCTokenAccount::new(
195+
payer.pubkey(),
196+
recipient.pubkey(),
197+
mint,
198+
)
199+
.instruction()
200+
.unwrap();
201+
202+
let ctoken_transfer_ix = TransferCToken {
203+
source: sender_ctoken_ata,
204+
destination: recipient_ctoken_ata,
205+
amount: ctoken_transfer_amount,
206+
authority: payer.pubkey(),
207+
max_top_up: None,
208+
}
209+
.instruction()
210+
.unwrap();
211+
212+
// COMBINED: create recipient ATA + transfer in one transaction
213+
let compute_unit_ix = ComputeBudgetInstruction::set_compute_unit_limit(10_000);
214+
let tx_id = rpc.create_and_send_transaction(
215+
&[compute_unit_ix, create_recipient_ata_ix, ctoken_transfer_ix],
216+
&payer.pubkey(),
217+
&[&payer],
218+
)
219+
.await
220+
.unwrap();
221+
println!("tx_id: {}", tx_id);
222+
223+
// 9. Verify results
224+
// Check SPL account balance decreased
225+
let spl_account_data = rpc
226+
.get_account(spl_token_account_keypair.pubkey())
227+
.await
228+
.unwrap()
229+
.unwrap();
230+
let spl_account =
231+
spl_pod::bytemuck::pod_from_bytes::<PodAccount>(&spl_account_data.data).unwrap();
232+
let final_spl_balance: u64 = spl_account.amount.into();
233+
assert_eq!(
234+
final_spl_balance,
235+
mint_amount - spl_to_ctoken_amount,
236+
"SPL account balance should be 3000"
237+
);
238+
239+
// Check sender cToken balance (7000 - 3000 = 4000)
240+
let sender_ctoken_data = rpc.get_account(sender_ctoken_ata).await.unwrap().unwrap();
241+
let sender_ctoken =
242+
spl_pod::bytemuck::pod_from_bytes::<PodAccount>(&sender_ctoken_data.data[..165]).unwrap();
243+
let sender_ctoken_balance: u64 = sender_ctoken.amount.into();
244+
assert_eq!(
245+
sender_ctoken_balance,
246+
spl_to_ctoken_amount - ctoken_transfer_amount,
247+
"Sender cToken balance should be 4000"
248+
);
249+
250+
// Check recipient cToken balance (3000)
251+
let recipient_ctoken_data = rpc.get_account(recipient_ctoken_ata).await.unwrap().unwrap();
252+
let recipient_ctoken =
253+
spl_pod::bytemuck::pod_from_bytes::<PodAccount>(&recipient_ctoken_data.data[..165]).unwrap();
254+
let recipient_ctoken_balance: u64 = recipient_ctoken.amount.into();
255+
assert_eq!(
256+
recipient_ctoken_balance, ctoken_transfer_amount,
257+
"Recipient cToken balance should be 3000"
258+
);
259+
260+
println!("SPL → cToken → cToken transfer completed!");
261+
println!(" - Created SPL mint: {}", mint);
262+
println!(" - Minted {} tokens to SPL account", mint_amount);
263+
println!(" - Transferred {} SPL → sender cToken", spl_to_ctoken_amount);
264+
println!(
265+
" - Transferred {} cToken → recipient cToken (in same tx as ATA creation)",
266+
ctoken_transfer_amount
267+
);
268+
println!("\nFinal balances:");
269+
println!(" - SPL account: {}", final_spl_balance);
270+
println!(" - Sender cToken: {}", sender_ctoken_balance);
271+
println!(" - Recipient cToken: {}", recipient_ctoken_balance);
272+
273+
println!("\nTest passed!");
274+
}
275+
276+
fn load_keypair(path: &str) -> Result<Keypair, Box<dyn std::error::Error>> {
277+
let path = if path.starts_with("~") {
278+
path.replace("~", &env::var("HOME").unwrap_or_default())
279+
} else {
280+
path.to_string()
281+
};
282+
let file = fs::read_to_string(&path)?;
283+
let bytes: Vec<u8> = serde_json::from_str(&file)?;
284+
Ok(Keypair::try_from(&bytes[..])?)
285+
}

0 commit comments

Comments
 (0)