# 买入和卖出代币

一旦代币发售开始，用户可以在绑定曲线上买卖，直到筹资目标达到。本节介绍如何集成交易功能。

## 绑定曲线交易的工作原理

LaunchLab 使用绑定曲线根据供需确定代币价格：

* **购买** 会推高价格 — 每次购买都会沿曲线将价格上移
* **出售** 会降低价格 — 每次出售都会沿曲线将价格下移
* **价格发现** — 早期买家获得较低价格，激励早期参与

交易将持续直到 `totalFundRaisingB` 等值的报价代币被收集到位，此时池将迁移到 Raydium AMM。

## 购买代币

使用 `buyToken()` 用报价代币（例如 SOL）购买代币。

```typescript
import { TxVersion } from '@raydium-io/raydium-sdk-v2'
import { initSdk } from './config'
import { PublicKey } from '@solana/web3.js'
import BN from 'bn.js'

const buyTokens = async () => {
  const raydium = await initSdk()

  const { execute, extInfo } = await raydium.launchpad.buyToken({
    mintA: new PublicKey('token-mint-address'),
    buyAmount: new BN(1_000_000_000),  // 1 SOL in lamports
    slippage: new BN(100),             // 1% slippage
    txVersion: TxVersion.V0,
  })

  console.log('Expected tokens:', extInfo.decimalOutAmount.toString())

  const { txId } = await execute({ sendAndConfirm: true })
  console.log('Transaction:', txId)
}
```

### 购买参数

| 参数                | 类型        | 必需 | 说明                                  |
| ----------------- | --------- | -- | ----------------------------------- |
| `mintA`           | PublicKey | 是  | 要购买的代币 mint。                        |
| `mintAProgram`    | PublicKey | 否  | 代币程序（SPL 或 Token-2022）。SDK 会在省略时检测。 |
| `buyAmount`       | BN        | 是  | 要花费的报价代币数量（例如 SOL 的 lamports）。      |
| `poolInfo`        | object    | 否  | 池的状态数据。若省略，SDK 会获取。                 |
| `configInfo`      | object    | 否  | 全局配置数据。若省略，SDK 会获取。                 |
| `platformFeeRate` | BN        | 否  | 平台的手续费率。若省略，SDK 会获取。                |
| `slippage`        | BN        | 否  | 最大滑点，单位为 bps（`100 = 1%`）。默认： `100`. |
| `minMintAAmount`  | BN        | 否  | 最少接收的代币数量。若省略，SDK 会计算。              |

### 购买精确代币数量

使用 `buyTokenExactOut()` 用于精确指定你想接收的代币数量。

```typescript
const { execute, extInfo } = await raydium.launchpad.buyTokenExactOut({
  programId: LAUNCHPAD_PROGRAM,
  mintA,
  poolInfo,

  // Specify exact output
  outAmount: new BN('1000000000000'),  // Exact tokens to receive
  maxBuyAmount: new BN('2000000000'),  // Maximum quote tokens to spend

  slippage: new BN(100),
  txVersion: TxVersion.V0,
})
```

## 出售代币

使用 `sellToken()` 将代币卖回以换取报价代币。

```typescript
const sellTokens = async () => {
  const raydium = await initSdk()

  const mintA = new PublicKey('token-mint-address')
  const sellAmount = new BN('500000000000')  // Tokens to sell

  const { execute, extInfo } = await raydium.launchpad.sellToken({
    programId: LAUNCHPAD_PROGRAM,

    // Token to sell
    mintA,

    // Sell parameters
    sellAmount,             // Amount of tokens to sell
    slippage: new BN(100),  // 1% slippage tolerance

    txVersion: TxVersion.V0,
  })

  console.log('Expected quote tokens:', extInfo.outAmount.toString())

  await execute({ sendAndConfirm: true })
}
```

### 出售参数

| 参数           | 类型        | 必需 | 说明                                  |
| ------------ | --------- | -- | ----------------------------------- |
| `mintA`      | PublicKey | 是  | 要出售的代币 mint。                        |
| `sellAmount` | BN        | 是  | 要出售的代币数量。                           |
| `poolInfo`   | object    | 否  | 池的状态数据。若省略，SDK 会获取。                 |
| `slippage`   | BN        | 否  | 最大滑点，单位为 bps（`100 = 1%`）。默认： `100`. |
| `minAmountB` | BN        | 否  | 接收的最低报价代币数量。若省略，SDK 会计算。            |

