Skip to content

Commit 106ad7c

Browse files
committed
mint to spl works
1 parent 3aae061 commit 106ad7c

File tree

16 files changed

+613
-15
lines changed

16 files changed

+613
-15
lines changed

Cargo.lock

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ members = [
2626
"sdk-libs/photon-api",
2727
"sdk-libs/program-test",
2828
"sdk-libs/compressed-token-types",
29+
"sdk-libs/compressed-token-sdk",
2930
"xtask",
3031
"examples/anchor/token-escrow",
3132
# "examples/anchor/name-service-without-macros",
@@ -41,6 +42,7 @@ members = [
4142
# Issue is that anchor discriminator now returns a slice instead of an array
4243
"program-tests/sdk-anchor-test/programs/sdk-anchor-test",
4344
"program-tests/sdk-test",
45+
"program-tests/sdk-token-test",
4446
"program-tests/sdk-pinocchio-test",
4547
"program-tests/create-address-test-program",
4648
"program-tests/utils",
@@ -181,6 +183,7 @@ light-compressed-token = { path = "programs/compressed-token", version = "1.2.0"
181183
"cpi",
182184
] }
183185
light-compressed-token-types = { path = "sdk-libs/compressed-token-types", name = "light-compressed-token-types" }
186+
light-compressed-token-sdk = { path = "sdk-libs/compressed-token-sdk" }
184187
light-system-program-anchor = { path = "anchor-programs/system", version = "1.2.0", features = [
185188
"cpi",
186189
] }
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[package]
2+
name = "sdk-token-test"
3+
version = "1.0.0"
4+
description = "Test program using compressed token SDK"
5+
repository = "https://github.com/Lightprotocol/light-protocol"
6+
license = "Apache-2.0"
7+
edition = "2021"
8+
9+
[lib]
10+
crate-type = ["cdylib", "lib"]
11+
name = "sdk_token_test"
12+
13+
[features]
14+
no-entrypoint = []
15+
no-idl = []
16+
no-log-ix-name = []
17+
cpi = ["no-entrypoint"]
18+
test-sbf = []
19+
default = []
20+
21+
[dependencies]
22+
light-compressed-token-sdk = { workspace = true }
23+
light-sdk = { workspace = true }
24+
25+
[dev-dependencies]
26+
light-program-test = { workspace = true, features = ["devenv"] }
27+
light-test-utils = { workspace = true }
28+
tokio = { workspace = true }
29+
serial_test = { workspace = true }
30+
solana-sdk = { workspace = true }
31+
anchor-lang = { workspace = true }
32+
anchor-spl = { workspace = true }
33+
34+
[lints.rust.unexpected_cfgs]
35+
level = "allow"
36+
check-cfg = [
37+
'cfg(target_os, values("solana"))',
38+
'cfg(feature, values("frozen-abi", "no-entrypoint"))',
39+
]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// use light_macros::pubkey;
2+
// use light_sdk::{cpi::CpiSigner, derive_light_cpi_signer};
3+
// use solana_program::{
4+
// account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey,
5+
// };
6+
7+
// pub const ID: Pubkey = pubkey!("5p1t1GAaKtK1FKCh5Hd2Gu8JCu3eREhJm4Q2qYfTEPYK");
8+
// pub const LIGHT_CPI_SIGNER: CpiSigner
9+
// entrypoint!(process_instruction);
10+
11+
// pub fn process_instruction(
12+
// _program_id: &Pubkey,
13+
// _accounts: &[AccountInfo],
14+
// _instruction_data: &[u8],
15+
// ) -> Result<(), ProgramError> {
16+
// // Empty program for now
17+
// Ok(())
18+
// }
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// #![cfg(feature = "test-sbf")]
2+
3+
use anchor_lang::AccountDeserialize;
4+
use anchor_spl::token::TokenAccount;
5+
use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc};
6+
use light_test_utils::spl::{create_mint_helper, create_token_account, mint_spl_tokens};
7+
use solana_sdk::{signature::Keypair, signer::Signer};
8+
9+
//#[serial]
10+
#[tokio::test]
11+
async fn test_create_token_account_and_mint() {
12+
// Initialize the test environment
13+
let mut rpc = LightProgramTest::new(ProgramTestConfig::new(false, None))
14+
.await
15+
.unwrap();
16+
17+
let payer = rpc.get_payer().insecure_clone();
18+
19+
// Create a mint
20+
let mint_pubkey = create_mint_helper(&mut rpc, &payer).await;
21+
println!("Created mint: {}", mint_pubkey);
22+
23+
// Create a token account
24+
let token_account_keypair = Keypair::new();
25+
26+
create_token_account(&mut rpc, &mint_pubkey, &token_account_keypair, &payer)
27+
.await
28+
.unwrap();
29+
30+
println!("Created token account: {}", token_account_keypair.pubkey());
31+
32+
// Mint some tokens to the account
33+
let mint_amount = 1_000_000; // 1000 tokens with 6 decimals
34+
35+
mint_spl_tokens(
36+
&mut rpc,
37+
&mint_pubkey,
38+
&token_account_keypair.pubkey(),
39+
&payer.pubkey(), // owner
40+
&payer, // mint authority
41+
mint_amount,
42+
false, // not token22
43+
)
44+
.await
45+
.unwrap();
46+
47+
println!("Minted {} tokens to account", mint_amount);
48+
49+
// Verify the token account has the correct balance
50+
let token_account_data = rpc
51+
.get_account(token_account_keypair.pubkey())
52+
.await
53+
.unwrap()
54+
.unwrap();
55+
56+
let token_account =
57+
TokenAccount::try_deserialize(&mut token_account_data.data.as_slice()).unwrap();
58+
59+
assert_eq!(token_account.amount, mint_amount);
60+
assert_eq!(token_account.mint, mint_pubkey);
61+
assert_eq!(token_account.owner, payer.pubkey());
62+
63+
println!("Test completed successfully!");
64+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "light-compressed-token-sdk"
3+
version = { workspace = true }
4+
edition = { workspace = true }
5+
6+
[features]
7+
8+
anchor = ["anchor-lang", "light-compressed-token-types/anchor"]
9+
10+
[dependencies]
11+
# Light Protocol dependencies
12+
light-compressed-token-types = { workspace = true }
13+
light-macros = { workspace = true }
14+
thiserror = { workspace = true }
15+
# Serialization
16+
borsh = { workspace = true }
17+
18+
# Solana dependencies
19+
solana-pubkey = { workspace = true }
20+
solana-instruction = { workspace = true }
21+
22+
# Optional Anchor dependency
23+
anchor-lang = { workspace = true, optional = true }
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use std::ops::Deref;
2+
3+
use crate::error::CTokenSdkError;
4+
use light_compressed_token_types::{InputTokenDataWithContext, PackedTokenTransferOutputData};
5+
use solana_pubkey::Pubkey;
6+
7+
/// Compress, decompress, new
8+
/// Questions:
9+
/// 1. do we need to implement compress?
10+
#[derive(Debug, PartialEq)]
11+
pub struct CTokenAccount {
12+
inputs: Vec<InputTokenDataWithContext>,
13+
output: PackedTokenTransferOutputData,
14+
compression_amount: Option<u64>,
15+
is_compress: bool,
16+
is_decompress: bool,
17+
}
18+
19+
impl CTokenAccount {
20+
pub fn new(
21+
owner: Pubkey,
22+
token_data: Vec<InputTokenDataWithContext>,
23+
output_merkle_tree_index: u8,
24+
) -> Self {
25+
let amount = token_data.iter().map(|data| data.amount).sum();
26+
let lamports = token_data.iter().map(|data| data.lamports).sum();
27+
let output = PackedTokenTransferOutputData {
28+
owner: owner.to_bytes(),
29+
amount,
30+
lamports,
31+
tlv: None,
32+
merkle_tree_index: output_merkle_tree_index,
33+
};
34+
Self {
35+
inputs: token_data,
36+
output,
37+
compression_amount: None,
38+
is_compress: false,
39+
is_decompress: false,
40+
}
41+
}
42+
43+
pub fn new_empty(owner: Pubkey, output_merkle_tree_index: u8) -> Self {
44+
Self {
45+
inputs: vec![],
46+
output: PackedTokenTransferOutputData {
47+
owner: owner.to_bytes(),
48+
amount: 0,
49+
lamports: None,
50+
tlv: None,
51+
merkle_tree_index: output_merkle_tree_index,
52+
},
53+
compression_amount: None,
54+
is_compress: false,
55+
is_decompress: false,
56+
}
57+
}
58+
59+
pub fn transfer(
60+
&mut self,
61+
recipient: &Pubkey,
62+
amount: u64,
63+
output_merkle_tree_index: Option<u8>,
64+
) -> Result<Self, CTokenSdkError> {
65+
if amount > self.output.amount {
66+
return Err(CTokenSdkError::InsufficientBalance);
67+
}
68+
// TODO: skip outputs with zero amount when creating the instruction data.
69+
self.output.amount -= amount;
70+
let merkle_tree_index = output_merkle_tree_index.unwrap_or(self.output.merkle_tree_index);
71+
72+
Ok(Self {
73+
compression_amount: None,
74+
is_compress: false,
75+
is_decompress: false,
76+
inputs: vec![],
77+
output: PackedTokenTransferOutputData {
78+
owner: recipient.to_bytes(),
79+
amount,
80+
lamports: None,
81+
tlv: None,
82+
merkle_tree_index,
83+
},
84+
})
85+
}
86+
87+
pub fn compress(&mut self, amount: u64) -> Result<(), CTokenSdkError> {
88+
self.output.amount += amount;
89+
self.is_compress = true;
90+
91+
match self.compression_amount.as_mut() {
92+
Some(amount_ref) => *amount_ref += amount,
93+
None => self.compression_amount = Some(amount),
94+
}
95+
Ok(())
96+
}
97+
98+
pub fn decompress(&mut self, amount: u64) -> Result<(), CTokenSdkError> {
99+
self.output.amount -= amount;
100+
self.is_decompress = true;
101+
102+
match self.compression_amount.as_mut() {
103+
Some(amount_ref) => *amount_ref -= amount,
104+
None => self.compression_amount = Some(amount),
105+
}
106+
Ok(())
107+
}
108+
109+
/// Consumes token account for instruction creation.
110+
pub fn into_inputs_and_outputs(
111+
self,
112+
) -> (
113+
Vec<InputTokenDataWithContext>,
114+
PackedTokenTransferOutputData,
115+
) {
116+
(self.inputs, self.output)
117+
}
118+
119+
// /// 1. Serializes the account data and sets the output data hash.
120+
// /// 2. Returns CompressedAccountInfo.
121+
// ///
122+
// /// Note this is an expensive operation
123+
// /// that should only be called once per instruction.
124+
// pub fn to_account_info(mut self) -> Result<CompressedAccountInfo, LightSdkError> {
125+
// if let Some(output) = self.account_info.output.as_mut() {
126+
// output.data_hash = self.account.hash::<Poseidon>()?;
127+
// output.data = self
128+
// .account
129+
// .try_to_vec()
130+
// .map_err(|_| LightSdkError::Borsh)?;
131+
// }
132+
// Ok(self.account_info)
133+
// }
134+
// }
135+
}
136+
137+
impl Deref for CTokenAccount {
138+
type Target = PackedTokenTransferOutputData;
139+
140+
fn deref(&self) -> &Self::Target {
141+
&self.output
142+
}
143+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use thiserror::Error;
2+
3+
/// Error type for the Light Compressed Token SDK
4+
#[derive(Debug, Error)]
5+
pub enum CTokenSdkError {
6+
#[error("Insufficient balance")]
7+
InsufficientBalance,
8+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod token_accounts;
2+
3+
// Re-export all instruction utilities
4+
pub use token_accounts::*;

0 commit comments

Comments
 (0)