Skip to content

Commit e134cf3

Browse files
tokens: add Quasar ports for pda-mint-authority and token-fundraiser
pda-mint-authority: PDA as mint authority with signed minting token-fundraiser: crowdfunding with initialize, contribute, check, refund Both build with quasar build and pass cargo test.
1 parent fbc331b commit e134cf3

14 files changed

Lines changed: 928 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "quasar-pda-mint-authority"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# Standalone workspace — not part of the root program-examples workspace.
7+
[workspace]
8+
9+
[lints.rust.unexpected_cfgs]
10+
level = "warn"
11+
check-cfg = [
12+
'cfg(target_os, values("solana"))',
13+
]
14+
15+
[lib]
16+
crate-type = ["cdylib", "lib"]
17+
18+
[features]
19+
alloc = []
20+
client = []
21+
debug = []
22+
23+
[dependencies]
24+
quasar-lang = "0.0"
25+
quasar-spl = "0.0"
26+
solana-instruction = { version = "3.2.0" }
27+
28+
[dev-dependencies]
29+
quasar-svm = { version = "0.1" }
30+
spl-token-interface = { version = "2.0.0" }
31+
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_pda_mint_authority"
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: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
/// Demonstrates using a PDA as the mint authority for an SPL token.
12+
///
13+
/// The mint account itself is at a PDA address derived from `["mint"]`.
14+
/// The same PDA serves as both the mint address AND the mint authority,
15+
/// so minting requires PDA signing.
16+
///
17+
/// The Anchor version uses Metaplex for on-chain metadata. Quasar does not have
18+
/// a Metaplex integration crate, so this example focuses on the PDA-as-authority
19+
/// pattern.
20+
#[program]
21+
mod quasar_pda_mint_authority {
22+
use super::*;
23+
24+
/// Create a token mint at a PDA. The PDA is its own mint authority.
25+
#[instruction(discriminator = 0)]
26+
pub fn create_mint(ctx: Ctx<CreateMint>, _decimals: u8) -> Result<(), ProgramError> {
27+
ctx.accounts.create_mint()
28+
}
29+
30+
/// Mint tokens using the PDA mint authority.
31+
#[instruction(discriminator = 1)]
32+
pub fn mint_tokens(ctx: Ctx<MintTokens>, amount: u64) -> Result<(), ProgramError> {
33+
ctx.accounts.mint_tokens(amount, ctx.bumps.mint)
34+
}
35+
}
36+
37+
/// Create the mint at a PDA. The mint authority is the mint PDA itself.
38+
#[derive(Accounts)]
39+
pub struct CreateMint<'info> {
40+
#[account(mut)]
41+
pub payer: &'info Signer,
42+
/// The mint account at PDA ["mint"]. Its authority is set to itself.
43+
#[account(mut, init, payer = payer, seeds = [b"mint"], bump, mint::decimals = 9, mint::authority = mint)]
44+
pub mint: &'info mut Account<Mint>,
45+
pub rent: &'info Sysvar<Rent>,
46+
pub token_program: &'info Program<Token>,
47+
pub system_program: &'info Program<System>,
48+
}
49+
50+
impl CreateMint<'_> {
51+
#[inline(always)]
52+
pub fn create_mint(&self) -> Result<(), ProgramError> {
53+
// Mint is created and initialised by Quasar's #[account(init)].
54+
Ok(())
55+
}
56+
}
57+
58+
/// Mint tokens to a token account, signing with the PDA mint authority.
59+
#[derive(Accounts)]
60+
pub struct MintTokens<'info> {
61+
#[account(mut)]
62+
pub payer: &'info Signer,
63+
/// The PDA mint whose authority is itself.
64+
#[account(mut, seeds = [b"mint"], bump)]
65+
pub mint: &'info mut Account<Mint>,
66+
/// Recipient token account (must already exist).
67+
#[account(mut)]
68+
pub token_account: &'info mut Account<Token>,
69+
pub token_program: &'info Program<Token>,
70+
}
71+
72+
impl MintTokens<'_> {
73+
#[inline(always)]
74+
pub fn mint_tokens(&mut self, amount: u64, mint_bump: u8) -> Result<(), ProgramError> {
75+
// The PDA mint is its own authority. Build signer seeds.
76+
let bump = [mint_bump];
77+
let seeds: &[Seed] = &[
78+
Seed::from(b"mint" as &[u8]),
79+
Seed::from(&bump as &[u8]),
80+
];
81+
82+
self.token_program
83+
.mint_to(self.mint, self.token_account, self.mint, amount)
84+
.invoke_signed(seeds)
85+
}
86+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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_pda_mint_authority.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_account(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+
/// Build create_mint instruction data.
57+
/// Wire format: [discriminator: u8 = 0] [decimals: u8]
58+
fn build_create_mint_data(decimals: u8) -> Vec<u8> {
59+
vec![0u8, decimals]
60+
}
61+
62+
/// Build mint_tokens instruction data.
63+
/// Wire format: [discriminator: u8 = 1] [amount: u64 LE]
64+
fn build_mint_tokens_data(amount: u64) -> Vec<u8> {
65+
let mut data = vec![1u8];
66+
data.extend_from_slice(&amount.to_le_bytes());
67+
data
68+
}
69+
70+
#[test]
71+
fn test_create_mint() {
72+
let mut svm = setup();
73+
74+
let payer = Pubkey::new_unique();
75+
let (mint_pda, _) = Pubkey::find_program_address(&[b"mint"], &crate::ID);
76+
let token_program = quasar_svm::SPL_TOKEN_PROGRAM_ID;
77+
let system_program = quasar_svm::system_program::ID;
78+
let rent = quasar_svm::solana_sdk_ids::sysvar::rent::ID;
79+
80+
let data = build_create_mint_data(9);
81+
82+
let instruction = Instruction {
83+
program_id: crate::ID,
84+
accounts: vec![
85+
solana_instruction::AccountMeta::new(payer.into(), true),
86+
solana_instruction::AccountMeta::new(mint_pda.into(), false),
87+
solana_instruction::AccountMeta::new_readonly(rent.into(), false),
88+
solana_instruction::AccountMeta::new_readonly(token_program.into(), false),
89+
solana_instruction::AccountMeta::new_readonly(system_program.into(), false),
90+
],
91+
data,
92+
};
93+
94+
let result = svm.process_instruction(&instruction, &[signer(payer), empty(mint_pda)]);
95+
assert!(result.is_ok(), "create_mint failed: {:?}", result.raw_result);
96+
println!(" CREATE MINT CU: {}", result.compute_units_consumed);
97+
}
98+
99+
#[test]
100+
fn test_mint_with_pda_authority() {
101+
let mut svm = setup();
102+
103+
let payer = Pubkey::new_unique();
104+
let (mint_pda, _) = Pubkey::find_program_address(&[b"mint"], &crate::ID);
105+
let token_addr = Pubkey::new_unique();
106+
let token_program = quasar_svm::SPL_TOKEN_PROGRAM_ID;
107+
108+
let amount = 1_000_000_000u64;
109+
let data = build_mint_tokens_data(amount);
110+
111+
let instruction = Instruction {
112+
program_id: crate::ID,
113+
accounts: vec![
114+
solana_instruction::AccountMeta::new(payer.into(), true),
115+
solana_instruction::AccountMeta::new(mint_pda.into(), false),
116+
solana_instruction::AccountMeta::new(token_addr.into(), false),
117+
solana_instruction::AccountMeta::new_readonly(token_program.into(), false),
118+
],
119+
data,
120+
};
121+
122+
let result = svm.process_instruction(
123+
&instruction,
124+
&[
125+
signer(payer),
126+
// The mint authority is the mint_pda itself
127+
mint_account(mint_pda, mint_pda),
128+
token_account(token_addr, mint_pda, payer, 0),
129+
],
130+
);
131+
132+
assert!(result.is_ok(), "mint_tokens failed: {:?}", result.raw_result);
133+
println!(" MINT WITH PDA CU: {}", result.compute_units_consumed);
134+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "quasar-token-fundraiser"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# Standalone workspace — not part of the root program-examples workspace.
7+
[workspace]
8+
9+
[lints.rust.unexpected_cfgs]
10+
level = "warn"
11+
check-cfg = [
12+
'cfg(target_os, values("solana"))',
13+
]
14+
15+
[lib]
16+
crate-type = ["cdylib", "lib"]
17+
18+
[features]
19+
alloc = []
20+
client = []
21+
debug = []
22+
23+
[dependencies]
24+
quasar-lang = "0.0"
25+
quasar-spl = "0.0"
26+
solana-instruction = { version = "3.2.0" }
27+
28+
[dev-dependencies]
29+
quasar-svm = { version = "0.1" }
30+
spl-token-interface = { version = "2.0.0" }
31+
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_token_fundraiser"
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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use {
2+
crate::state::Fundraiser,
3+
quasar_lang::prelude::*,
4+
quasar_spl::{Token, TokenCpi},
5+
};
6+
7+
#[derive(Accounts)]
8+
pub struct CheckContributions<'info> {
9+
#[account(mut)]
10+
pub maker: &'info Signer,
11+
#[account(
12+
mut,
13+
has_one = maker,
14+
close = maker,
15+
seeds = [b"fundraiser", maker],
16+
bump = fundraiser.bump
17+
)]
18+
pub fundraiser: &'info mut Account<Fundraiser>,
19+
#[account(mut)]
20+
pub vault: &'info mut Account<Token>,
21+
#[account(mut)]
22+
pub maker_ta: &'info mut Account<Token>,
23+
pub token_program: &'info Program<Token>,
24+
}
25+
26+
impl CheckContributions<'_> {
27+
#[inline(always)]
28+
pub fn check_contributions(&mut self, fundraiser_bump: u8) -> Result<(), ProgramError> {
29+
// Verify the target was met
30+
require!(
31+
self.fundraiser.current_amount >= self.fundraiser.amount_to_raise,
32+
ProgramError::Custom(0) // TargetNotMet
33+
);
34+
35+
let maker_key = self.fundraiser.maker;
36+
let bump = [fundraiser_bump];
37+
let seeds: &[Seed] = &[
38+
Seed::from(b"fundraiser" as &[u8]),
39+
Seed::from(maker_key.as_ref()),
40+
Seed::from(&bump as &[u8]),
41+
];
42+
43+
// Transfer all vault funds to the maker
44+
let vault_amount = self.vault.amount();
45+
self.token_program
46+
.transfer(self.vault, self.maker_ta, self.fundraiser, vault_amount)
47+
.invoke_signed(seeds)?;
48+
49+
// Close the vault token account
50+
self.token_program
51+
.close_account(self.vault, self.maker, self.fundraiser)
52+
.invoke_signed(seeds)?;
53+
54+
Ok(())
55+
}
56+
}

0 commit comments

Comments
 (0)