Flux Launch
首页文档X
首页文档X
文
FLUX LAUNCH2 · 开发者文档

LaunchCurveRouter 接入文档

支持 Launch Token 与 Pair Token 之间 ERC20 和原生币交易的前端接入指南。

Launch2Solidity 0.8.24ethers v6ERC20 / NativeExact Input / OutputPath Length = 2
本页内容
合约信息
Arc MainnetRobinhood L2 Mainnet
从 0 接入 Router
精确输入交易精确输出交易
01 · CONTRACTS

合约地址

调用前请根据当前 chainId 选择 Router。不要跨链混用 Router、Factory、Pair Token 和 Launch Token 地址。

Arc Mainnet
Chain ID 5042
已启用
LaunchFactory
0xA4f31EE541862B3B7505e60CF2DB2D61fF54A730
LaunchCurveRouter
0xb6F86747fe4004595Af15fcE30033CCf97ED4FC8
LaunchLocker
0xae4c5b799ba5aE639C01Ea1a488F3Cd816773f10
LaunchCurve
由 LaunchFactory 动态创建
Robinhood L2 Mainnet
Chain ID 4663
已启用
LaunchFactory
0x59462DbAFE07E8155C152645C105D0E90B09d008
LaunchCurveRouter
0x7Ddbb18e90Ad177825A5cf32531C475b9E7c6d70
LaunchLocker
0xa56B1a41d31B571d53F74e646c4FFE5C2fF2f2b7
LaunchCurve
由 LaunchFactory 动态创建
页面只显示 chains 中启用的网络,地址读取自 src/config/contracts.ts。维护网络和地址配置后,本文档会自动更新。
02 · EXACT INPUT

精确输入交易

已知 launchToken 后,通过 Factory 获取 Pair Token 和 Curve,再完成报价、授权与 Router 调用。示例兼容 ERC20 和原生币 Pair Token。

const factory = new ethers.Contract(
  factoryAddress,
  launchFactoryAbi,
  signer,
)
const launch = await factory.getLaunch(launchToken)

const curve = new ethers.Contract(launch.curve, launchCurveAbi, signer)
const router = new ethers.Contract(routerAddress, launchCurveRouterAbi, signer)

const [reserve0, reserve1] = await curve.getReserves()
const token0IsLaunchToken = await curve.token0IsLaunchToken() // Launch Token 是否为 token0
const pairTokenIsWrappedNative = await curve.pairTokenIsWrappedNative() // Pair Token 是否为封装原生币
const protocolFeeBps = await factory.protocolFeeBps() // 协议费率,分母为 10000
const graduationPairAmount = await curve.graduationPairAmount() // 发射成功所需 Pair Token 数量
const salePairReserve = await curve.salePairReserve() // 当前已进入曲线的 Pair Token 储备

const launchReserve = token0IsLaunchToken
  ? reserve0
  : reserve1
const pairReserve = token0IsLaunchToken
  ? reserve1
  : reserve0

const pairToken = launch.pairToken
const pair = new ethers.Contract(pairToken, erc20Abi, signer)
const amountIn = ethers.parseUnits('100', await pair.decimals())

const BPS = 10_000n
let curveAmountIn =
  amountIn * (BPS - protocolFeeBps) / BPS
const remainingPairAmount =
  graduationPairAmount - salePairReserve

if (curveAmountIn > remainingPairAmount) {
  curveAmountIn = remainingPairAmount
}

const quotedOut =
  curveAmountIn * launchReserve /
  (pairReserve + curveAmountIn)
const slippageBps = 100n
const amountOutMin =
  quotedOut * (BPS - slippageBps) / BPS

const path = [pairToken, launchToken]
const to = await signer.getAddress()
const deadline = Math.floor(Date.now() / 1000) + 600

if (!pairTokenIsWrappedNative) {
  await (
    await pair.approve(routerAddress, amountIn)
  ).wait()
}

const callAmountIn =
  pairTokenIsWrappedNative ? 0n : amountIn
const overrides =
  pairTokenIsWrappedNative
    ? { value: amountIn }
    : {}

const tx = await router.swapExactTokensForTokens(
  callAmountIn,
  amountOutMin,
  path,
  to,
  deadline,
  overrides,
)
const receipt = await tx.wait()

getLaunch 返回参数

字段作用
tokenLaunch Token 地址
creator代币创建者地址
curve对应的 LaunchCurve 地址
pairToken买入和卖出使用的配对资产
dexId迁移目标 DEX 配置编号
launchConfigId发射曲线配置编号
supplyLaunch Token 总供应量
initialBuyAmount创建时的初始购买数量
dexVersion迁移目标 DEX 版本
existsToken 是否已注册到 Factory

getReserves 返回参数

字段作用
reserve0token0 对应的曲线虚拟储备
reserve1token1 对应的曲线虚拟储备
blockTimestampLast最近更新储备的区块时间戳

Router 参数与返回值

