如何开发 pump.fun 智能合约 - 带源码

Pump.fun 创新性地使用连接曲线机制来解决流动性问题, 本文带领大家实现一个 pump.fun

Bonding 曲线合约

如何像 pump.fun 一样编写 Bonding 曲线合约

Pump.fun 创新性地使用连接曲线机制来解决流动性问题。在 pump.fun 之前,如果一个 meme 项目想要推出代币,他们需要在 Twitter 上请求人们发送 SOL,然后等待他们收集到足够的资金在 DEX 上提供流动性,最后启动 meme 代币。

这种方法引入了几个信任问题:

  • 发送 SOL 后,项目创建者可能不会分配代币(纯欺诈)
  • 分配代币后,创建者可能不会部署 DEX 池(欺诈)
  • 即使在部署 DEX 池后,创建者也可能撤回流动性(依然是欺诈)

Pump.fun 通过智能合约执行解决了这些问题:

  • 代币以预定价格出售;发送 SOL 的用户会立即收到代币
  • 一旦达到资金目标,流动性池会自动部署,从而消除欺诈风险
  • DEX 池使用销毁的 LP 代币创建,使撤回流动性变得不可能

这优雅地解决了上述三个问题。

pump.fun 实际上是如何工作的?

  • 它基于基本的 AMM 流动性公式:A×B = K
  • 购买代币 A 时,支付代币 B 的数量为:b = aB/(A-a)
  • 出售代币 A 时,获得的代币 B 数量为:b = aB/(A+a)

所以 A 和 B 分别对应代币和 SOL,但当 B 从 0 开始时,我们如何计算这个?

Pump.fun 智妙地引入了虚拟池的概念,假设在虚拟池中有 30 SOL 和 1,073,000,000 代币。这个虚拟池用于定价代币,后续的交易计算基于这个虚拟池,巧妙地实现了代币价格计算。

合约实现

基于 Anchor 框架,连接曲线数据的结构如下:

struct BondingCurveLayout {  
    blob1: u64,  
    pooled_token: u64,    
    pooled_sol: u64,      
    real_token: u64,      
    real_sol: u64,        
    total_supply: u64,  
    state: bool,          
}

这代表了 pump 的连接曲线状态。

以下是代币定价的计算方式:

struct AmmPool {  
    token_a: u64,  
    token_b: u64,  
}

impl AmmPool {  
    fn buy_token_a_price(&self, token_amount: u64) -> Result<u64, String> {  
        if token_amount >= self.token_a {  
            return Err("买入太多".to_string());  
        }  
        if token_amount == 0 {  
            return Err("买入太少".to_string());  
        }

        let new_token_a = self.token_a - token_amount;

        let price = U256::from(self.token_b);  
        let price = price.mul(U256::from(token_amount));  
        let price = price.div(U256::from(new_token_a));

        if let Some(real_price) = price.as_u64() {  
            return Ok(real_price);  
        }

        Err("支付过多".to_string())  
    }

    fn sell_token_a_return(&self, token_amount: u64) -> Result<u64, String> {  
        let new_token_a = U256::from(self.token_a);  
        let new_token_a = new_token_a.add(U256::from(token_amount));

        if new_token_a.eq(&U256::zero()) {  
            return Err("售卖太少".to_string());  
        }

        let temp = U256::from(self.token_b);  
        let temp = temp.mul(U256::from(token_amount));  
        let price = temp.div(new_token_a);

        if let Some(real_price) = price.as_u64() {  
            if real_price > self.token_b {  
                return Err("超过了池中代币".to_string());  
            }  
            return Ok(real_price);  
        }

        Err("支付过多".to_string())  
    }  
}

核心合约函数

CreateToken

