Skip to content

Commit bed4749

Browse files
committed
token transfer works
1 parent a0047f2 commit bed4749

File tree

17 files changed

+264
-69
lines changed

17 files changed

+264
-69
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

program-tests/sdk-token-test/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ serial_test = { workspace = true }
3030
solana-sdk = { workspace = true }
3131
anchor-spl = { workspace = true }
3232
light-sdk = { workspace = true }
33+
light-compressed-account = { workspace = true, features = ["anchor"] }
34+
light-client = { workspace = true }
3335

3436
[lints.rust.unexpected_cfgs]
3537
level = "allow"

program-tests/sdk-token-test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub mod sdk_token_test {
8585
// TODO: add to program error conversion
8686
let instruction =
8787
create_compressed_token_instruction(cpi_inputs, &light_cpi_accounts).unwrap();
88+
msg!("instruction created {:?}", instruction);
8889
let account_infos = light_cpi_accounts.to_account_infos();
8990

9091
// TODO: make invoke_signed

program-tests/sdk-token-test/tests/test.rs

Lines changed: 159 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@ use anchor_spl::token::TokenAccount;
55
use light_compressed_token_sdk::{
66
instruction::{get_transfer_instruction_account_metas, TokenAccountsMetaConfig},
77
token_pool::get_token_pool_pda,
8+
InputTokenDataWithContext, PackedMerkleContext,
89
};
9-
use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc};
10+
use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc};
1011
use light_sdk::instruction::PackedAccounts;
1112
use light_test_utils::{
1213
spl::{create_mint_helper, create_token_account, mint_spl_tokens},
1314
RpcError,
1415
};
1516
use solana_sdk::{
16-
instruction::{AccountMeta, Instruction},
17+
instruction::Instruction,
1718
pubkey::Pubkey,
1819
signature::{Keypair, Signature, Signer},
1920
};
2021

