Skip to content

Commit fbc331b

Browse files
tokens: add Quasar ports for create-token, transfer-tokens, and escrow
create-token: mint creation and minting (no Metaplex metadata) transfer-tokens: mint_to and transfer SPL token operations escrow: full make/take/refund with PDA vault, has_one checks, close All three build with quasar build and pass cargo test.
1 parent 58de8b9 commit fbc331b

17 files changed

Lines changed: 1124 additions & 0 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "quasar-create-token"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# Standalone workspace — not part of the root program-examples workspace.
7+
# Quasar uses a different resolver and dependency tree.
8+
[workspace]
9+
10+
[lints.rust.unexpected_cfgs]
11+
level = "warn"
12+
check-cfg = [
13+
'cfg(target_os, values("solana"))',
14+
]
15+
16+
[lib]
17+
crate-type = ["cdylib", "lib"]
18+
19+
[features]
20+
alloc = []
21+
client = []
22+
debug = []
23+
24+
[dependencies]
25+
quasar-lang = "0.0"
26+
quasar-spl = "0.0"
27+
solana-instruction = { version = "3.2.0" }
28+
29+
[dev-dependencies]
30+
quasar-svm = { version = "0.1" }
31+
spl-token-interface = { version = "2.0.0" }
32+
solana-program-pack = { version = "3.1.0" }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[project]
2+
name = "quasar_create_token"
3+
4+
[toolchain]
5+
type = "solana"
6+
7+
[testing]
8+
language = "rust"
9+
10+
[testing.rust]
11+
framework = "quasar-svm"
12+
13+
[testing.rust.test]
14+
program = "cargo"
15+
args = [
16+
"test",
17+
"tests::",
18+
]
19+
20+
[clients]
21+
languages = ["rust"]
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#![cfg_attr(not(test), no_std)]
2+
3+
use quasar_lang::prelude::*;
4+
use quasar_spl::{Mint, Token, TokenCpi};
5+
6+
#[cfg(test)]
7+
mod tests;
8+
9+
declare_id!("22222222222222222222222222222222222222222222");
10+
11+
/// Creates a token mint and mints initial tokens to the creator's token account.
12+
///
13+
/// The Anchor version uses Metaplex for on-chain metadata. Quasar does not have
14+
/// a Metaplex integration crate, so this example focuses on the core SPL Token
15+
/// operations: creating a mint and minting tokens.
16+
#[program]
17+
mod quasar_create_token {
18+
use super::*;
19+
20+
/// Create a new token mint (account init handled by Quasar's `#[account(init)]`).
21+
#[instruction(discriminator = 0)]
22+
pub fn create_token(ctx: Ctx<CreateToken>, _decimals: u8) -> Result<(), ProgramError> {
23+
ctx.accounts.create_token()
24+
}
25+
26+
/// Mint tokens to the creator's token account.
27+
#[instruction(discriminator = 1)]
28+
pub fn mint_tokens(ctx: Ctx<MintTokens>, amount: u64) -> Result<(), ProgramError> {
29+
ctx.accounts.mint_tokens(amount)
30+
}
31+
}
32+
33+
/// Accounts for creating a new token mint.
34+
/// Quasar's `#[account(init)]` handles the create_account + initialize_mint CPI.
35+
#[derive(Accounts)]
36+
pub struct CreateToken<'info> {
37+
#[account(mut)]
38+
pub payer: &'info Signer,
39+
#[account(mut, init, payer = payer, mint::decimals = 9, mint::authority = payer)]
40+
pub mint: &'info mut Account<Mint>,
41+
pub rent: &'info Sysvar<Rent>,
42+
pub token_program: &'info Program<Token>,
43+
pub system_program: &'info Program<System>,
44+
}
45+
46+
impl CreateToken<'_> {
47+
#[inline(always)]
48+
pub fn create_token(&self) -> Result<(), ProgramError> {
49+
// Mint account is created and initialised by Quasar's account init.
50+
Ok(())
51+
}
52+
}
53+
54+
/// Accounts for minting tokens to an existing token account.
55+
#[derive(Accounts)]
56+
pub struct MintTokens<'info> {
57+
#[account(mut)]
58+
pub authority: &'info Signer,
59+
#[account(mut)]
60+
pub mint: &'info mut Account<Mint>,
61+
#[account(mut)]
62+
pub token_account: &'info mut Account<Token>,
63+
pub token_program: &'info Program<Token>,
64+
}
65+
66+
impl MintTokens<'_> {
67+
#[inline(always)]
68+
pub fn mint_tokens(&mut self, amount: u64) -> Result<(), ProgramError> {
69+
self.token_program
70+
.mint_to(self.mint, self.token_account, self.authority, amount)
71+
.invoke()
72+
}
73+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
extern crate std;
2+
use {
3+
alloc::vec,
4+
quasar_svm::{Account, Instruction, Pubkey, QuasarSvm},
5+
spl_token_interface::state::{Account as TokenAccount, AccountState, Mint},
6+
std::println,
7+
};
8+
9+
fn setup() -> QuasarSvm {
10+
let elf = std::fs::read("target/deploy/quasar_create_token.so").unwrap();
11+
QuasarSvm::new()
12+
.with_program(&crate::ID, &elf)
13+
.with_token_program()
14+
}
15+
16+
fn signer(address: Pubkey) -> Account {
17+
quasar_svm::token::create_keyed_system_account(&address, 1_000_000_000)
18+
}
19+
20+
fn empty(address: Pubkey) -> Account {
21+
Account {
22+
address,
23+
lamports: 0,
24+
data: vec![],
25+
owner: quasar_svm::system_program::ID,
26+
executable: false,
27+
}
28+
}
29+
30+
fn mint(address: Pubkey, authority: Pubkey) -> Account {
31+
quasar_svm::token::create_keyed_mint_account(
32+
&address,
33+
&Mint {
34+
mint_authority: Some(authority).into(),
35+
supply: 0,
36+
decimals: 9,
37+
is_initialized: true,
38+
freeze_authority: None.into(),
39+
},
40+
)
41+
}
42+
43+
fn token_account(address: Pubkey, mint: Pubkey, owner: Pubkey, amount: u64) -> Account {
44+
quasar_svm::token::create_keyed_token_account(
45+
&address,
46+
&TokenAccount {
47+
mint,
48+
owner,
49+
amount,
50+
state: AccountState::Initialized,
51+
..TokenAccount::default()
52+
},
53+
)
54+
}
55+
56+
/// Mark specific account indices as signers.
57+
fn with_signers(mut ix: Instruction, indices: &[usize]) -> Instruction {
58+
for &i in indices {
59+
ix.accounts[i].is_signer = true;
60+
}
61+
ix
62+
}
63+
64+
/// Build create_token instruction data.
65+
/// Wire format: [discriminator: u8 = 0] [decimals: u8]
66+
fn build_create_token_data(decimals: u8) -> Vec<u8> {
67+
vec![0u8, decimals]
68+
}
69+
70+
/// Build mint_tokens instruction data.
71+
/// Wire format: [discriminator: u8 = 1] [amount: u64 LE]
72+
fn build_mint_tokens_data(amount: u64) -> Vec<u8> {
73+
let mut data = vec![1u8];
74+
data.extend_from_slice(&amount.to_le_bytes());
75+
data
76+
}
77+
78+
#[test]
79+
fn test_create_token() {
80+
let mut svm = setup();
81+
82+
let payer = Pubkey::new_unique();
83+
let mint_address = Pubkey::new_unique();
84+
let token_program = quasar_svm::SPL_TOKEN_PROGRAM_ID;
85+
let system_program = quasar_svm::system_program::ID;
86+
let rent = quasar_svm::solana_sdk_ids::sysvar::rent::ID;
87+
88+
let data = build_create_token_data(9);
89+
90+
let instruction = Instruction {
91+
program_id: crate::ID,
92+
accounts: vec![
93+
solana_instruction::AccountMeta::new(payer.into(), true),
94+
solana_instruction::AccountMeta::new(mint_address.into(), true),
95+
solana_instruction::AccountMeta::new_readonly(rent.into(), false),
96+
solana_instruction::AccountMeta::new_readonly(token_program.into(), false),
97+
solana_instruction::AccountMeta::new_readonly(system_program.into(), false),
98+
],
99+
data,
100+
};
101+
102+
let result = svm.process_instruction(
103+
&instruction,
104+
&[signer(payer), empty(mint_address)],
105+
);
106+
107+
assert!(result.is_ok(), "create_token failed: {:?}", result.raw_result);
108+
println!(" CREATE TOKEN CU: {}", result.compute_units_consumed);
109+
}
110+
111+
#[test]
112+
fn test_mint_tokens() {
113+
let mut svm = setup();
114+
115+
let authority = Pubkey::new_unique();
116+
let mint_address = Pubkey::new_unique();
117+
let token_addr = Pubkey::new_unique();
118+
let token_program = quasar_svm::SPL_TOKEN_PROGRAM_ID;
119+
120+
let amount = 1_000_000_000u64;
121+
let data = build_mint_tokens_data(amount);
122+
123+
let instruction = Instruction {
124+
program_id: crate::ID,
125+
accounts: vec![
126+
solana_instruction::AccountMeta::new(authority.into(), true),
127+
solana_instruction::AccountMeta::new(mint_address.into(), false),
128+
solana_instruction::AccountMeta::new(token_addr.into(), false),
129+
solana_instruction::AccountMeta::new_readonly(token_program.into(), false),
130+
],
131+
data,
132+
};
133+
134+
let result = svm.process_instruction(
135+
&instruction,
136+
&[
137+
signer(authority),
138+
mint(mint_address, authority),
139+
token_account(token_addr, mint_address, authority, 0),
140+
],
141+
);
142+
143+
assert!(result.is_ok(), "mint_tokens failed: {:?}", result.raw_result);
144+
println!(" MINT TOKENS CU: {}", result.compute_units_consumed);
145+
}

