Ethers.js
包含共享设置、估算、买入、卖出、借款、追加借款和还款示例。
Viem
展示 V5 流程的类型化 public client / wallet client 读取与写入。
Web3.js
为仍在使用 Web3.js 的集成方提供示例。
Python Web3.py
覆盖需要直接调用 V5 合约的后端脚本、机器人和分析任务。
1
解析代币的 poolId
可用时使用 API 数据,然后 fallback 到
hook.tokenToPoolId(tokenAddress)。2
解析或构建 PoolKey
优先使用
factory.getPoolKey(tokenAddress),只有在已知常量时才手动构建。3
通过 Hook 获取报价
V5 估算函数包含用户钱包地址,因为推荐路由会影响费用接收方。
4
通过 Swap Router 执行买入和卖出
交易执行使用该代币对应的 PoolKey。
5
通过 Hook 执行 borrow、borrow more 和 repay
借贷操作使用
poolId,抵押品授权给 Hook。不要把旧的 V3 示例用于 V5 代币。在 V5 中,
estimateBuy 和 estimateSell 都需要用户地址,createToken 使用 devBuyEth,并且代币交易必须使用正确的 PoolKey 路由。V5 共享概念
type PoolKey = {
currency0: `0x${string}`; // 原生 BNB 表示为 address(0)
currency1: `0x${string}`; // 代币地址
fee: number; // 通常为 3000
tickSpacing: number; // 通常为 60
hooks: `0x${string}`; // Hook 合约
};
token.pool_id,但集成方也应该知道备用解析方式:
poolId = token.pool_id || token.poolId || hook.tokenToPoolId(tokenAddress)
factory.getPoolKey(tokenAddress)。如果已经知道网络常量,也可以这样构建相同的 PoolKey:
{
currency0: '0x0000000000000000000000000000000000000000',
currency1: tokenAddress,
fee: 3000,
tickSpacing: 60,
hooks: hookAddress
}
Ethers.js v5
共享设置
import { ethers } from 'ethers';
const API_BASE = 'https://lfg.rich/api/bsc/tokens';
const ZERO = ethers.constants.AddressZero;
async function loadContractsConfig() {
const res = await fetch(`${API_BASE}/config/contracts`);
const json = await res.json();
if (!json.success) throw new Error(json.error || 'Failed to load contract config');
return json.data;
}
async function loadToken(tokenAddress) {
const res = await fetch(`${API_BASE}/${tokenAddress}`);
const json = await res.json();
if (!json.success) throw new Error(json.error || 'Failed to load token');
return json.data;
}
async function getPoolContext({ tokenAddress, provider }) {
const cfg = await loadContractsConfig();
const token = await loadToken(tokenAddress);
const factory = new ethers.Contract(cfg.factoryAddress, cfg.factoryABI, provider);
const hook = new ethers.Contract(cfg.hookAddress, cfg.hookABI, provider);
let poolId = token.pool_id || token.poolId;
if (!poolId || poolId === '0x') {
poolId = await hook.tokenToPoolId(tokenAddress);
}
let poolKey;
try {
poolKey = await factory.getPoolKey(tokenAddress);
} catch {
poolKey = {
currency0: ZERO,
currency1: tokenAddress,
fee: 3000,
tickSpacing: 60,
hooks: cfg.hookAddress
};
}
return { cfg, token, poolId, poolKey, factory, hook };
}
function applySlippage(value, slippagePct = 5) {
return ethers.BigNumber.from(value).mul(100 - slippagePct).div(100);
}
估算买入
async function estimateBuy({ tokenAddress, bnbAmount, walletAddress, provider }) {
const { hook, poolId } = await getPoolContext({ tokenAddress, provider });
const ethWei = ethers.utils.parseEther(bnbAmount);
const quote = await hook.estimateBuy(
poolId,
ethWei,
walletAddress || ethers.constants.AddressZero
);
return {
tokensOut: quote.tokensOut.toString(),
platformFee: quote.platformFee.toString(),
inviterFee: quote.inviterFee.toString(),
totalFee: quote.platformFee.add(quote.inviterFee || 0).toString()
};
}
买入代币
async function buyToken({ tokenAddress, bnbAmount, slippagePct = 5, signer }) {
const provider = signer.provider;
const walletAddress = await signer.getAddress();
const { cfg, hook, poolId, poolKey } = await getPoolContext({ tokenAddress, provider });
const router = new ethers.Contract(cfg.swapRouterAddress, cfg.swapRouterABI, signer);
const value = ethers.utils.parseEther(bnbAmount);
const quote = await hook.estimateBuy(poolId, value, walletAddress);
const minTokensOut = applySlippage(quote.tokensOut, slippagePct);
const tx = await router.buy(poolKey, minTokensOut, { value });
return await tx.wait();
}
估算卖出
async function estimateSell({ tokenAddress, tokenAmount, walletAddress, provider }) {
const { hook, poolId } = await getPoolContext({ tokenAddress, provider });
const tokenWei = ethers.utils.parseEther(tokenAmount);
const quote = await hook.estimateSell(
poolId,
tokenWei,
walletAddress || ethers.constants.AddressZero
);
return {
ethOut: quote.ethOut.toString(),
platformFee: quote.platformFee.toString(),
inviterFee: quote.inviterFee.toString(),
totalFee: quote.platformFee.add(quote.inviterFee || 0).toString()
};
}
卖出代币
async function sellToken({ tokenAddress, tokenAmount, slippagePct = 5, signer }) {
const provider = signer.provider;
const walletAddress = await signer.getAddress();
const { cfg, hook, poolId, poolKey } = await getPoolContext({ tokenAddress, provider });
const token = new ethers.Contract(tokenAddress, cfg.tokenABI, signer);
const router = new ethers.Contract(cfg.swapRouterAddress, cfg.swapRouterABI, signer);
const amountWei = ethers.utils.parseEther(tokenAmount);
const allowance = await token.allowance(walletAddress, cfg.swapRouterAddress);
if (allowance.lt(amountWei)) {
const approveTx = await token.approve(cfg.swapRouterAddress, ethers.constants.MaxUint256);
await approveTx.wait();
}
const quote = await hook.estimateSell(poolId, amountWei, walletAddress);
const minEthOut = applySlippage(quote.ethOut, slippagePct);
const tx = await router.sell(poolKey, amountWei, minEthOut);
return await tx.wait();
}
借款
async function borrowAgainstToken({ tokenAddress, tokenAmount, signer }) {
const provider = signer.provider;
const walletAddress = await signer.getAddress();
const { cfg, poolId } = await getPoolContext({ tokenAddress, provider });
const hook = new ethers.Contract(cfg.hookAddress, cfg.hookABI, signer);
const token = new ethers.Contract(tokenAddress, cfg.tokenABI, signer);
const amountWei = ethers.utils.parseEther(tokenAmount);
const allowance = await token.allowance(walletAddress, cfg.hookAddress);
if (allowance.lt(amountWei)) {
const approveTx = await token.approve(cfg.hookAddress, ethers.constants.MaxUint256);
await approveTx.wait();
}
const tx = await hook.borrow(poolId, amountWei);
return await tx.wait();
}
追加借款
async function borrowMore({ tokenAddress, signer }) {
const { cfg, poolId } = await getPoolContext({
tokenAddress,
provider: signer.provider
});
const hook = new ethers.Contract(cfg.hookAddress, cfg.hookABI, signer);
const tx = await hook.borrowMore(poolId);
return await tx.wait();
}
还款
async function repayBorrow({ tokenAddress, bnbAmount, signer }) {
const { cfg, poolId } = await getPoolContext({
tokenAddress,
provider: signer.provider
});
const hook = new ethers.Contract(cfg.hookAddress, cfg.hookABI, signer);
const value = ethers.utils.parseEther(bnbAmount);
const tx = await hook.repay(poolId, { value });
return await tx.wait();
}
读取用户借款状态
async function getBorrowState({ tokenAddress, walletAddress, provider }) {
const { hook, poolId } = await getPoolContext({ tokenAddress, provider });
const [borrowedETH, collateralBalance, borrowMoreEstimate] = await Promise.all([
hook.borrowedETH(poolId, walletAddress),
hook.collateralBalance(poolId, walletAddress),
hook.estimateBorrowMore(poolId, walletAddress).catch(() => null)
]);
return {
poolId,
borrowedETH: borrowedETH.toString(),
collateralBalance: collateralBalance.toString(),
borrowMoreEstimate: borrowMoreEstimate
? {
additionalEth: borrowMoreEstimate.additionalEth.toString(),
fee: borrowMoreEstimate.fee.toString()
}
: null
};
}
Viem
import { createPublicClient, createWalletClient, custom, http, parseAbi, parseEther, zeroAddress } from 'viem';
import { bsc } from 'viem/chains';
const API_BASE = 'https://lfg.rich/api/bsc/tokens';
async function getConfig() {
const res = await fetch(`${API_BASE}/config/contracts`);
const json = await res.json();
if (!json.success) throw new Error(json.error || 'Failed to load config');
return {
...json.data,
factoryABI: parseAbi(json.data.factoryABI),
hookABI: parseAbi(json.data.hookABI),
swapRouterABI: parseAbi(json.data.swapRouterABI),
tokenABI: parseAbi(json.data.tokenABI),
};
}
async function getPoolId(publicClient, cfg, tokenAddress, apiToken) {
return apiToken.pool_id || apiToken.poolId || await publicClient.readContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'tokenToPoolId',
args: [tokenAddress],
});
}
function poolKey(tokenAddress, hookAddress) {
return {
currency0: zeroAddress,
currency1: tokenAddress,
fee: 3000,
tickSpacing: 60,
hooks: hookAddress,
};
}
使用 Viem 买入
async function viemBuy(tokenAddress, bnbAmount, account) {
const cfg = await getConfig();
const apiToken = await fetch(`${API_BASE}/${tokenAddress}`).then(r => r.json()).then(j => j.data);
const publicClient = createPublicClient({ chain: bsc, transport: http(cfg.rpcUrl) });
const walletClient = createWalletClient({ account, chain: bsc, transport: custom(window.ethereum) });
const poolId = await getPoolId(publicClient, cfg, tokenAddress, apiToken);
const value = parseEther(bnbAmount);
const quote = await publicClient.readContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'estimateBuy',
args: [poolId, value, account],
});
const minTokensOut = quote[0] * 95n / 100n;
return walletClient.writeContract({
address: cfg.swapRouterAddress,
abi: cfg.swapRouterABI,
functionName: 'buy',
args: [poolKey(tokenAddress, cfg.hookAddress), minTokensOut],
value,
});
}
使用 Viem 卖出
async function viemSell(tokenAddress, tokenAmount, account) {
const cfg = await getConfig();
const apiToken = await fetch(`${API_BASE}/${tokenAddress}`).then(r => r.json()).then(j => j.data);
const publicClient = createPublicClient({ chain: bsc, transport: http(cfg.rpcUrl) });
const walletClient = createWalletClient({ account, chain: bsc, transport: custom(window.ethereum) });
const amount = parseEther(tokenAmount);
const poolId = await getPoolId(publicClient, cfg, tokenAddress, apiToken);
const allowance = await publicClient.readContract({
address: tokenAddress,
abi: cfg.tokenABI,
functionName: 'allowance',
args: [account, cfg.swapRouterAddress],
});
if (allowance < amount) {
await walletClient.writeContract({
address: tokenAddress,
abi: cfg.tokenABI,
functionName: 'approve',
args: [cfg.swapRouterAddress, 2n ** 256n - 1n],
});
}
const quote = await publicClient.readContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'estimateSell',
args: [poolId, amount, account],
});
const minEthOut = quote[0] * 95n / 100n;
return walletClient.writeContract({
address: cfg.swapRouterAddress,
abi: cfg.swapRouterABI,
functionName: 'sell',
args: [poolKey(tokenAddress, cfg.hookAddress), amount, minEthOut],
});
}
使用 Viem 借款、追加借款和还款
async function viemBorrow(tokenAddress, tokenAmount, account) {
const cfg = await getConfig();
const publicClient = createPublicClient({ chain: bsc, transport: http(cfg.rpcUrl) });
const walletClient = createWalletClient({ account, chain: bsc, transport: custom(window.ethereum) });
const amount = parseEther(tokenAmount);
const poolId = await publicClient.readContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'tokenToPoolId',
args: [tokenAddress],
});
const allowance = await publicClient.readContract({
address: tokenAddress,
abi: cfg.tokenABI,
functionName: 'allowance',
args: [account, cfg.hookAddress],
});
if (allowance < amount) {
await walletClient.writeContract({
address: tokenAddress,
abi: cfg.tokenABI,
functionName: 'approve',
args: [cfg.hookAddress, 2n ** 256n - 1n],
});
}
return walletClient.writeContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'borrow',
args: [poolId, amount],
});
}
async function viemBorrowMore(tokenAddress, account) {
const cfg = await getConfig();
const publicClient = createPublicClient({ chain: bsc, transport: http(cfg.rpcUrl) });
const walletClient = createWalletClient({ account, chain: bsc, transport: custom(window.ethereum) });
const poolId = await publicClient.readContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'tokenToPoolId',
args: [tokenAddress],
});
return walletClient.writeContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'borrowMore',
args: [poolId],
});
}
async function viemRepay(tokenAddress, bnbAmount, account) {
const cfg = await getConfig();
const publicClient = createPublicClient({ chain: bsc, transport: http(cfg.rpcUrl) });
const walletClient = createWalletClient({ account, chain: bsc, transport: custom(window.ethereum) });
const poolId = await publicClient.readContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'tokenToPoolId',
args: [tokenAddress],
});
return walletClient.writeContract({
address: cfg.hookAddress,
abi: cfg.hookABI,
functionName: 'repay',
args: [poolId],
value: parseEther(bnbAmount),
});
}
Web3.js
import Web3 from 'web3';
const API_BASE = 'https://lfg.rich/api/bsc/tokens';
const ZERO = '0x0000000000000000000000000000000000000000';
async function web3Context(tokenAddress, web3) {
const cfg = (await (await fetch(`${API_BASE}/config/contracts`)).json()).data;
const tokenData = (await (await fetch(`${API_BASE}/${tokenAddress}`)).json()).data;
const factory = new web3.eth.Contract(cfg.factoryABI, cfg.factoryAddress);
const hook = new web3.eth.Contract(cfg.hookABI, cfg.hookAddress);
const router = new web3.eth.Contract(cfg.swapRouterABI, cfg.swapRouterAddress);
const token = new web3.eth.Contract(cfg.tokenABI, tokenAddress);
const poolId = tokenData.pool_id || tokenData.poolId || await hook.methods.tokenToPoolId(tokenAddress).call();
let key;
try {
key = await factory.methods.getPoolKey(tokenAddress).call();
} catch {
key = [ZERO, tokenAddress, 3000, 60, cfg.hookAddress];
}
return { cfg, tokenData, factory, hook, router, token, poolId, key };
}
async function web3Buy({ tokenAddress, bnbAmount, from, web3 }) {
const { hook, router, poolId, key } = await web3Context(tokenAddress, web3);
const value = web3.utils.toWei(bnbAmount, 'ether');
const quote = await hook.methods.estimateBuy(poolId, value, from).call();
const minTokensOut = (BigInt(quote.tokensOut || quote[0]) * 95n / 100n).toString();
return router.methods.buy(key, minTokensOut).send({ from, value });
}
async function web3Sell({ tokenAddress, tokenAmount, from, web3 }) {
const { cfg, hook, router, token, poolId, key } = await web3Context(tokenAddress, web3);
const amount = web3.utils.toWei(tokenAmount, 'ether');
const allowance = await token.methods.allowance(from, cfg.swapRouterAddress).call();
if (BigInt(allowance) < BigInt(amount)) {
await token.methods.approve(cfg.swapRouterAddress, web3.utils.toTwosComplement(-1)).send({ from });
}
const quote = await hook.methods.estimateSell(poolId, amount, from).call();
const minEthOut = (BigInt(quote.ethOut || quote[0]) * 95n / 100n).toString();
return router.methods.sell(key, amount, minEthOut).send({ from });
}
async function web3Borrow({ tokenAddress, tokenAmount, from, web3 }) {
const { cfg, hook, token, poolId } = await web3Context(tokenAddress, web3);
const amount = web3.utils.toWei(tokenAmount, 'ether');
const allowance = await token.methods.allowance(from, cfg.hookAddress).call();
if (BigInt(allowance) < BigInt(amount)) {
await token.methods.approve(cfg.hookAddress, web3.utils.toTwosComplement(-1)).send({ from });
}
return hook.methods.borrow(poolId, amount).send({ from });
}
async function web3BorrowMore({ tokenAddress, from, web3 }) {
const { hook, poolId } = await web3Context(tokenAddress, web3);
return hook.methods.borrowMore(poolId).send({ from });
}
async function web3Repay({ tokenAddress, bnbAmount, from, web3 }) {
const { hook, poolId } = await web3Context(tokenAddress, web3);
const value = web3.utils.toWei(bnbAmount, 'ether');
return hook.methods.repay(poolId).send({ from, value });
}
Python Web3.py
from web3 import Web3
import requests
API_BASE = "https://lfg.rich/api/bsc/tokens"
ZERO = "0x0000000000000000000000000000000000000000"
def load_config():
data = requests.get(f"{API_BASE}/config/contracts", timeout=20).json()
if not data.get("success"):
raise RuntimeError(data.get("error", "Failed to load config"))
return data["data"]
def load_token(token_address):
data = requests.get(f"{API_BASE}/{token_address}", timeout=20).json()
if not data.get("success"):
raise RuntimeError(data.get("error", "Failed to load token"))
return data["data"]
def pool_context(w3: Web3, token_address: str):
cfg = load_config()
token_data = load_token(token_address)
factory = w3.eth.contract(address=Web3.to_checksum_address(cfg["factoryAddress"]), abi=cfg["factoryABI"])
hook = w3.eth.contract(address=Web3.to_checksum_address(cfg["hookAddress"]), abi=cfg["hookABI"])
router = w3.eth.contract(address=Web3.to_checksum_address(cfg["swapRouterAddress"]), abi=cfg["swapRouterABI"])
token = w3.eth.contract(address=Web3.to_checksum_address(token_address), abi=cfg["tokenABI"])
pool_id = token_data.get("pool_id") or token_data.get("poolId")
if not pool_id:
pool_id = hook.functions.tokenToPoolId(Web3.to_checksum_address(token_address)).call()
try:
key = factory.functions.getPoolKey(Web3.to_checksum_address(token_address)).call()
except Exception:
key = (
ZERO,
Web3.to_checksum_address(token_address),
3000,
60,
Web3.to_checksum_address(cfg["hookAddress"]),
)
return cfg, token_data, factory, hook, router, token, pool_id, key
def sign_and_send(w3: Web3, tx, private_key: str):
signed = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
return w3.eth.wait_for_transaction_receipt(tx_hash)
使用 Python 买入
def buy_token(w3, token_address, bnb_amount, wallet, private_key, slippage_pct=5):
cfg, token_data, factory, hook, router, token, pool_id, key = pool_context(w3, token_address)
wallet = Web3.to_checksum_address(wallet)
wei_in = w3.to_wei(bnb_amount, "ether")
tokens_out, platform_fee, inviter_fee = hook.functions.estimateBuy(pool_id, wei_in, wallet).call()
min_tokens_out = tokens_out * (100 - slippage_pct) // 100
tx = router.functions.buy(key, min_tokens_out).build_transaction({
"from": wallet,
"value": wei_in,
"nonce": w3.eth.get_transaction_count(wallet),
"gas": 650000,
"gasPrice": w3.eth.gas_price,
"chainId": 56,
})
return sign_and_send(w3, tx, private_key)
使用 Python 卖出
def sell_token(w3, token_address, token_amount, wallet, private_key, slippage_pct=5):
cfg, token_data, factory, hook, router, token, pool_id, key = pool_context(w3, token_address)
wallet = Web3.to_checksum_address(wallet)
amount = w3.to_wei(token_amount, "ether")
allowance = token.functions.allowance(wallet, Web3.to_checksum_address(cfg["swapRouterAddress"])).call()
nonce = w3.eth.get_transaction_count(wallet)
if allowance < amount:
approve_tx = token.functions.approve(
Web3.to_checksum_address(cfg["swapRouterAddress"]),
2**256 - 1
).build_transaction({
"from": wallet,
"nonce": nonce,
"gas": 120000,
"gasPrice": w3.eth.gas_price,
"chainId": 56,
})
sign_and_send(w3, approve_tx, private_key)
nonce += 1
eth_out, platform_fee, inviter_fee = hook.functions.estimateSell(pool_id, amount, wallet).call()
min_eth_out = eth_out * (100 - slippage_pct) // 100
tx = router.functions.sell(key, amount, min_eth_out).build_transaction({
"from": wallet,
"nonce": nonce,
"gas": 650000,
"gasPrice": w3.eth.gas_price,
"chainId": 56,
})
return sign_and_send(w3, tx, private_key)
使用 Python 借款、追加借款和还款
def borrow_token(w3, token_address, token_amount, wallet, private_key):
cfg, token_data, factory, hook, router, token, pool_id, key = pool_context(w3, token_address)
wallet = Web3.to_checksum_address(wallet)
amount = w3.to_wei(token_amount, "ether")
nonce = w3.eth.get_transaction_count(wallet)
allowance = token.functions.allowance(wallet, Web3.to_checksum_address(cfg["hookAddress"])).call()
if allowance < amount:
approve_tx = token.functions.approve(
Web3.to_checksum_address(cfg["hookAddress"]),
2**256 - 1
).build_transaction({
"from": wallet,
"nonce": nonce,
"gas": 120000,
"gasPrice": w3.eth.gas_price,
"chainId": 56,
})
sign_and_send(w3, approve_tx, private_key)
nonce += 1
tx = hook.functions.borrow(pool_id, amount).build_transaction({
"from": wallet,
"nonce": nonce,
"gas": 650000,
"gasPrice": w3.eth.gas_price,
"chainId": 56,
})
return sign_and_send(w3, tx, private_key)
def borrow_more(w3, token_address, wallet, private_key):
cfg, token_data, factory, hook, router, token, pool_id, key = pool_context(w3, token_address)
wallet = Web3.to_checksum_address(wallet)
tx = hook.functions.borrowMore(pool_id).build_transaction({
"from": wallet,
"nonce": w3.eth.get_transaction_count(wallet),
"gas": 650000,
"gasPrice": w3.eth.gas_price,
"chainId": 56,
})
return sign_and_send(w3, tx, private_key)
def repay(w3, token_address, bnb_amount, wallet, private_key):
cfg, token_data, factory, hook, router, token, pool_id, key = pool_context(w3, token_address)
wallet = Web3.to_checksum_address(wallet)
tx = hook.functions.repay(pool_id).build_transaction({
"from": wallet,
"value": w3.to_wei(bnb_amount, "ether"),
"nonce": w3.eth.get_transaction_count(wallet),
"gas": 650000,
"gasPrice": w3.eth.gas_price,
"chainId": 56,
})
return sign_and_send(w3, tx, private_key)
常见错误
| 错误 | 修正方式 |
|---|---|
调用 estimateBuy(poolId, ethIn) | 使用 estimateBuy(poolId, ethIn, buyer) |
调用 estimateSell(poolId, tokenAmount) | 使用 estimateSell(poolId, tokenAmount, seller) |
构建 router 调用时没有使用正确的代币 PoolKey | 调用 factory.getPoolKey(tokenAddress),或严格按 V5 格式构建 PoolKey |
| 借款前没有授权代币 | 在调用 hook.borrow(poolId, amount) 前授权 Hook 地址 |
| 卖出前没有授权代币 | 在调用 router.sell(...) 前授权 Swap Router 地址 |
使用过期或缺失的 poolId | 使用 token.pool_id、factory.getTokenInfo(token).poolId 或 hook.tokenToPoolId(token) |
| 仍然期待旧手续费输出字段 | V5 返回 platformFee 和 inviterFee,不是 floorBoostFee |

