You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

457 lines
14 KiB

use anchor_lang::prelude::*;
use anchor_spl::token::{self, Burn, Mint, MintTo, Token, TokenAccount};
use anchor_spl::associated_token::AssociatedToken;
declare_id!("8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf");
#[program]
pub mod lottery_simple {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let state = &mut ctx.accounts.state;
state.bump = ctx.bumps.state;
state.vault_bump = ctx.bumps.vault;
state.authority = ctx.accounts.authority.key();
state.mint = ctx.accounts.mint.key();
state.vault = ctx.accounts.vault.key();
state.total_supply = 0;
state.dev_fee_bps = 300; // 3%
state.dividend_fee_bps = 200; // 2%
state.token_price_initial = 100_000; // 0.0001 SOL
state.token_price_increment = 10_000; // 0.00001 SOL per token
state.profit_per_share = 0;
msg!("PoWH initialized with mint: {}", ctx.accounts.mint.key());
Ok(())
}
pub fn register_user(ctx: Context<RegisterUser>) -> Result<()> {
let user = &mut ctx.accounts.user;
user.owner = ctx.accounts.owner.key();
user.token_account = ctx.accounts.token_account.key();
user.dividends_withdrawn = 0;
msg!("User registered: {}", ctx.accounts.owner.key());
Ok(())
}
pub fn buy_tokens(ctx: Context<BuyTokens>, sol_amount: u64) -> Result<()> {
// Capture needed values before mutable borrow
let state_bump = ctx.accounts.state.bump;
let buyer_key = ctx.accounts.buyer.key();
let state_account_info = ctx.accounts.state.to_account_info();
let state = &mut ctx.accounts.state;
// Calculate fees
let dev_fee = (sol_amount as u128 * state.dev_fee_bps as u128) / 10_000;
let dividend_fee = (sol_amount as u128 * state.dividend_fee_bps as u128) / 10_000;
let net_purchase = sol_amount as u128 - dev_fee - dividend_fee;
// Transfer dev fee from buyer to dev wallet
if dev_fee > 0 {
let transfer_ix = anchor_lang::solana_program::system_instruction::transfer(
&ctx.accounts.buyer.key(),
&ctx.accounts.dev_wallet.key(),
dev_fee as u64,
);
anchor_lang::solana_program::program::invoke(
&transfer_ix,
&[
ctx.accounts.buyer.to_account_info(),
ctx.accounts.dev_wallet.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
}
// Transfer net purchase + dividend fee to vault (vault holds everything except dev fee)
let vault_amount = net_purchase + dividend_fee;
if vault_amount > 0 {
let transfer_ix = anchor_lang::solana_program::system_instruction::transfer(
&ctx.accounts.buyer.key(),
&ctx.accounts.vault.key(),
vault_amount as u64,
);
anchor_lang::solana_program::program::invoke(
&transfer_ix,
&[
ctx.accounts.buyer.to_account_info(),
ctx.accounts.vault.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
}
// Distribute dividends to existing holders
if state.total_supply > 0 && dividend_fee > 0 {
let dividend_per_token = (dividend_fee * PRECISION) / state.total_supply as u128;
state.profit_per_share += dividend_per_token;
}
// Calculate tokens to mint using bonding curve
let tokens_to_mint = calculate_tokens_for_sol(net_purchase as u64, state.total_supply, state)?;
// Mint tokens to buyer
let seeds = &[b"state".as_ref(), &[state_bump]];
let signer_seeds = &[&seeds[..]];
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.buyer_token_account.to_account_info(),
authority: state_account_info,
},
signer_seeds,
);
token::mint_to(cpi_ctx, tokens_to_mint)?;
state.total_supply += tokens_to_mint;
emit!(TradeEvent {
user: buyer_key,
tokens: tokens_to_mint,
sol_amount,
is_buy: true,
});
msg!("Bought {} tokens for {} SOL", tokens_to_mint, sol_amount);
Ok(())
}
pub fn sell_tokens(ctx: Context<SellTokens>, tokens_to_sell: u64) -> Result<()> {
let state = &mut ctx.accounts.state;
require!(tokens_to_sell > 0, ErrorCode::InvalidAmount);
require!(ctx.accounts.seller_token_account.amount >= tokens_to_sell, ErrorCode::InsufficientFunds);
// Calculate SOL to return using bonding curve
let calculated_sol = calculate_sol_for_tokens(tokens_to_sell, state.total_supply, state)?;
// Safety check: limit to vault balance to prevent errors
let vault_balance = ctx.accounts.vault.to_account_info().lamports();
let sol_to_return = if calculated_sol > vault_balance {
msg!("Calculated return {} exceeds vault balance {}, limiting to vault balance", calculated_sol, vault_balance);
// Leave enough for rent exemption - be more conservative
vault_balance.saturating_sub(5000) // Leave 5000 lamports for rent exemption
} else {
calculated_sol
};
// Burn tokens from seller
let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Burn {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.seller_token_account.to_account_info(),
authority: ctx.accounts.seller.to_account_info(),
},
);
token::burn(cpi_ctx, tokens_to_sell)?;
state.total_supply -= tokens_to_sell;
// Transfer SOL from vault to seller (direct lamport manipulation)
// This is the correct approach for program-owned PDAs as per Solana documentation
if sol_to_return > 0 {
// Direct lamport manipulation - standard practice for program-owned accounts
**ctx.accounts.vault.to_account_info().try_borrow_mut_lamports()? -= sol_to_return;
**ctx.accounts.seller.to_account_info().try_borrow_mut_lamports()? += sol_to_return;
msg!("Transferred {} lamports from vault to seller", sol_to_return);
}
emit!(TradeEvent {
user: ctx.accounts.seller.key(),
tokens: tokens_to_sell,
sol_amount: sol_to_return,
is_buy: false,
});
msg!("Sold {} tokens for {} SOL", tokens_to_sell, sol_to_return);
Ok(())
}
pub fn withdraw_dividends(ctx: Context<WithdrawDividends>) -> Result<()> {
let state = &ctx.accounts.state;
let user = &mut ctx.accounts.user;
let token_balance = ctx.accounts.token_account.amount as u128;
// Calculate available dividends
let total_dividends = (state.profit_per_share * token_balance) / PRECISION;
let available_dividends = total_dividends - user.dividends_withdrawn;
require!(available_dividends > 0, ErrorCode::NoDividends);
user.dividends_withdrawn = total_dividends;
// Transfer dividends from vault to user (direct lamport manipulation)
// This is the correct approach for program-owned PDAs
**ctx.accounts.vault.to_account_info().try_borrow_mut_lamports()? -= available_dividends as u64;
**ctx.accounts.user_authority.to_account_info().try_borrow_mut_lamports()? += available_dividends as u64;
emit!(WithdrawalEvent {
user: ctx.accounts.user_authority.key(),
amount: available_dividends as u64,
});
msg!("Withdrew {} lamports in dividends", available_dividends);
Ok(())
}
}
// Realistic bonding curve based on actual SOL in vault
fn calculate_tokens_for_sol(sol: u64, supply: u64, _state: &PowhState) -> Result<u64> {
// Linear bonding curve: price increases with supply
// Base price: 0.0001 SOL per token (100,000 lamports per token)
// Price increases by 0.00001 SOL per billion tokens in supply
let base_price_lamports = 100_000u128; // 0.0001 SOL in lamports
let price_increment = 10_000u128; // 0.00001 SOL in lamports
let supply_factor = (supply as u128) / 1_000_000_000; // Per billion tokens
let current_price = base_price_lamports + (price_increment * supply_factor);
let tokens = (sol as u128 * 1_000_000_000) / current_price; // Convert lamports to tokens
msg!("Buy: {} lamports -> {} tokens at price {} lamports per token", sol, tokens, current_price);
Ok(tokens as u64)
}
fn calculate_sol_for_tokens(tokens: u64, supply: u64, _state: &PowhState) -> Result<u64> {
// Sell at 90% of current buy price to create spread
let base_price_lamports = 100_000u128;
let price_increment = 10_000u128;
let supply_factor = (supply as u128) / 1_000_000_000;
let current_price = base_price_lamports + (price_increment * supply_factor);
let sell_price = (current_price * 90) / 100; // 90% of buy price
let sol_lamports = (tokens as u128 * sell_price) / 1_000_000_000;
msg!("Sell: {} tokens -> {} lamports at price {} lamports per token", tokens, sol_lamports, sell_price);
Ok(sol_lamports as u64)
}
const PRECISION: u128 = 1_000_000_000_000_000_000; // 18 decimal places for dividend calculations
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + PowhState::INIT_SPACE,
seeds = [b"state"],
bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA for holding SOL
#[account(
init,
payer = authority,
space = 0,
seeds = [b"vault"],
bump
)]
pub vault: UncheckedAccount<'info>,
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct RegisterUser<'info> {
#[account(
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
#[account(
init,
payer = owner,
space = 8 + User::INIT_SPACE,
seeds = [b"user", owner.key().as_ref()],
bump
)]
pub user: Account<'info, User>,
#[account(
init_if_needed,
payer = owner,
associated_token::mint = mint,
associated_token::authority = owner,
)]
pub token_account: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
#[derive(Accounts)]
pub struct BuyTokens<'info> {
#[account(
mut,
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA
#[account(
mut,
seeds = [b"vault"],
bump = state.vault_bump
)]
pub vault: UncheckedAccount<'info>,
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(
mut,
token::mint = mint,
token::authority = buyer,
)]
pub buyer_token_account: Account<'info, TokenAccount>,
/// CHECK: Dev wallet for fees
#[account(mut)]
pub dev_wallet: UncheckedAccount<'info>,
#[account(mut)]
pub buyer: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct SellTokens<'info> {
#[account(
mut,
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA
#[account(
mut,
seeds = [b"vault"],
bump = state.vault_bump
)]
pub vault: UncheckedAccount<'info>,
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(
mut,
token::mint = mint,
token::authority = seller,
)]
pub seller_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub seller: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct WithdrawDividends<'info> {
#[account(
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA
#[account(
mut,
seeds = [b"vault"],
bump = state.vault_bump
)]
pub vault: UncheckedAccount<'info>,
#[account(
mut,
seeds = [b"user", user_authority.key().as_ref()],
bump
)]
pub user: Account<'info, User>,
#[account(
token::mint = mint,
token::authority = user_authority,
)]
pub token_account: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
#[account(mut)]
pub user_authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct PowhState {
pub bump: u8,
pub vault_bump: u8,
pub authority: Pubkey,
pub mint: Pubkey,
pub vault: Pubkey,
pub total_supply: u64,
pub dev_fee_bps: u16,
pub dividend_fee_bps: u16,
pub token_price_initial: u64,
pub token_price_increment: u64,
pub profit_per_share: u128,
}
#[account]
#[derive(InitSpace)]
pub struct User {
pub owner: Pubkey,
pub token_account: Pubkey,
pub dividends_withdrawn: u128,
}
#[event]
pub struct TradeEvent {
pub user: Pubkey,
pub tokens: u64,
pub sol_amount: u64,
pub is_buy: bool,
}
#[event]
pub struct WithdrawalEvent {
pub user: Pubkey,
pub amount: u64,
}
#[error_code]
pub enum ErrorCode {
#[msg("Insufficient funds")]
InsufficientFunds,
#[msg("Invalid amount")]
InvalidAmount,
#[msg("No dividends available")]
NoDividends,
}