-
Notifications
You must be signed in to change notification settings - Fork 483
Commit a1b4500
authored
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<Initialize>,
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<Buy>, 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<Sell>, 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, }1 parent f3e8e8d commit a1b4500Copy full SHA for a1b4500
File tree
Expand file treeCollapse file tree
1 file changed
+1
-0
lines changedOpen diff view settings
Filter options
- basics
Expand file treeCollapse file tree
1 file changed
+1
-0
lines changedOpen diff view settings
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
0 commit comments