pub fn create_bonding_curve(ctx: Context<CreateBondingCurve>, params: Box<CreateTokenParams>) -> Result<()> {  
    msg!("开始创建连接曲线 1 ");  
    if params.version >= ctx.accounts.settings.configs.len() as u8 {  
        return Err(err::SwapError::InvalidParamsVersion.into());  
    }  
    let seeds = &["icta".as_bytes(), &[ctx.bumps.mint_authority]];  
    let signer = &[&seeds[..]];

    create_metadata(&ctx, &params, &signer)?;  
    let config = &ctx.accounts.settings.configs[params.version as usize];  
    ctx.accounts.bonding_curve_account.version = params.version;  
    ctx.accounts.bonding_curve_account.pid = ctx.program_id.clone();  
    ctx.accounts.bonding_curve_account.mint = ctx.accounts.mint.key().clone();  
    ctx.accounts.bonding_curve_account.vrt = config.init_virtual_token;  
    ctx.accounts.bonding_curve_account.vrs = config.init_virtual_sol;  
    ctx.accounts.bonding_curve_account.rrt = config.max_sell_count;  
    ctx.accounts.bonding_curve_account.rrs = 0;  
    ctx.accounts.bonding_curve_account.osc = config::MAX_OVER_SELLING_COUNT;  
    ctx.accounts.bonding_curve_account.it = config.total_supply;  
    ctx.accounts.bonding_curve_account.supply = config.total_supply;  
    ctx.accounts.bonding_curve_account.state = config::CurveState::Running;  
    mint_to(CpiContext::new_with_signer(  
        ctx.accounts.token_program.to_account_info(),  
        MintTo {  
            mint: ctx.accounts.mint.to_account_info(),  
            to: ctx.accounts.ic_ata.to_account_info(),  
            authority: ctx.accounts.mint_authority.to_account_info(),  
        },  
        &signer,  
    ),  
            ctx.accounts.bonding_curve_account.supply)?;

    let clock = Clock::get()?;  
    emit_cpi!(config::CreateEvent{  
        event: config::EventType::CreateBondingCurve,  
        name: params.name.clone(),  
        symbol: params.symbol.clone(),  
        uri: params.uri.clone(),  
        user: *ctx.accounts.payer.to_account_info().key,  
        curve: *ctx.accounts.bonding_curve_account.to_account_info().key,  
        mint: *ctx.accounts.mint.to_account_info().key,  
        version: params.version,  
        ts: clock.unix_timestamp,  
    });  
    Ok(())  
}

Buy


pub fn buy(ctx: Context<BuyFromCurve>, params: Box<TradeParams>) -> Result<()> {  
    msg!("开始购买: {:?}", params);  
    if params.token_amount < config::MIN_BUY_TOKEN_COUNT {  
        return Err(err::SwapError::TokenAmountTooSmall.into());  
    }  
    let version = ctx.accounts.bonding_curve_account.version as usize;  
    if version >= ctx.accounts.settings.configs.len() {  
        return Err(err::SwapError::InvalidParamsVersion.into());  
    }  
    let curve_config = &ctx.accounts.settings.configs[version];  
    let real_amount = price::get_buy_amount(&ctx.accounts.bonding_curve_account, &params)?;  
    let sol_price = price::buy_token_a(  
        ctx.accounts.bonding_curve_account.vrt,  
        ctx.accounts.bonding_curve_account.vrs,  
        real_amount)?;  
    let sol_fee = price::get_fee_from_x(ctx.accounts.settings.fee_point, sol_price)?;  
    let total_cost = sol_price.checked_add(sol_fee).ok_or(err::SwapError::InvalidArgument)?;  
    if total_cost > params.sol_amount {  
        return Err(err::SwapError::ExtendSlippage.into());  
    }  

    let destination = &ctx.accounts.to_ata;  
    let source = &ctx.accounts.from_ata;  
    let token_program = &ctx.accounts.token_program;  
    let authority = &ctx.accounts.bonding_curve_account;  
    let source_amount = source.amount;

    if source_amount < real_amount + curve_config.least_remain_count {  
        msg!("源金额{}, 实际金额{}, 最少保留金额{}", source_amount, real_amount, curve_config.least_remain_count);  
        return Err(err::SwapError::TooManyToBuy.into());  
    }  
    let seeds = &["ic".as_bytes(), ctx.accounts.mint.to_account_info().key.as_ref(), &[ctx.bumps.bonding_curve_account]];  
    let signer = &[&seeds[..]];

    let cpi_accounts = SplTransfer {  
        from: source.to_account_info().clone(),  
        to: destination.to_account_info().clone(),  
        authority: authority.to_account_info().clone(),  
    };  
    let cpi_program = token_program.to_account_info();  
    let cpi_tx = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer);  
    token::transfer(cpi_tx, real_amount)?;  

    let from_account = &ctx.accounts.payer;  
    let to_account = &ctx.accounts.bonding_curve_account;  
    if from_account.lamports() < total_cost {  
        msg!("账户余额:{}, 总费用 {}", from_account.lamports(), total_cost);  
        return Err(err::SwapError::PayerInsufficientSol.into());  
    }  
    let transfer_instruction = system_instruction::transfer(&from_account.key(), &to_account.key(), sol_price);  

    anchor_lang::solana_program::program::invoke_signed(  
        &transfer_instruction,  
        &[...

剩余50%的内容购买后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
CoinsBench
CoinsBench
https://coinsbench.com/