22+
use light_client::indexer::CompressedTokenAccount;
23+
2124
#[tokio::test]
22-
async fn test_compress_spl_tokens() {
25+
async fn test() {
2326
// Initialize the test environment
2427
let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(
2528
false,
@@ -78,12 +81,16 @@ async fn test_compress_spl_tokens() {
7881

7982
// Now compress the SPL tokens
8083
let compress_amount = 500_000; // Compress half of the tokens
81-
let recipient = payer.pubkey(); // Compress to the same owner
84+
let compression_recipient = payer.pubkey(); // Compress to the same owner
85+
86+
// Declare transfer parameters early
87+
let transfer_recipient = Keypair::new();
88+
let transfer_amount = 10;
8289

8390
compress_spl_tokens(
8491
&mut rpc,
8592
&payer,
86-
recipient,
93+
compression_recipient,
8794
mint_pubkey,
8895
compress_amount,
8996
token_account_keypair.pubkey(),
@@ -93,11 +100,87 @@ async fn test_compress_spl_tokens() {
93100

94101
println!("Compressed {} tokens successfully", compress_amount);
95102

96-
// TODO: Add verification of compressed token accounts
97-
// This would require checking the compressed account state tree
98-
// and verifying the token balance was reduced in the SPL account
103+
// Get the compressed token account from indexer
104+
let compressed_accounts = rpc
105+
.indexer()
106+
.unwrap()
107+
.get_compressed_token_accounts_by_owner(&payer.pubkey(), None, None)
108+
.await
109+
.unwrap()
110+
.value
111+
.items;
112+
113+
let compressed_account = &compressed_accounts[0];
114+
115+
// Assert the compressed token account properties
116+
assert_eq!(compressed_account.token.owner, payer.pubkey());
117+
assert_eq!(compressed_account.token.mint, mint_pubkey);
118+
119+
// Verify the token amount (should match the compressed amount)
120+
let amount = compressed_account.token.amount;
121+
assert_eq!(amount, compress_amount);
122+
123+
println!(
124+
"Verified compressed token account: owner={}, mint={}, amount={}",
125+
payer.pubkey(),
126+
mint_pubkey,
127+
amount
128+
);
129+
println!("compressed_account {:?}", compressed_account);
130+
// Now transfer some compressed tokens to a recipient
131+
transfer_compressed_tokens(
132+
&mut rpc,
133+
&payer,
134+
transfer_recipient.pubkey(),
135+
compressed_account,
136+
)
137+
.await
138+
.unwrap();
139+
140+
println!(
141+
"Transferred {} compressed tokens to recipient successfully",
142+
transfer_amount
143+
);
144+
145+
// Verify the transfer by checking both sender and recipient accounts
146+
let updated_accounts = rpc
147+
.indexer()
148+
.unwrap()
149+
.get_compressed_token_accounts_by_owner(&payer.pubkey(), None, None)
150+
.await
151+
.unwrap()
152+
.value
153+
.items;
154+
155+
let recipient_accounts = rpc
156+
.indexer()
157+
.unwrap()
158+
.get_compressed_token_accounts_by_owner(&transfer_recipient.pubkey(), None, None)
159+
.await
160+
.unwrap()
161+
.value
162+
.items;
163+
164+
// Sender should have (compress_amount - transfer_amount) remaining
165+
if !updated_accounts.is_empty() {
166+
let sender_account = &updated_accounts[0];
167+
let sender_amount = sender_account.token.amount;
168+
assert_eq!(sender_amount, compress_amount - transfer_amount);
169+
println!("Verified sender remaining balance: {}", sender_amount);
170+
}
171+
172+
// Recipient should have transfer_amount
173+
assert!(
174+
!recipient_accounts.is_empty(),
175+
"Recipient should have compressed token account"
176+
);
177+
let recipient_account = &recipient_accounts[0];
178+
assert_eq!(recipient_account.token.owner, transfer_recipient.pubkey());
179+
let recipient_amount = recipient_account.token.amount;
180+
assert_eq!(recipient_amount, transfer_amount);
181+
println!("Verified recipient balance: {}", recipient_amount);
99182

100-
println!("Compression test completed successfully!");
183+
println!("Compression and transfer test completed successfully!");
101184
}
102185

103186
async fn compress_spl_tokens(
@@ -147,3 +230,70 @@ async fn compress_spl_tokens(
147230
rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer])
148231
.await
149232
}
233+
234+
async fn transfer_compressed_tokens(
235+
rpc: &mut LightProgramTest,
236+
payer: &Keypair,
237+
recipient: Pubkey,
238+
compressed_account: &CompressedTokenAccount,
239+
) -> Result<Signature, RpcError> {
240+
let mut remaining_accounts = PackedAccounts::default();
241+
let config = TokenAccountsMetaConfig::new(payer.pubkey(), payer.pubkey());
242+
remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey());
243+
let metas = get_transfer_instruction_account_metas(config);
244+
remaining_accounts.add_pre_accounts_metas(metas.as_slice());
245+
246+
// Get validity proof from RPC
247+
let rpc_result = rpc
248+
.get_validity_proof(vec![compressed_account.account.hash], vec![], None)
249+
.await?
250+
.value;
251+
252+
let packed_tree_info = rpc_result.pack_tree_infos(&mut remaining_accounts);
253+
let output_tree_index = packed_tree_info
254+
.state_trees
255+
.as_ref()
256+
.unwrap()
257+
.output_tree_index;
258+
259+
// Use the tree info from the validity proof result
260+
let tree_info = packed_tree_info
261+
.state_trees
262+
.as_ref()
263+
.unwrap()
264+
.packed_tree_infos[0];
265+
println!("Transfer tree_info: {:?}", tree_info);
266+
267+
// Create input token data
268+
let token_data = vec![InputTokenDataWithContext {
269+
amount: compressed_account.token.amount,
270+
delegate_index: None,
271+
merkle_context: PackedMerkleContext {
272+
merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
273+
nullifier_queue_pubkey_index: tree_info.queue_pubkey_index,
274+
leaf_index: tree_info.leaf_index,
275+
proof_by_index: tree_info.prove_by_index,
276+
},
277+
root_index: tree_info.root_index,
278+
lamports: None,
279+
tlv: None,
280+
}];
281+
282+
let (remaining_accounts, _, _) = remaining_accounts.to_account_metas();
283+
println!("remaining_accounts {:?}", remaining_accounts);
284+
let instruction = Instruction {
285+
program_id: sdk_token_test::ID,
286+
accounts: [remaining_accounts].concat(),
287+
data: sdk_token_test::instruction::Transfer {
288+
validity_proof: rpc_result.proof,
289+
token_data,
290+
output_tree_index,
291+
mint: compressed_account.token.mint,
292+
recipient,
293+
}
294+
.data(),
295+
};
296+
297+
rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer])
298+
.await
299+
}

sdk-libs/client/src/indexer/indexer_trait.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use solana_pubkey::Pubkey;
55
use super::{
66
response::{Items, ItemsWithCursor, Response},
77
types::{
8-
CompressedAccount, OwnerBalance, SignatureWithMetadata, TokenAccount, TokenBalance,
9-
ValidityProofWithContext,
8+
CompressedAccount, CompressedTokenAccount, OwnerBalance, SignatureWithMetadata,
9+
TokenBalance, ValidityProofWithContext,
1010
},
1111
Address, AddressWithTree, BatchAddressUpdateIndexerResponse,
1212
GetCompressedAccountsByOwnerConfig, GetCompressedTokenAccountsByOwnerOrDelegateOptions, Hash,
@@ -75,14 +75,14 @@ pub trait Indexer: std::marker::Send + std::marker::Sync {
7575
delegate: &Pubkey,
7676
options: Option<GetCompressedTokenAccountsByOwnerOrDelegateOptions>,
7777
config: Option<IndexerRpcConfig>,
78-
) -> Result<Response<ItemsWithCursor<TokenAccount>>, IndexerError>;
78+
) -> Result<Response<ItemsWithCursor<CompressedTokenAccount>>, IndexerError>;
7979

8080
async fn get_compressed_token_accounts_by_owner(
8181
&self,
8282
owner: &Pubkey,
8383
options: Option<GetCompressedTokenAccountsByOwnerOrDelegateOptions>,
8484
config: Option<IndexerRpcConfig>,
85-
) -> Result<Response<ItemsWithCursor<TokenAccount>>, IndexerError>;
85+
) -> Result<Response<ItemsWithCursor<CompressedTokenAccount>>, IndexerError>;
8686