tokens/escrow/quasar/Cargo.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[package]
2+
name = "quasar-escrow"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# Standalone workspace — not part of the root program-examples workspace.
7+
# Quasar uses a different resolver and dependency tree.
8+
[workspace]
9+
10+
[lints.rust.unexpected_cfgs]
11+
level = "warn"
12+
check-cfg = [
13+
'cfg(target_os, values("solana"))',
14+
]
15+
16+
[lib]
17+
crate-type = ["cdylib", "lib"]
18+
19+
[features]
20+
alloc = []
21+
client = []
22+
debug = []
23+
24+
[dependencies]
25+
quasar-lang = "0.0"
26+
quasar-spl = "0.0"
27+
solana-address = { version = "2.2.0" }
28+
solana-instruction = { version = "3.2.0" }
29+
30+
[dev-dependencies]
31+
quasar-svm = { version = "0.1" }
32+
spl-token-interface = { version = "2.0.0" }
33+
solana-program-pack = { version = "3.1.0" }

tokens/escrow/quasar/Quasar.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[project]
2+
name = "quasar_escrow"
3+
4+
[toolchain]
5+
type = "solana"
6+
7+
[testing]
8+
language = "rust"
9+
10+
[testing.rust]
11+
framework = "quasar-svm"
12+
13+
[testing.rust.test]
14+
program = "cargo"
15+
args = [
16+
"test",
17+
"tests::",
18+
]
19+
20+
[clients]
21+
languages = ["rust"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use {
2+
crate::state::Escrow,
3+
quasar_lang::prelude::*,
4+
quasar_spl::{Mint, Token, TokenCpi},
5+
};
6+
7+
#[derive(Accounts)]
8+
pub struct Make<'info> {
9+
#[account(mut)]
10+
pub maker: &'info Signer,
11+
#[account(mut, init, payer = maker, seeds = [b"escrow", maker], bump)]
12+
pub escrow: &'info mut Account<Escrow>,
13+
pub mint_a: &'info Account<Mint>,
14+
pub mint_b: &'info Account<Mint>,
15+
#[account(mut)]
16+
pub maker_ta_a: &'info mut Account<Token>,
17+
#[account(mut, init_if_needed, payer = maker, token::mint = mint_b, token::authority = maker)]
18+
pub maker_ta_b: &'info mut Account<Token>,
19+
#[account(mut, init_if_needed, payer = maker, token::mint = mint_a, token::authority = escrow)]
20+
pub vault_ta_a: &'info mut Account<Token>,
21+
pub rent: &'info Sysvar<Rent>,
22+
pub token_program: &'info Program<Token>,
23+
pub system_program: &'info Program<System>,
24+
}
25+
26+
impl Make<'_> {
27+
#[inline(always)]
28+
pub fn make_escrow(&mut self, receive: u64, bumps: &MakeBumps) -> Result<(), ProgramError> {
29+
self.escrow.set_inner(
30+
*self.maker.address(),
31+
*self.mint_a.address(),
32+
*self.mint_b.address(),
33+
*self.maker_ta_b.address(),
34+
receive,
35+
bumps.escrow,
36+
);
37+
Ok(())
38+
}
39+
40+
#[inline(always)]
41+
pub fn deposit_tokens(&mut self, amount: u64) -> Result<(), ProgramError> {
42+
self.token_program
43+
.transfer(self.maker_ta_a, self.vault_ta_a, self.maker, amount)
44+
.invoke()
45+
}
46+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub mod make;
2+
pub use make::*;
3+
4+
pub mod take;
5+
pub use take::*;
6+
7+
pub mod refund;
8+
pub use refund::*;

0 commit comments

Comments
 (0)