From a1b4500da2f6ee10233658626b77a60b2a81a238 Mon Sep 17 00:00:00 2001 From: istvanmajsai70-pixel Date: Wed, 29 Oct 2025 09:52:13 +0100 Subject: [PATCH] Create MajsaiSol use anchor_lang::prelude::*; use anchor_spl::token::{self, Mint, TokenAccount, Token, MintTo, Transfer, CloseAccount}; declare_id!("ReplaceWithYourProgramId1111111111111111111111"); // Simple Anchor program that: // - creates an SPL mint with a fixed total supply (100_000_000_000 tokens, decimals = 6) // - holds a token reserve owned by the program // - allows users to buy tokens by sending SOL and selling tokens back for SOL // - uses a fixed price (tokens per 1 SOL) and a small fee (in basis points) // NOTE: // - This is an educational example. Do NOT use in production without audit. // - We use decimals = 6 so total_supply fits into u64 when scaled. // - The program uses CPI to the SPL Token program. #[program] pub mod anchor_spl_token_exchange { use super::*; pub fn initialize( ctx: Context, price_tokens_per_sol: u64, fee_bps: u16, ) -> Result<()> { // price_tokens_per_sol: how many token base-units (i.e. including decimals) equals 1 SOL // fee_bps: fee in basis points (10000 bps = 100%) let state = &mut ctx.accounts.state; state.mint = ctx.accounts.mint.key(); state.reserve = ctx.accounts.reserve.key(); state.authority = ctx.accounts.authority.key(); state.price_tokens_per_sol = price_tokens_per_sol; state.fee_bps = fee_bps; // Mint the total supply to the reserve token account (program-owned reserve) let cpi_accounts = MintTo { mint: ctx.accounts.mint.to_account_info(), to: ctx.accounts.reserve.to_account_info(), authority: ctx.accounts.mint_authority.to_account_info(), }; // mint_authority is a PDA we derived; sign with seeds let mint_authority_seeds: &[&[u8]] = &[ b"mint_authority", ctx.program_id.as_ref(), ]; let signer = &[&mint_authority_seeds[..]]; let cpi_program = ctx.accounts.token_program.to_account_info(); let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); // 100_000_000_000 tokens with decimals = 6 -> scaled supply = 100_000_000_000 * 10^6 let total_supply: u64 = 100_000_000_000u64.checked_mul(10u64.pow(ctx.accounts.mint.decimals as u32)).unwrap(); token::mint_to(cpi_ctx, total_supply)?; Ok(()) } // Buy tokens: user attaches SOL (lamports) and receives tokens at fixed price pub fn buy(ctx: Context, min_tokens_out: u64) -> Result<()> { let state = &ctx.accounts.state; // amount of lamports sent in this instruction must be transfered by the client let lamports_sent = ctx.accounts.payer.to_account_info().lamports() .checked_sub(ctx.accounts.payer_starting_balance) .unwrap_or(0); require!(lamports_sent > 0, ExchangeError::NoLamportsSent); // tokens per 1 SOL (1 SOL = 1_000_000_000 lamports) let tokens_per_lamport = state.price_tokens_per_sol.checked_div(1_000_000_000u64).ok_or(ExchangeError::InvalidPrice)?; let tokens_out = lamports_sent.checked_mul(tokens_per_lamport).ok_or(ExchangeError::MathOverflow)?; // apply fee let fee = tokens_out.checked_mul(state.fee_bps as u64).ok_or(ExchangeError::MathOverflow)? .checked_div(10000).ok_or(ExchangeError::MathOverflow)?; let tokens_to_user = tokens_out.checked_sub(fee).ok_or(ExchangeError::MathOverflow)?; require!(tokens_to_user >= min_tokens_out, ExchangeError::SlippageExceeded); // Transfer tokens from reserve -> user_token_account (program signed) let cpi_accounts = Transfer { from: ctx.accounts.reserve.to_account_info(), to: ctx.accounts.user_token.to_account_info(), authority: ctx.accounts.reserve_authority.to_account_info(), }; let seeds = &[b"reserve_authority", ctx.program_id.as_ref()]; let signer = &[&seeds[..]]; let cpi_ctx = CpiContext::new_with_signer(ctx.accounts.token_program.to_account_info(), cpi_accounts, signer); token::transfer(cpi_ctx, tokens_to_user)?; Ok(()) } // Sell tokens: user transfers tokens to reserve, program sends SOL back at fixed price pub fn sell(ctx: Context, tokens_in: u64, min_lamports_out: u64) -> Result<()> { let state = &mut ctx.accounts.state; require!(tokens_in > 0, ExchangeError::InvalidAmount); // compute lamports out = tokens_in / tokens_per_lamport let tokens_per_lamport = state.price_tokens_per_sol.checked_div(1_000_000_000u64).ok_or(ExchangeError::InvalidPrice)?; require!(tokens_per_lamport > 0, ExchangeError::InvalidPrice); let lamports_out = tokens_in.checked_div(tokens_per_lamport).ok_or(ExchangeError::MathOverflow)?; // apply fee on tokens (fee taken to reserve, so user receives lamports based on tokens after fee) let fee = tokens_in.checked_mul(state.fee_bps as u64).ok_or(ExchangeError::MathOverflow)? .checked_div(10000).ok_or(ExchangeError::MathOverflow)?; let tokens_after_fee = tokens_in.checked_sub(fee).ok_or(ExchangeError::MathOverflow)?; let lamports_to_user = tokens_after_fee.checked_div(tokens_per_lamport).ok_or(ExchangeError::MathOverflow)?; require!(lamports_to_user >= min_lamports_out, ExchangeError::SlippageExceeded); // Transfer tokens from user -> reserve (signed by user) let cpi_accounts = Transfer { from: ctx.accounts.user_token.to_account_info(), to: ctx.accounts.reserve.to_account_info(), authority: ctx.accounts.user_authority.to_account_info(), }; let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts); token::transfer(cpi_ctx, tokens_in)?; // Send lamports from program's vault (the state account) to user **ctx.accounts.state.to_account_info().try_borrow_mut_lamports()? -= lamports_to_user; **ctx.accounts.user_authority.to_account_info().try_borrow_mut_lamports()? += lamports_to_user; Ok(()) } } // ------------------- Accounts & State ------------------- #[account] pub struct State { pub mint: Pubkey, pub reserve: Pubkey, pub authority: Pubkey, pub price_tokens_per_sol: u64, pub fee_bps: u16, } #[derive(Accounts)] #[instruction(price_tokens_per_sol: u64, fee_bps: u16)] pub struct Initialize<'info> { #[account(init, payer = authority, space = 8 + 32*3 + 8 + 2)] pub state: Account<'info, State>, #[account( init, payer = authority, mint::decimals = 6, mint::authority = mint_authority, )] pub mint: Account<'info, Mint>, /// CHECK: PDA that will be mint authority #[account(seeds = [b"mint_authority", program_id.as_ref()], bump)] pub mint_authority: UncheckedAccount<'info>, #[account( init, payer = authority, token::mint = mint, token::authority = reserve_authority, )] pub reserve: Account<'info, TokenAccount>, /// CHECK: PDA that will be reserve authority for transferring tokens #[account(seeds = [b"reserve_authority", program_id.as_ref()], bump)] pub reserve_authority: UncheckedAccount<'info>, #[account(mut)] pub authority: Signer<'info>, pub token_program: Program<'info, Token>, pub system_program: Program<'info, System>, pub rent: Sysvar<'info, Rent>, } #[derive(Accounts)] pub struct Buy<'info> { #[account(mut)] pub state: Account<'info, State>, /// CHECK: reserve authority PDA #[account(seeds = [b"reserve_authority", program_id.as_ref()], bump)] pub reserve_authority: UncheckedAccount<'info>, #[account(mut, token::mint = state.mint, token::authority = reserve_authority)] pub reserve: Account<'info, TokenAccount>, #[account(mut)] pub payer: Signer<'info>, #[account(mut, token::mint = state.mint, token::authority = payer)] pub user_token: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, pub system_program: Program<'info, System>, // helper to compute lamports sent by client #[account(mut)] pub payer_starting_balance: AccountInfo<'info>, } #[derive(Accounts)] pub struct Sell<'info> { #[account(mut)] pub state: Account<'info, State>, #[account(mut, token::mint = state.mint)] pub reserve: Account<'info, TokenAccount>, #[account(mut)] pub user_authority: Signer<'info>, #[account(mut, token::mint = state.mint, token::authority = user_authority)] pub user_token: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, } // ------------------- Errors ------------------- #[error_code] pub enum ExchangeError { #[msg("No lamports were sent to buy tokens")] NoLamportsSent, #[msg("Invalid price configuration")] InvalidPrice, #[msg("Math overflow")] MathOverflow, #[msg("Slippage exceeded")] SlippageExceeded, #[msg("Invalid amount")] InvalidAmount, } --- basics/MajsaiSol | 1 + 1 file changed, 1 insertion(+) create mode 100644 basics/MajsaiSol diff --git a/basics/MajsaiSol b/basics/MajsaiSol new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/basics/MajsaiSol @@ -0,0 +1 @@ +