> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lfg.rich/llms.txt
> Use this file to discover all available pages before exploring further.

# 协议示例

> 使用 Ethers、Viem、Web3.js 和 Python Web3.py 与 LFG.RICH V5 合约交互的示例。

这些示例遵循 LFG.RICH 应用本身使用的 V5 模式：

<CardGroup cols={2}>
  <Card title="Ethers.js" icon="code">
    包含共享设置、估算、买入、卖出、借款、追加借款和还款示例。
  </Card>

  <Card title="Viem" icon="terminal">
    展示 V5 流程的类型化 public client / wallet client 读取与写入。
  </Card>

  <Card title="Web3.js" icon="brackets-curly">
    为仍在使用 Web3.js 的集成方提供示例。
  </Card>

  <Card title="Python Web3.py" icon="python">
    覆盖需要直接调用 V5 合约的后端脚本、机器人和分析任务。
  </Card>
</CardGroup>

<Steps>
  <Step title="解析代币的 poolId">
    可用时使用 API 数据，然后 fallback 到 `hook.tokenToPoolId(tokenAddress)`。
  </Step>

  <Step title="解析或构建 PoolKey">
    优先使用 `factory.getPoolKey(tokenAddress)`，只有在已知常量时才手动构建。
  </Step>

  <Step title="通过 Hook 获取报价">
    V5 估算函数包含用户钱包地址，因为推荐路由会影响费用接收方。
  </Step>

  <Step title="通过 Swap Router 执行买入和卖出">
    交易执行使用该代币对应的 PoolKey。
  </Step>

  <Step title="通过 Hook 执行 borrow、borrow more 和 repay">
    借贷操作使用 `poolId`，抵押品授权给 Hook。
  </Step>
</Steps>

<Warning>
  不要把旧的 V3 示例用于 V5 代币。在 V5 中，`estimateBuy` 和 `estimateSell` 都需要用户地址，`createToken` 使用 `devBuyEth`，并且代币交易必须使用正确的 `PoolKey` 路由。
</Warning>

## V5 共享概念

```ts theme={null}
type PoolKey = {
  currency0: `0x${string}`; // 原生 BNB 表示为 address(0)
  currency1: `0x${string}`; // 代币地址
  fee: number;              // 通常为 3000
  tickSpacing: number;      // 通常为 60
  hooks: `0x${string}`;     // Hook 合约
};
```

应用可以使用 API 返回的 `token.pool_id`，但集成方也应该知道备用解析方式：

```txt theme={null}
poolId = token.pool_id || token.poolId || hook.tokenToPoolId(tokenAddress)
```

对于 swap，优先在可用时调用 `factory.getPoolKey(tokenAddress)`。如果已经知道网络常量，也可以这样构建相同的 PoolKey：

```js theme={null}
{
  currency0: '0x0000000000000000000000000000000000000000',
  currency1: tokenAddress,
  fee: 3000,
  tickSpacing: 60,
  hooks: hookAddress
}
```

## Ethers.js v5

### 共享设置

```js theme={null}
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);
}
```

### 估算买入

```js theme={null}
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()
  };
}
```

### 买入代币

```js theme={null}
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();
}
```

### 估算卖出

```js theme={null}
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()
  };
}
```

### 卖出代币

```js theme={null}
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();
}
```

### 借款

```js theme={null}
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();
}
```

### 追加借款

```js theme={null}
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();
}
```

### 还款

```js theme={null}
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();
}
```

### 读取用户借款状态

```js theme={null}
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

```ts theme={null}
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 买入

```ts theme={null}
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 卖出

```ts theme={null}
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 借款、追加借款和还款

```ts theme={null}
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

```js theme={null}
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

```python theme={null}
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 买入

```python theme={null}
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 卖出

```python theme={null}
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 借款、追加借款和还款

```python theme={null}
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`                                 |
