# 🚀 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>