# 🚀 Thena 后端接入指南 > **面向后端开发者** | TypeScript + ethers.js v5 --- ## 📋 快速导航 - [核心概念](#-核心概念) - [合约地址](#-合约地址) - [Quoter 报价](#-quoter-报价) - [SwapX 交易](#-swapx-交易) - [重要规范](#-重要规范) - [快速上手](#-快速上手) --- ## 🎯 核心概念 ### Thena 三种池类型 | 池类型 | 特点 | 适用场景 | 手续费 | |:------|:-----|:---------|:------| | 🔷 **Thena V3** | Algebra 集中流动性<br/>动态手续费 | 大额交易<br/>流动性集中 | **动态** | | 🔶 **Thena Stable** | Curve 稳定币算法<br/>低滑点 | 稳定币交易<br/>(USDT/USDC) | **低** | | 🔵 **Thena V2** | 传统 AMM (x*y=k) | 一般代币对 | **固定** | ### 枚举值对照表 ```typescript // 🔹 Quoter 使用的 PoolType enum PoolType { THENA_STABLE = 9, // Stable 池 THENA_V3 = 10, // V3 池 THENA_V2 = 11 // V2 池 } // 🔹 SwapX 使用的 SwapType enum SwapType { THENA_STABLE = 3, // Stable 池 THENA_V3 = 4, // V3 池 THENA_V2 = 5 // V2 池 } ``` --- ## 📍 合约地址 ### BSC Mainnet 部署地址 ```typescript // ✅ 核心合约 (已部署) export const CONTRACTS = { SWAPX: "0x5145556d28db18ce643785a2cb7dc0fa8731011a", // SwapX Router QUOTER: "0x5b18De3582e5eFDDFe8e9EA3fc38EaA191A72759", // Quoter (已验证) // 🏭 Thena 工厂合约 THENA_V3_FACTORY: "0x30055F87716d3DFD0E5198C27024481099fB4A98", THENA_STABLE_FACTORY: "0xAFD89d21BdB66d00817d4153E055830B1c2B3970", // 💰 常用代币 WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", THENA: "0xF4C8E32EaDEC4BFe97E0F595AdD0f4450a863a11", ETH: "0x2170Ed0880ac9A755fd29B2688956BD959F933F8", USDT: "0x55d398326f99059fF775485246999027B3197955" }; // 🌐 RPC 节点 export const BSC_RPC = "https://bsc-dataseed1.binance.org/"; ``` ### 🔍 获取池地址 ```typescript // V3 池 (通过 ThenaV3Factory) const poolV3 = await ThenaV3Factory.poolByPair(tokenA, tokenB); // Stable 池 (stable = true) const poolStable = await ThenaStableFactory.getPair(tokenA, tokenB, true); // V2 池 (stable = false) const poolV2 = await ThenaStableFactory.getPair(tokenA, tokenB, false); ``` --- ## 📊 Quoter 报价 > **核心功能**:获取交易的预期输出金额(只读查询,不消耗 gas) ### 📐 数据结构 ```typescript interface Route { poolType: PoolType; // 池类型 (9=Stable, 10=V3, 11=V2) tokenIn: string; // 输入代币地址 tokenOut: string; // 输出代币地址 fee: number; // ⚠️ 固定为 0 tickSpacing: number; // 固定为 0 hook: string; // 固定为 AddressZero amountIn: string; // 输入金额 (Wei) hookData: string; // 固定为 "0x" } ``` ### 🔹 单路径报价 ```typescript const quoter = new ethers.Contract(QUOTER_ADDRESS, QUOTER_ABI, provider); const route = { poolType: PoolType.THENA_V3, tokenIn: TOKEN_A, tokenOut: TOKEN_B, fee: 0, // ⚠️ 所有池类型都设为 0 tickSpacing: 0, hook: ethers.constants.AddressZero, amountIn: ethers.utils.parseEther("1"), hookData: "0x" }; // 🎯 获取报价 (使用 callStatic) const amountOut = await quoter.callStatic.quoteExactIn([route]); console.log("预期输出:", ethers.utils.formatEther(amountOut)); ``` ### 🔹 多跳报价 ```typescript // 示例:ETH -> WBNB (V3) -> THENA (Stable) const routes = [ { poolType: PoolType.THENA_V3, tokenIn: ETH, tokenOut: WBNB, fee: 0, ... }, { poolType: PoolType.THENA_STABLE, tokenIn: WBNB, tokenOut: THENA, fee: 0, ... } ]; const finalAmountOut = await quoter.callStatic.quoteExactIn(routes); ``` ### 🔹 批量报价(找最优路径) ```typescript // 🎯 一次性比较三种池类型 const routeBatches = [ [{ poolType: PoolType.THENA_V3, ... }], // V3 [{ poolType: PoolType.THENA_STABLE, ... }], // Stable [{ poolType: PoolType.THENA_V2, ... }] // V2 ]; const amountOuts = await quoter.callStatic.multicall(routeBatches); // 找出最大输出 const bestIndex = amountOuts.reduce((max, curr, idx, arr) => curr.gt(arr[max]) ? idx : max, 0 ); console.log(`最优路径: ${["V3", "Stable", "V2"][bestIndex]}`); ``` --- ## 💱 SwapX 交易 > **核心功能**:执行代币交易(支持单跳和多跳) ### 📐 数据结构 ```typescript interface SwapDesc { swapType: SwapType; // 交易类型 (3=Stable, 4=V3, 5=V2) tokenIn: string; // 输入代币 (address(0) = 原生 BNB) tokenOut: string; // 输出代币 (address(0) = 原生 BNB) poolAddress: string; // 池地址 fee: number; // ⚠️ 固定为 0 tickSpacing: number; // 固定为 0 hooks: string; // 固定为 AddressZero hookData: string; // 固定为 "0x" } ``` ### 🔹 基础交易流程 ```typescript const swapX = new ethers.Contract(SWAPX_ADDRESS, SWAPX_ABI, signer); // 📝 步骤 1: ERC20 代币需要先授权 if (tokenIn !== ethers.constants.AddressZero) { await tokenContract.approve(SWAPX_ADDRESS, amountIn); } // 📝 步骤 2: 构建交易描述 const swapDesc = { swapType: SwapType.THENA_V3, tokenIn: TOKEN_A, tokenOut: TOKEN_B, poolAddress: POOL_ADDRESS, fee: 0, tickSpacing: 0, hooks: ethers.constants.AddressZero, hookData: "0x" }; // 📝 步骤 3: 计算最小输出 (滑点保护) const minAmountOut = expectedAmountOut.mul(9900).div(10000); // 1% 滑点 // 📝 步骤 4: 执行交易 const tx = await swapX.swap( [swapDesc], // SwapDesc 数组 (支持多跳) tokenIn, // feeToken (从哪个代币扣手续费) amountIn, minAmountOut, { value: isNativeBNB ? amountIn : 0, // 原生 BNB 需要传 value gasLimit: 300000 } ); // 📝 步骤 5: 等待确认 const receipt = await tx.wait(); console.log("✅ 交易成功:", receipt.transactionHash); ``` ### 🔹 原生 BNB 交易 ```typescript // BNB -> ERC20 await swapX.swap( [{ tokenIn: ethers.constants.AddressZero, // address(0) = BNB tokenOut: TOKEN_ADDRESS, ... }], ethers.constants.AddressZero, amountIn, minOut, { value: amountIn } // ⚠️ 必须传 value ); // ERC20 -> BNB await token.approve(SWAPX, amountIn); // 需要授权 await swapX.swap( [{ tokenIn: TOKEN_ADDRESS, tokenOut: ethers.constants.AddressZero, // address(0) = BNB ... }], TOKEN_ADDRESS, amountIn, minOut ); ``` --- ## ⚡ 重要规范 ### 🎯 核心要点 <table> <tr> <td width="50%"> #### ✅ 必须遵守 - **所有池的 `fee` 参数都设为 `0`** - 原生 BNB 用 `address(0)` 表示 - Quoter 必须用 `callStatic` 调用 - ERC20 交易前必须先授权 - 金额使用 Wei 单位 </td> <td width="50%"> #### 💡 推荐做法 - 使用 `multicall` 批量报价 - 设置合理滑点保护 - Gas limit 增加 20% buffer - 监控交易事件获取实际输出 - 大额交易建议分批执行 </td> </tr> </table> ### 📊 代币地址处理 | 场景 | tokenIn | tokenOut | 注意事项 | |:-----|:--------|:---------|:---------| | 🟢 ERC20 → ERC20 | 代币地址 | 代币地址 | ✅ 需要授权 | | 🔵 BNB → ERC20 | `address(0)` | 代币地址 | ✅ 传 `value` | | 🔴 ERC20 → BNB | 代币地址 | `address(0)` | ✅ 需要授权 | ### 🎚️ 滑点设置 ```typescript const SLIPPAGE = { STABLE: 0.001, // 0.1% - 稳定币对 V3: 0.005, // 0.5% - V3 池 V2: 0.01, // 1.0% - V2 池 VOLATILE: 0.02 // 2.0% - 高波动 }; // 计算公式 const minOut = expectedOut.mul(Math.floor((1 - slippage) * 10000)).div(10000); ``` ### ⛽ Gas 配置 ```typescript const GAS_LIMIT = { SINGLE_HOP: 300000, // 单跳 MULTI_HOP: 500000 // 多跳 }; // 动态估算 const estimate = await swapX.estimateGas.swap(...); const gasLimit = estimate.mul(120).div(100); // +20% buffer ``` --- ## 🚦 注意事项 ### 🔴 常见错误 | 错误信息 | 原因 | 解决方法 | |:---------|:-----|:---------| | `insufficient funds` | 余额不足 | 检查账户余额 | | `min return not reached` | 滑点过大 | 增加滑点容忍度或重试 | | `INSUFFICIENT_ALLOWANCE` | 授权不足 | 增加授权额度 | | `pool not found` | 池不存在 | 检查代币地址或池类型 | | `Transaction too old` | 截止时间过期 | 重新发起交易 | ### ⚠️ 关键提醒 <table> <tr> <td> **🔸 手续费参数** - ⚠️ **所有 Thena 池 `fee` 都设为 `0`** - Thena V3 使用动态手续费 - 不需要手动指定费率 </td> <td> **🔸 池地址获取** - V3: `poolByPair(tokenA, tokenB)` - Stable: `getPair(tokenA, tokenB, true)` - V2: `getPair(tokenA, tokenB, false)` </td> </tr> <tr> <td> **🔸 报价方法** - ✅ 使用 `callStatic.quoteExactIn()` - ❌ 不能直接调用 `quoteExactIn()` - Quoter 通过 revert 返回结果 </td> <td> **🔸 原生代币** - BNB = `address(0)` - 自动处理 wrap/unwrap - 传 `value` 参数 </td> </tr> </table> --- ## 🎓 快速上手 ### 示例 1: Thena V3 交易 ```typescript // 1️⃣ 初始化 const provider = new ethers.providers.JsonRpcProvider(BSC_RPC); const signer = new ethers.Wallet(PRIVATE_KEY, provider); const quoter = new ethers.Contract(QUOTER_ADDRESS, QUOTER_ABI, provider); const swapX = new ethers.Contract(SWAPX_ADDRESS, SWAPX_ABI, signer); // 2️⃣ 获取池地址 const factory = new ethers.Contract(THENA_V3_FACTORY, FACTORY_ABI, provider); const pool = await factory.poolByPair(tokenA, tokenB); // 3️⃣ 报价 const route = { poolType: PoolType.THENA_V3, tokenIn: tokenA, tokenOut: tokenB, fee: 0, amountIn: ethers.utils.parseEther("1"), tickSpacing: 0, hook: ethers.constants.AddressZero, hookData: "0x" }; const expectedOut = await quoter.callStatic.quoteExactIn([route]); // 4️⃣ 授权 const token = new ethers.Contract(tokenA, ERC20_ABI, signer); await token.approve(SWAPX_ADDRESS, amountIn); // 5️⃣ 交易 const minOut = expectedOut.mul(9950).div(10000); // 0.5% 滑点 await swapX.swap( [{ swapType: SwapType.THENA_V3, tokenIn: tokenA, tokenOut: tokenB, poolAddress: pool, fee: 0, tickSpacing: 0, hooks: ethers.constants.AddressZero, hookData: "0x" }], tokenA, amountIn, minOut, { gasLimit: 300000 } ); ``` ### 示例 2: 找最优路径 ```typescript // 🎯 批量比较三种池类型 const amountIn = ethers.utils.parseEther("1"); const routes = [ [{ poolType: PoolType.THENA_V3, tokenIn, tokenOut, fee: 0, amountIn, ... }], [{ poolType: PoolType.THENA_STABLE, tokenIn, tokenOut, fee: 0, amountIn, ... }], [{ poolType: PoolType.THENA_V2, tokenIn, tokenOut, fee: 0, amountIn, ... }] ]; const results = await quoter.callStatic.multicall(routes); const bestIdx = results.reduce((max, curr, i, arr) => curr.gt(arr[max]) ? i : max, 0); console.log("最优池:", ["V3", "Stable", "V2"][bestIdx]); console.log("预期输出:", ethers.utils.formatEther(results[bestIdx])); ``` ### 示例 3: BNB 交易 ```typescript // BNB -> Token await swapX.swap( [{ swapType: SwapType.THENA_V3, tokenIn: ethers.constants.AddressZero, // BNB tokenOut: TOKEN_ADDRESS, poolAddress: pool, fee: 0, tickSpacing: 0, hooks: ethers.constants.AddressZero, hookData: "0x" }], ethers.constants.AddressZero, amountIn, minOut, { value: amountIn, // ⚠️ 必须传 value gasLimit: 300000 } ); ``` --- ## 📚 参考资源 - 🌐 [Thena 官方文档](https://docs.thena.fi/) - 📖 [ethers.js v5 文档](https://docs.ethers.io/v5/) - 🔍 [BSC 浏览器](https://bscscan.com/) - 🔗 [Quoter 合约](https://bscscan.com/address/0x5b18De3582e5eFDDFe8e9EA3fc38EaA191A72759) - 🔗 [SwapX 合约](https://bscscan.com/address/0x5145556d28db18ce643785a2cb7dc0fa8731011a) --- <div align="center"> **📝 文档版本**: v1.0 **📅 最后更新**: 2025-01-12 **💡 技术支持**: 参考合约源码或联系开发团队 </div>