### 按精确报价出售

使用 `sellTokenExactOut()` 用于精确指定你想接收的报价代币数量。

```typescript
const { execute } = await raydium.launchpad.sellTokenExactOut({
  programId: LAUNCHPAD_PROGRAM,
  mintA,
  poolInfo,

  // Specify exact output
  inAmount: new BN('1000000000'),        // Exact quote tokens to receive
  maxSellAmount: new BN('600000000000'), // Maximum tokens to sell

  slippage: new BN(100),
  txVersion: TxVersion.V0,
})
```

## 高级：计算报价

使用 `Curve` 工具在执行交易前计算预期输出（例如在 UI 中显示预期数量）。

```typescript
import { Curve, PlatformConfig } from '@raydium-io/raydium-sdk-v2'

// Fetch required data
const poolInfo = await raydium.launchpad.getRpcPoolInfo({ poolId })
const platformData = await raydium.connection.getAccountInfo(poolInfo.platformId)
const platformInfo = PlatformConfig.decode(platformData!.data)
const mintInfo = await raydium.token.getTokenInfo(mintA)
const slot = await raydium.connection.getSlot()

// Calculate buy quote
const buyQuote = Curve.buyExactIn({
  poolInfo,
  amountB: new BN('1000000000'),  // 1 SOL
  protocolFeeRate: poolInfo.configInfo.tradeFeeRate,
  platformFeeRate: platformInfo.feeRate,
  curveType: poolInfo.configInfo.curveType,
  shareFeeRate: new BN(0),
  creatorFeeRate: platformInfo.creatorFeeRate,
  transferFeeConfigA: mintInfo.extensions.feeConfig,  // For Token-2022
  slot,
})

console.log('Tokens out:', buyQuote.amountA.amount.toString())
console.log('Fees:', {
  protocol: buyQuote.splitFee.protocolFee.toString(),
  platform: buyQuote.splitFee.platformFee.toString(),
  creator: buyQuote.splitFee.creatorFee.toString(),
  share: buyQuote.splitFee.shareFee.toString(),
})

// Calculate sell quote
const sellQuote = Curve.sellExactIn({
  poolInfo,
  amountA: new BN('500000000000'),  // Tokens to sell
  protocolFeeRate: poolInfo.configInfo.tradeFeeRate,
  platformFeeRate: platformInfo.feeRate,
  curveType: poolInfo.configInfo.curveType,
  shareFeeRate: new BN(0),
  creatorFeeRate: platformInfo.creatorFeeRate,
  transferFeeConfigA: mintInfo.extensions.feeConfig,
  slot,
})

console.log('Quote tokens out:', sellQuote.amountB.toString())
```

### 可用的报价方法

| 方法                     | 说明                   |
| ---------------------- | -------------------- |
| `Curve.buyExactIn()`   | 计算给定报价代币输入可接收的代币数量   |
| `Curve.buyExactOut()`  | 计算为特定代币输出所需的报价代币数量   |
| `Curve.sellExactIn()`  | 计算给定代币输入可接收的报价代币数量   |
| `Curve.sellExactOut()` | 计算为接收特定报价代币输出所需的代币数量 |

## 推荐费

集成者可以通过传递 `shareFeeRate` 和 `shareFeeReceiver` 参数来赚取推荐费。

```typescript
const { execute } = await raydium.launchpad.buyToken({
  // ... other params ...

  // Referral configuration
  shareFeeRate: new BN(5000),  // 0.5% referral fee
  shareFeeReceiver: new PublicKey('referrer-wallet'),
})
```

> **注意：** `shareFeeRate` 不能超过 `maxShareFeeRate` 全局配置中的值。推荐费以报价代币（例如 SOL）支付，并直接转入 `shareFeeReceiver` wallet。

## 检查池状态

在交易前，验证池仍处于可交易状态：

```typescript
const poolInfo = await raydium.launchpad.getRpcPoolInfo({ poolId })

if (poolInfo.status === 0) {
  console.log('Pool is active - trading enabled')
} else if (poolInfo.status === 1) {
  console.log('Pool is migrating - trading disabled')
} else if (poolInfo.status === 2) {
  console.log('Pool has migrated - trade on Raydium AMM instead')
}

// Check progress toward migration
const progress = poolInfo.realB.mul(new BN(100)).div(poolInfo.totalFundRaisingB)
console.log(`Fundraising progress: ${progress.toString()}%`)
```
