Pump.fun 创新性地使用连接曲线机制来解决流动性问题, 本文带领大家实现一个 pump.fun
Pump.fun 创新性地使用连接曲线机制来解决流动性问题。在 pump.fun 之前,如果一个 meme 项目想要推出代币,他们需要在 Twitter 上请求人们发送 SOL,然后等待他们收集到足够的资金在 DEX 上提供流动性,最后启动 meme 代币。
这种方法引入了几个信任问题:
Pump.fun 通过智能合约执行解决了这些问题:
这优雅地解决了上述三个问题。
所以 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())
}
}
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, ¶ms, &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(())
}
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, ¶ms)?;
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,
&[...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!