字段作用
amountIn固定总输入;原生币买入时传 0
amountOutMin允许收到的最低净输出
path买入路径为 [pairToken, launchToken]
toLaunch Token 接收地址
deadline交易失效的秒级时间戳
curveAmountIn扣除协议费并受 graduation 限制后进入 Curve 的数量
quotedOut根据虚拟储备计算的预计输出
receipt交易上链后的回执
getReserves() 按 token0/token1 顺序返回,必须通过 token0IsLaunchToken() 判断 Launch Token 储备。
03 · EXACT OUTPUT

精确输出交易

用户指定最终收到的 Launch Token 数量,Router 反算实际输入,并通过 amountInMax 限制最高支出。

const factory = new ethers.Contract(
  factoryAddress,
  launchFactoryAbi,
  signer,
)
const launch = await factory.getLaunch(launchToken)

const curve = new ethers.Contract(launch.curve, launchCurveAbi, signer)
const router = new ethers.Contract(routerAddress, launchCurveRouterAbi, signer)

const [reserve0, reserve1] = await curve.getReserves()
const token0IsLaunchToken = await curve.token0IsLaunchToken() // Launch Token 是否为 token0
const pairTokenIsWrappedNative = await curve.pairTokenIsWrappedNative() // Pair Token 是否为封装原生币
const protocolFeeBps = await factory.protocolFeeBps() // 协议费率,分母为 10000
const graduationPairAmount = await curve.graduationPairAmount() // 发射成功所需 Pair Token 数量
const salePairReserve = await curve.salePairReserve() // 当前已进入曲线的 Pair Token 储备

const launchReserve = token0IsLaunchToken ? reserve0 : reserve1
const pairReserve = token0IsLaunchToken ? reserve1 : reserve0

const pairToken = launch.pairToken
const pair = new ethers.Contract(pairToken, erc20Abi, signer)
const token = new ethers.Contract(launchToken, erc20Abi, signer)
const amountOut = ethers.parseUnits('100000', await token.decimals())

if (amountOut >= launchReserve) {
  throw new Error('amountOut exceeds curve reserve')
}

const BPS = 10_000n
const curveAmountIn =
  pairReserve * amountOut / (launchReserve - amountOut) + 1n
const protocolFee =
  curveAmountIn * protocolFeeBps / BPS
const quotedAmountIn =
  curveAmountIn + protocolFee

const remainingPairAmount =
  graduationPairAmount - salePairReserve
if (curveAmountIn > remainingPairAmount) {
  throw new Error('amountOut exceeds remaining launch capacity')
}

const slippageBps = 100n
const amountInMax =
  (
    quotedAmountIn *
    (BPS + slippageBps) +
    BPS - 1n
  ) / BPS

const path = [pairToken, launchToken]
const to = await signer.getAddress()
const deadline = Math.floor(Date.now() / 1000) + 600

if (!pairTokenIsWrappedNative) {
  await (
    await pair.approve(routerAddress, amountInMax)
  ).wait()
}

const overrides =
  pairTokenIsWrappedNative
    ? { value: amountInMax }
    : {}

const tx = await router.swapTokensForExactTokens(
  amountOut,
  amountInMax,
  path,
  to,
  deadline,
  overrides,
)
const receipt = await tx.wait()
字段作用
amountOut用户希望收到的净输出数量
curveAmountIn根据虚拟储备反算的 Curve 净输入
protocolFee根据 Factory 协议费率计算的费用
quotedAmountInCurve 净输入与协议费的预计总和
amountInMax增加滑点后的最高输入
path买入路径为 [pairToken, launchToken]
toLaunch Token 接收地址
deadline交易失效的秒级时间戳
ERC20 只转走实际需要的 amountIn。原生币调用将 value 设置为 amountInMax,未使用部分退回 msg.sender。
04 · QUOTE

报价与滑点

Router 当前没有公开 view 报价函数。应先根据虚拟储备计算报价,再通过 simulateContract 验证交易,并加入滑点保护。

amountOut = amountIn × reserveOut ÷ (reserveIn + amountIn)
const amountOutMin =
  quotedOut * (10_000n - slippageBps) / 10_000n

const amountInMax =
  (quotedIn * (10_000n + slippageBps) + 9_999n) /
  10_000n
生产环境不要将 amountOutMin 固定为 0。协议费和接近 graduation 时的输入限制都会影响最终数量。
05 · SECURITY

特殊行为与安全提示

  • 零数量语义:amountIn 或 amountInMax 为 0 时,Router 会使用当前持有的全部对应资产。
  • 不要预转账:不要提前将 ERC20 转入 Router,也不要把 Router 当作资金保管合约。
  • Graduation 退款:超过剩余目标的 Pair Token 会退回 msg.sender,而不是 to。
  • 接收地址:输出原生币时,如果 to 是合约,该合约必须支持接收原生币。
  • 迁移后交易:graduated 为 true 后,应切换至迁移后的 V3 Router。
06 · ERRORS

常见错误

常见错误原因与处理
InvalidPathpath 必须包含两个地址;检查买卖方向
InvalidLaunchCurveFactory 没有找到对应 Curve
TransactionExpireddeadline 已过期;重新生成
InsufficientOutputAmount输出低于最低值;刷新报价
ExcessiveInputAmount输入超过最高值;刷新报价
NativeTransferFailed接收地址无法接收原生币
SafeERC20FailedOperation检查余额、allowance 和代币行为