8787
/// Returns the token balances for a given owner.
8888
async fn get_compressed_token_balances_by_owner_v2(

sdk-libs/client/src/indexer/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ pub use indexer_trait::Indexer;
1515
pub use response::{Context, Items, ItemsWithCursor, Response};
1616
pub use types::{
1717
AccountProofInputs, Address, AddressMerkleTreeAccounts, AddressProofInputs, AddressQueueIndex,
18-
AddressWithTree, BatchAddressUpdateIndexerResponse, CompressedAccount, Hash, MerkleProof,
19-
MerkleProofWithContext, NewAddressProofWithContext, NextTreeInfo, OwnerBalance, ProofOfLeaf,
20-
RootIndex, SignatureWithMetadata, StateMerkleTreeAccounts, TokenAccount, TokenBalance,
21-
TreeInfo, ValidityProofWithContext,
18+
AddressWithTree, BatchAddressUpdateIndexerResponse, CompressedAccount, CompressedTokenAccount,
19+
Hash, MerkleProof, MerkleProofWithContext, NewAddressProofWithContext, NextTreeInfo,
20+
OwnerBalance, ProofOfLeaf, RootIndex, SignatureWithMetadata, StateMerkleTreeAccounts,
21+
TokenBalance, TreeInfo, ValidityProofWithContext,
2222
};
2323
mod options;
2424
pub use options::*;

sdk-libs/client/src/indexer/photon_indexer.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ use solana_pubkey::Pubkey;
1111
use tracing::{debug, error, warn};
1212

1313
use super::{
14-
types::{CompressedAccount, OwnerBalance, SignatureWithMetadata, TokenAccount, TokenBalance},
14+
types::{
15+
CompressedAccount, CompressedTokenAccount, OwnerBalance, SignatureWithMetadata,
16+
TokenBalance,
17+
},
1518
BatchAddressUpdateIndexerResponse, MerkleProofWithContext,
1619
};
1720
use crate::indexer::{
@@ -543,7 +546,7 @@ impl Indexer for PhotonIndexer {
543546
delegate: &Pubkey,
544547
options: Option<GetCompressedTokenAccountsByOwnerOrDelegateOptions>,
545548
config: Option<IndexerRpcConfig>,
546-
) -> Result<Response<ItemsWithCursor<TokenAccount>>, IndexerError> {
549+
) -> Result<Response<ItemsWithCursor<CompressedTokenAccount>>, IndexerError> {
547550
let config = config.unwrap_or_default();
548551
self.retry(config.retry_config, || async {
549552
#[cfg(feature = "v2")]
@@ -575,7 +578,7 @@ impl Indexer for PhotonIndexer {
575578
.value
576579
.items
577580
.iter()
578-
.map(TokenAccount::try_from)
581+
.map(CompressedTokenAccount::try_from)
579582
.collect();
580583

581584
let cursor = response.value.cursor;
@@ -619,7 +622,7 @@ impl Indexer for PhotonIndexer {
619622
.value
620623
.items
621624
.iter()
622-
.map(TokenAccount::try_from)
625+
.map(CompressedTokenAccount::try_from)
623626
.collect();
624627

625628
let cursor = response.value.cursor;
@@ -643,7 +646,7 @@ impl Indexer for PhotonIndexer {
643646
owner: &Pubkey,
644647
options: Option<GetCompressedTokenAccountsByOwnerOrDelegateOptions>,
645648
config: Option<IndexerRpcConfig>,
646-
) -> Result<Response<ItemsWithCursor<TokenAccount>>, IndexerError> {
649+
) -> Result<Response<ItemsWithCursor<CompressedTokenAccount>>, IndexerError> {
647650
let config = config.unwrap_or_default();
648651
self.retry(config.retry_config, || async {
649652
#[cfg(feature = "v2")]
@@ -676,7 +679,7 @@ impl Indexer for PhotonIndexer {
676679
.value
677680
.items
678681
.iter()
679-
.map(TokenAccount::try_from)
682+
.map(CompressedTokenAccount::try_from)
680683
.collect();
681684

682685
let cursor = response.value.cursor;
@@ -727,7 +730,7 @@ impl Indexer for PhotonIndexer {
727730
.value
728731
.items
729732
.iter()
730-
.map(TokenAccount::try_from)
733+
.map(CompressedTokenAccount::try_from)
731734
.collect();
732735

733736
let cursor = response.value.cursor;

0 commit comments

Comments
 (0)