Ethers.js
Includes shared setup, estimates, buy, sell, borrow, borrowMore, and repay examples.
Viem
Shows typed public/wallet client reads and writes for the V5 flow.
Web3.js
Provides examples for integrations still using Web3.js.
Python Web3.py
Covers backend scripts, bots, and analytics workers that need direct V5 contract calls.
1
Resolve the token's poolId
Use API data when available, then fall back to
hook.tokenToPoolId(tokenAddress).2
Resolve or build the PoolKey
Prefer
factory.getPoolKey(tokenAddress) and only build manually when constants are known.3
Quote through the Hook
V5 estimate functions include the user wallet address because referral routing can affect fee recipients.
4
Execute buys and sells through the Swap Router
Swap execution uses the token-specific PoolKey.
5
Execute borrow, borrow more, and repay through the Hook
Lending actions use
poolId, and collateral approvals go to the Hook.Do not use old V3 examples for V5 tokens. In V5,
estimateBuy and estimateSell include the user address, createToken takes devBuyEth, and token trades must be routed with the correct PoolKey.Shared V5 concepts
type PoolKey = {
currency0: `0x${string}`; // native BNB represented as address(0)
currency1: `0x${string}`; // token address
fee: number; // usually 3000
tickSpacing: number; // usually 60
hooks: `0x${string}`; // Hook contract
};
token.pool_id from the API, but integrations should also know the fallback:
poolId = token.pool_id || token.poolId || hook.tokenToPoolId(tokenAddress)
factory.getPoolKey(tokenAddress) when available. If you already know the network constants, the same PoolKey can be built as:
{
currency0: '0x0000000000000000000000000000000000000000',
currency1: tokenAddress,
fee: 3000,
tickSpacing: 60,
hooks: hookAddress
}
Ethers.js v5
Shared setup
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);
}
Estimate buy
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()
};
}
Buy tokens
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();
}
Estimate sell
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()
};
}
Sell tokens
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();
}
Borrow
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();
}
Borrow more
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();
}
Repay
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();
}
Read user borrow state
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,
};
}
Buy with 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,
});
}
Sell with 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],
});
}
Borrow, borrow more, and repay with 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)
Buy with 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)
Sell with 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)
Borrow, borrow more, and repay with 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)
Common mistakes
| Mistake | Fix |
|---|---|
Calling estimateBuy(poolId, ethIn) | Use estimateBuy(poolId, ethIn, buyer) |
Calling estimateSell(poolId, tokenAmount) | Use estimateSell(poolId, tokenAmount, seller) |
Building a router call without the correct token PoolKey | Call factory.getPoolKey(tokenAddress) or build the V5 PoolKey exactly |
| Borrowing without token approval | Approve the Hook address before hook.borrow(poolId, amount) |
| Selling without token approval | Approve the Swap Router address before router.sell(...) |
Using a stale or missing poolId | Use token.pool_id, factory.getTokenInfo(token).poolId, or hook.tokenToPoolId(token) |
| Expecting old fee output names | V5 returns platformFee and inviterFee, not floorBoostFee |

