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
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, |
|
} |